Урок 04

useState с типами

Простые типы

const [count, setCount] = useState(0);
// number

const [name, setName] = useState('');
// string

const [active, setActive] = useState(false);
// boolean

TypeScript выводит тип из начального значения.

Union

const [status, setStatus] = useState<'idle' | 'loading' | 'success'>('idle');

Или через as const:

const [status, setStatus] = useState('idle' as 'idle' | 'loading' | 'success');

Object

type User = {
    name: string;
    age: number;
};

const [user, setUser] = useState<User | null>(null);

null в начальном значении требует union.

Массив

const [items, setItems] = useState<string[]>([]);

Без явного типа TypeScript выведет never[] для пустого массива.

const [items, setItems] = useState<string[]>([]); // правильно
const [items, setItems] = useState([]); // never[]

Массив объектов

type Post = { id: number; title: string };

const [posts, setPosts] = useState<Post[]>([]);

Пустой объект

const [data, setData] = useState<Record<string, unknown>>({});

Ленивая инициализация

const [data, setData] = useState<User | null>(() => {
    const stored = localStorage.getItem('user');
    return stored ? JSON.parse(stored) : null;
});

Функциональное обновление

setCount((prev) => prev + 1);

Тип prev выводится из count.

Обновление объекта

setUser((prev) => (prev ? { ...prev, name: 'Боб' } : null));

Обновление массива

setItems((prev) => [...prev, 'новый']);
setItems((prev) => prev.filter((i) => i !== 'удалить'));

Несколько состояний

const [name, setName] = useState('');
const [email, setEmail] = useState('');

Или одно:

type FormState = {
    name: string;
    email: string;
};

const [form, setForm] = useState<FormState>({ name: '', email: '' });

Дискриминатор

type State =
    | { status: 'idle' }
    | { status: 'loading' }
    | { status: 'success'; data: User }
    | { status: 'error'; error: Error };

const [state, setState] = useState<State>({ status: 'idle' });

При обновлении проверяется, что поля соответствуют статусу:

setState({ status: 'success', data: user }); // ок
setState({ status: 'success' }); // ошибка

Обновление через reducer

setState((prev) => {
    if (prev.status === 'loading') {
        return { status: 'success', data: user };
    }
    return prev;
});

Generic-хук

function useToggle(initial: boolean): [boolean, () => void] {
    const [value, setValue] = useState(initial);
    const toggle = useCallback(() => setValue((v) => !v), []);
    return [value, toggle];
}

С дженериком:

function useArray<T>(initial: T[] = []): [T[], (item: T) => void] {
    const [items, setItems] = useState<T[]>(initial);
    const add = useCallback((item: T) => setItems((prev) => [...prev, item]), []);
    return [items, add];
}

const [users, addUser] = useArray<User>();

Итоги

  • Простые типы выводятся
  • Пустой массив — явный тип
  • null — через union
  • as const для литералов
  • Дискриминатор — для сложных состояний
  • Generic для переиспользуемых хуков