网站搜索

reactjs - 设置状态类型缩小


我有一个函数式组件,接收从 SSR(NextJS)传来的 props,并且内部有如下的状态:

const Component = ({err, n}: {err?: ErrorType, n?: N})=>{
    const [x, setX] = useState(n || null)
    ...
    if(err){
        return <ErrorComponent err={err}>
    }
    else if(!(x===null)){
        return <SomeComponent setX={setX}>
    }
}

x 可以被看作是类型 N | null。然而,在“类型缩小” if 条件之后,虽然 x 的类型现在是 N,但 setX 的类型仍然是 Dispatch<SetState<N | null>>。然而,SomeComponent 期望 setX 的类型是 Dispatch<SetState<N>>。

我如何在不使用类型断言的情况下强制类型缩小?

我也尝试过这个,但 React 抛出了一个 React Hook "useState" 是有条件调用的。React Hooks 必须在每次组件渲染中以完全相同的顺序调用。 错误。

const Component = ({err, n}: {err?: ErrorType, n?: N})=>{
    if(err || !n){
        return <ErrorComponent err={err}>
    }
    const [x, setX] = useState(n)
    return <SomeComponent setX={setX}>
}

答案#1

Typescript 按预期工作。它不会缩小类型,因为这样做不安全。

Dispatch<SetState<N>> 不能赋值给 Dispatch<SetState<N | null>>。让我们来详细解析一下。

Dispatch<SetState<N>> 等价于 ((action: N | ((prevValue: N) => N)) => void.

(action: N) => void 可以赋值给 (action: N | null) => void,但是 (action: (prevValue: N) => N) => void 不能赋值给 (action: (prevValue: N | null) => N | null) => void。

x 非空并不意味着 setX 会提供非空的 prevValue,因为 x 可能是过时的。在这方面,这两种类型是独立的。


如果你可以不用 set state 动作,你可以将 setX 属性类型设置为 (action: N) => void。这样它就可以赋值给 Dispatch<SetState<N | null>>。当然,缺点是 SomeComponent 不能用一个函数来调用 setX。


如果你需要在 SomeComponent 中使用函数设置状态的操作,你必须传入一个在调用操作回调之前执行运行时类型检查的函数,以使其真正类型安全。

以下对 setX 的扩展用一个无操作更新来替换类型不安全的调用。

const typeSafeSetX = (action: SetState<N | null>) => typeof action === 'function' ? setX(prevValue => prevValue === null ? prevValue : action(prevValue)) : setX(action);

我不建议你这样做,因为这只是用一种不良行为替代另一种不良行为。