对来自 React.useContext (或其他任何地方)的值断言非空时的最佳实践

Bri*_*ian 10 typescript reactjs react-hooks

在我的 React 应用程序中,我使用非空断言 (!) 来告诉 TS 来自useContext钩子的值不为空。它运行良好,似乎是非空断言的教科书用例,但根据推荐的 eslint 规则,非空断言是一个警告

作为 TypeScript 的新手,我想确保在处理这些情况时不会错过通用模式或最佳实践方法。或者也许有不同的方式来键入user下面示例中的对象。

  1. 状态在独立文件中定义。
// context.ts
interface State {
  user: User | null; // User type is defined elsewhere, has 'id' and 'email'
}

const state: State = {
  user: null,
};
Run Code Online (Sandbox Code Playgroud)
  1. 如果用户已在定义中登录state.user
// App.tsx
return state.user ? <Dashboard /> : <Login />;
Run Code Online (Sandbox Code Playgroud)
  1. 如果不为空,用户只能看到仪表板state.user,但我仍然看到以下错误。
// Dashboard.tsx
const { state } = React.useContext(context);

const name = state.user.firstName; // Object is possibly 'null'.
const { id, email } = state.user; // Property 'id/email' does not exist on type 'User | null'.
Run Code Online (Sandbox Code Playgroud)
  1. 注销方法设置,state = { user: null }因此 null 必须可分配给该User类型。

预期可能的答案不能解决我关于最佳实践/模式的问题:

  • 我知道如何禁用@typescript-eslint/no-non-null-assertion规则,这不是我正在寻找的解决方案。

  • 我知道如果我在第一个示例中使用可选链接,则可以避免错误和非空断言警告,如下所示:const name = state.user?.firstName;但这并不能回答我的问题。在这种情况下,我实际上更喜欢非空断言,因为会更明确。

谢谢!

T.J*_*der 7

有几个选项供您选择:

  1. 明确检查user不是,null如果是则抛出

  2. 定义一个“已登录”状态接口并编写一个钩子,在检查user未登录后返回该状态接口null

  3. 定义一个“已登录”状态接口并编写一个类型断言函数Dashboard,然后在或钩子中使用它

它们之间的共同主题不仅仅是断言 that userisn't null,而是在运行时证明它,以便主动且清晰地捕获使用statewith的编程错误null,而不是看似随机的“Can't use X of null”错误。

1.显式检查

由于用户只有Dashboard在登录后才能看到该组件,而当他们登录时,上下文user成员将不会是null,因此您可以显式检查:

const { state } = React.useContext(context);
const { user } = state;
if (!user) {
    throw new Error(`ERROR: User reached Dashboard with null 'user' in context`);
}
// ...code uses `user`...
Run Code Online (Sandbox Code Playgroud)

这样,TypeScript 就知道组件代码的其余部分user不是null,并且如果编程错误导致尝试使用Dashboardwhen useris ,您会得到一个明显的错误null

2.LoggedInState和一个钩子

或者,您可以定义LoggedInState

interface LoggedInState {
    user: User;
}
Run Code Online (Sandbox Code Playgroud)

并编写一个可重用的钩子:

function useLoggedInContext(context: ContextType): LoggedInState {
    const { state } = React.useContext(context);
    if (!state.user) {
        throw new Error(`ERROR: User reached logged-in-only component with null 'user' in context`);
    }
    return state as LoggedInState;
}
Run Code Online (Sandbox Code Playgroud)

它仍然有一个类型断言,但在一个可重用的位置,上面的运行时代码证明它是一个正确的断言。

然后在Dashboard(和其他地方):

const { state } = useLoggedIncontext(context);
// ...
Run Code Online (Sandbox Code Playgroud)

3.类型断言函数

您可以将其包装在类型断言函数中:

function assertIsLoggedInState(state: State): asserts state is LoggedInState {
    if (!state.user) {
        throw new AssertionError(`'state' has a null 'user' member, expected a logged-in state`);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后直接在以下位置使用它Dashboard

const { state } = React.useContext(context);
assertIsLoggedInState(state);
// ...code can use `state.user`, which TypeScript now knows can't be `null`
Run Code Online (Sandbox Code Playgroud)

或者在该钩子中使用它useLoggedInContext

function useLoggedInContext(context: ContextType): LoggedInState {
    const { state } = React.useContext(context);
    assertIsLoggedInState(state);
    return state; // TypeScript knows this is a LoggedInState
}
Run Code Online (Sandbox Code Playgroud)

作为对第一个的一个小调整:如果您不需要state其他任何内容,您可以合并前两个语句:

const { state: { user } } = React.useContext(context);
// ...
Run Code Online (Sandbox Code Playgroud)

那只宣示user,不宣示state。如果您还有其他state需要的东西,可以在之后添加user

  • 这是一个非常深入的答案,并提供了一些很好的建议。感谢那!我断言而不是证明的理由是,已经有 __is__ 一个 if 语句(在 App.tsx 中),TS 只是不知道它。但我想没有什么可以阻止有人在这个 if 语句之外滥用仪表板组件。在这种情况下,包括对组件本身的检查更有意义。再次感谢。 (2认同)