Ошибка "Property 'value' does not exist on type 'Readonly<{}>'" в React и TypeScript
Value: {state.value}
Причина ошибки
Ошибка возникает из-за того, что вы инициализируете useState пустым объектом {}. TypeScript автоматически выводит тип этого состояния как Readonly<{}> — пустой объект, у которого нет никаких свойств. Когда вы пытаетесь обратиться к state.value, TypeScript справедливо сообщает, что такого свойства не существует.
Проблема не в том, что вы добавляете свойство через setState, а в том, что TypeScript проверяет типы статически (на этапе компиляции), а не во время выполнения. Он не знает, что вы планируете добавить туда свойство value позже.
Правильное решение: явное указание типа
Нужно явно указать тип состояния при вызове useState. Есть несколько способов:
- Способ 1: Интерфейс для объекта (рекомендуется для сложных состояний)
- Способ 2: Инлайн-тип (для простых случаев)
Способ 1: Определите интерфейс
import React, { useState } from 'react';
// Определяем интерфейс для состояния
interface MyState {
value: string;
}
function MyComponent() {
// Указываем тип явно
const [state, setState] = useState<MyState>({ value: '' });
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState({ value: e.target.value });
};
return (
<div>
<input type="text" onChange={handleChange} />
<p>Value: {state.value}</p>
</div>
);
}
Способ 2: Инлайн-тип (для простых случаев)
const [state, setState] = useState<{ value: string }>({ value: '' });
Важные моменты
- Инициализация должна соответствовать типу: Если тип
{ value: string }, то начальное значение должно содержать это свойство. Нельзя писатьuseState<{ value: string }>({})— это тоже вызовет ошибку. - Если свойство может быть необязательным: Используйте
value?: stringи проверяйте наличие. - Для нескольких полей: Добавляйте их в интерфейс и инициализируйте все сразу.
Альтернатива: использование примитива
Если вам нужно хранить только одно строковое значение, не используйте объект — храните строку напрямую:
const [value, setValue] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
return <p>Value: {value}</p>;
Это самый простой и правильный способ для данного случая. Тип string выводится автоматически из начального значения ''.
Почему это работает
Когда вы явно указываете тип useState<{ value: string }>({ value: '' }), TypeScript знает, что в состоянии есть свойство value типа string. При вызове setState он проверяет, что новый объект соответствует этому типу. При чтении state.value он знает, что это строка, и вы можете безопасно использовать её в JSX.
Это одна из ключевых особенностей TypeScript — статическая типизация помогает отлавливать ошибки на этапе разработки, а не во время выполнения.
<<Использование Record для динамических ключей
Если вам действительно нужно, чтобы объект мог содержать произвольные ключи (не только value), используйте тип Record<string, any> или более точный вариант:
import React, { useState } from 'react';
function MyComponent() {
const [state, setState] = useState<Record<string, string>>({});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState({ ...state, value: e.target.value });
};
return (
<div>
<input type="text" onChange={handleChange} />
<p>Value: {state.value}</p>
</div>
);
}
Record<string, string> означает объект, где ключи — строки, а значения — тоже строки. TypeScript разрешит обратиться к state.value, но не будет знать конкретный тип значения (будет string | undefined).
Важно: Этот подход менее строгий, чем явное определение интерфейса. Используйте его только если количество и названия полей действительно неизвестны заранее. Для большинства случаев лучше определить конкретную структуру объекта.
Также можно использовать Partial<MyState>, если некоторые поля могут отсутствовать:
interface MyState {
value: string;
label?: string;
}
const [state, setState] = useState<Partial<MyState>>({});
В этом случае TypeScript будет ожидать, что свойства могут быть undefined, и вам придётся использовать state.value ?? '' или проверять наличие.
Ответов пока нет.
Войдите чтобы оставить ответ