#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-updates2. Каждый день изучайте что-то новое! Я удалю это