React w/TypeScript:为什么当 useState 将回调作为更新程序时,状态的类型会丢失

Joj*_*oji 12 typescript reactjs

首先,您将看到的代码片段是一个人为的示例。我只是好奇为什么打字不起作用。

interface MyState {
  a: boolean;
  b: number;
}

const defaultState: MyState = {
  a: false,
  b: 1
};

const keys = Object.keys(defaultState) as Array<keyof MyState>;

export default function App() {
  const [myState, setMyState] = React.useState<MyState>(defaultState);

  React.useEffect(() => {
    keys.forEach((key) => {
      setMyState((prev) => ({
        ...prev,
        [key]: "lol". // should've given an error but it didn't
      }));
    });
  }, []);

Run Code Online (Sandbox Code Playgroud)

所以我有一个defaultState和它在组件外部定义的键列表。在 useEffect 中,我使用密钥来更新状态(现在它只是将每个值更改为“lol”)。我传递了一个回调作为 中的更新程序,setMyState它返回的应该是新状态。但是在这里我将值设为字符串,这违反了我拥有的接口MyState

我想知道为什么它没有触发错误

这是一个现场演示:https : //codesandbox.io/s/sweet-dewdney-u83id?file=/ src/ App.tsx

没有反应的最小例子:游乐场

Set*_*ske 3

问题不在于使用回调来更新状态 - 这很好。问题是,当您使用动态键设置对象的属性时,打字稿不知道循环的任何给定迭代的键值是什么,更不用说它的值应该是什么类型了。所以你需要告诉它。您需要使用泛型动态键入密钥。这里有一篇关于它的很棒的文章。

  function updateValue<T extends keyof MyState, K extends MyState[T]>(
    name: T,
    value: K
  ) {
    setMyState(prev => ({ ...prev, [name]: value }));
  }

  React.useEffect(() => {
    keys.forEach((key) => {
      updateValue(key, "lol");
    });
  }, []);
Run Code Online (Sandbox Code Playgroud)

这是一个工作代码沙箱

工作打字稿游乐场

完全按照应有的方式给出错误。

进一步的思考:

这有一些有趣的怪癖。如果将该值设置为界面上可能的值之一会怎样?

keys.forEach((key) => {
 updateValue(key, 4);  // <---- no error, but there should be
});
Run Code Online (Sandbox Code Playgroud)

理论上,这也应该引发错误,因为key可能是a,在这种情况下类型不匹配。TypeScript 不会抛出错误。但是,如果您指定仅在 时运行此命令key === 'a',则会出现错误:

keys.forEach((key) => {
  if (key === 'a'){
    updateValue(key, 4); // <---- typechecking error occurs
  }
});
Run Code Online (Sandbox Code Playgroud)

在后一种情况下,打字稿足够聪明,知道如果键是a,则类型必须是数字。但在前一种情况下,当您尝试分配一个与接口上的一种类型(但不是所有类型)匹配的值时,我们可能会看到错误,或者达到打字稿的限制。