Реагировать с машинописным текстом: почему теряется тип состояния при использовании состояния с обратным вызовом в качестве средства обновления

#reactjs #typescript

#reactjs ( реакция ) #машинописный текст #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
      }));
    });
  }, []);

  

итак, у меня есть defaultState и список его ключей, определенных вне компонента. В useEffect я использую ключ для обновления состояния (прямо сейчас он просто меняет каждое значение на ‘lol’). Я передал обратный вызов в качестве средства обновления в setMyState , и то, что он возвращает, должно быть новым состоянием. Но здесь я делаю значение строковым, что нарушает интерфейс, который у меня был MyState .

Интересно, почему это не вызывает ошибку

Это живая демо-версия:https://codesandbox.io/s/sweet-dewdney-u83id?file=/src/App.tsx

Минимальный пример без реакции: Игровая площадка

Комментарии:

1. Может быть просто ошибка!

2. попробуйте прочитать переменную в обратном вызове React.useEffect(() => { ... }, [keys])

3. Это кажется ошибочным, если мы используем подпись индекса: interface MyState { a: boolean; b: number; [key: string]: number | boolean} ts выдает ошибку, близкую к той, которую я ожидал бы без нее

4. Часть того, что происходит, заключается в том, что этого ...prev достаточно для выполнения всего MyState интерфейса, и [key]: "lol" это просто рассматривается как нечто дополнительное. Typescript должен быть в состоянии видеть, что вы переопределяете свойство и делаете состояние недействительным, но это не так умно.

5. Ну, нет, потому что [key] перезаписывает существующие значения в предыдущем. Если вы правильно введете ключи и значения, как в моем ответе, вы все равно получите правильную ошибку ввода. Я скорректировал свой ответ и CSB для использования setMyState(prev => ({ ...prev, [key]: value })) шаблона … по-прежнему правильно отображает ошибку

Ответ №1:

Проблема не в использовании обратного вызова для обновления состояния — это нормально. Проблема в том, что когда вы устанавливаете свойство объекта с использованием динамического ключа, typescript не знает, каково значение ключа для любой заданной итерации цикла, не говоря уже о том, какого типа должно быть его значение. Итак, вам нужно сообщить об этом. Вам нужно динамически вводить свои ключи, используя общие. Об этом есть отличная статья прямо здесь.

   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");
    });
  }, []);
  

Вот рабочий codesandbox

Рабочая площадка для машинописи

Выдает ошибки точно так, как должно.

Дальнейшие размышления:

В этом есть несколько интересных особенностей. Что, если вы установите значение в одно из возможных значений в вашем интерфейсе?

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

Теоретически, это также должно выдавать ошибку, потому что key может быть a , и в этом случае типы не совпадают. TypeScript не выдает ошибку. Однако, если вы укажете запускать это только тогда, когда key === 'a' возникает ошибка:

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

В последнем случае typescript достаточно умен, чтобы знать, что если ключ a , тип должен быть числом. Но в первом случае, когда вы пытаетесь присвоить значение, соответствующее одному из типов в интерфейсе, но не всем из них, мы можем видеть ошибку или достижение пределов typescript.

Комментарии:

1. К вашему сведению, вы можете использовать функцию prevState : reactjs.org/docs/hooks-reference.html#functional-updates

2. Каждый день изучайте что-то новое! Я удалю это