JavaRush /Курсы /Модуль 3: React /Типизация контекста и данных, передаваемых через контекст...

Типизация контекста и данных, передаваемых через контекст

Модуль 3: React
2 уровень , 6 лекция
Открыта

Типизация контекста: основы

Контекст — это отличный инструмент для управления состоянием приложения. Но как только в вашем приложении появляется сложная структура данных, ошибки в передаваемых значениях могут становиться кошмаром для отладки. TypeScript позволяет нам гарантировать, что значения в контексте имеют ожидаемые типы. Это особенно полезно, когда контекст распространяется по всему приложению, и вы хотите быть уверены, что ваши компоненты получают только те данные, которые ожидаются.

Давайте начнём с простого примера и планомерно усложним его.

Создание интерфейса для контекста

Допустим, мы работаем с простым состоянием пользователя, в котором хранятся его имя и информацию об авторизации. Сначала определим интерфейс.

interface UserContextType {
  name: string;
  isLoggedIn: boolean;
}

Теперь, создавая контекст с этим интерфейсом, мы можем использовать React.createContext:

import React, { createContext } from 'react';

// Объявляем дефолтное значение контекста
const defaultUserContext: UserContextType = {
  name: '',
  isLoggedIn: false,
};

// Создаём сам контекст
const UserContext = createContext<UserContextType>(defaultUserContext);

export default UserContext;

В этом примере мы сразу определили интерфейс UserContextType, который будет использоваться для типизации данных в контексте. Это простой, но мощный способ избежать ошибок, поскольку в дальнейшем TypeScript будет проверять любые данные, которые мы пытаемся передать в контекст.

Типизация значений, передаваемых через контекст

Использование контекста в компонентах

Теперь давайте используем наш контекст в компоненте. Для этого воспользуемся хуком useContext.

import React, { useContext } from 'react';
import UserContext from './UserContext';

const UserProfile: React.FC = () => {
  // Получаем данные из контекста
  const { name, isLoggedIn } = useContext(UserContext);

  return (
    <div>
      <h1>Профиль пользователя</h1>
      {isLoggedIn ? <p>Добро пожаловать, {name}!</p> : <p>Вы не авторизованы.</p>}
    </div>
  );
};

export default UserProfile;

Отлично! Теперь, если мы забудем передать имя или значение isLoggedIn в поставщике контекста (Context Provider), TypeScript моментально предупредит нас об ошибке.

Типизация контекста с помощью дополнительных интерфейсов

Добавление методов в контекст

Часто контекст передаёт не просто данные, но и функции для их обновления. Давайте добавим метод для логина пользователя.

interface UserContextType {
  name: string;
  isLoggedIn: boolean;
  login: (name: string) => void;
}

// Дефолтное значение должно включать и метод login
const defaultUserContext: UserContextType = {
  name: '',
  isLoggedIn: false,
  login: () => {}, // Заглушка по умолчанию
};

const UserContext = createContext<UserContextType>(defaultUserContext);

Реализация контекста с провайдером

Давайте теперь расширим наш контекст, добавив провайдер, который будет управлять состоянием.

import React, { useState, FC } from 'react';
import UserContext from './UserContext';

const UserProvider: FC = ({ children }) => {
  const [name, setName] = useState('');
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  // Метод для логина
  const login = (userName: string) => {
    setName(userName);
    setIsLoggedIn(true);
  };

  // Передаём состояние и метод в провайдер
  return (
    <UserContext.Provider value={{ name, isLoggedIn, login }}>
      {children}
    </UserContext.Provider>
  );
};

export default UserProvider;

Теперь мы можем завернуть все компоненты нашего приложения в UserProvider, чтобы данные контекста name, isLoggedIn, login были доступны везде.

Типизация контекста с использованием useReducer

Для более сложного состояния можно использовать useReducer. В таком случае типизация становится немного сложнее, но от этого интереснее.

Интерфейсы для состояния и действий

interface UserState {
  name: string;
  isLoggedIn: boolean;
}

type UserAction =
  | { type: 'LOGIN'; payload: string }
  | { type: 'LOGOUT' };

Редьюсер и контекст

Определим редьюсер для управления состоянием:

const userReducer = (state: UserState, action: UserAction): UserState => {
  switch (action.type) {
    case 'LOGIN':
      return { name: action.payload, isLoggedIn: true };
    case 'LOGOUT':
      return { name: '', isLoggedIn: false };
    default:
      return state;
  }
};

Теперь создадим наш контекст:

interface UserContextType {
  state: UserState;
  dispatch: React.Dispatch<UserAction<;
}

const defaultUserContext: UserContextType = {
  state: { name: '', isLoggedIn: false },
  dispatch: () => {}, // Заглушка по умолчанию
};

const UserContext = createContext<UserContextType<(defaultUserContext);

Провайдер для контекста

Как и раньше, мы должны создать провайдер, который передаёт данные из редьюсера в контекст.

const UserProvider: FC = ({ children }) => {
  const [state, dispatch] = React.useReducer(userReducer, defaultUserContext.state);

  return (
    <UserContext.Provider value={{ state, dispatch }}>
      {children}
    </UserContext.Provider>
  );
};

export default UserProvider;

Теперь состояние state и функция dispatch доступны в любом компоненте через useContext.

Практическое применение: работа с типизированным контекстом

Допустим, нам нужно создать компонент, который позволяет пользователю логиниться и выходить из приложения.

import React, { useContext } from 'react';
import UserContext from './UserContext';

const LoginButton: React.FC = () => {
  const { state, dispatch } = useContext(UserContext);

  const handleLogin = () => {
    const userName = prompt('Введите ваше имя:') || '';
    dispatch({ type: 'LOGIN', payload: userName });
  };

  const handleLogout = () => {
    dispatch({ type: 'LOGOUT' });
  };

  return (
    <div>
      {state.isLoggedIn ? (
        <>
          <p>Привет, {state.name}!</p>
          <button onClick={handleLogout}>Выйти</button>
        </>
      ) : (
        <button onClick={handleLogin}>Войти</button>
      )}
</div>
);
};

export default LoginButton;

Готово! Мы создали полностью типизированный контекст, интегрировали его с useReducer и обеспечили корректную передачу данных.

1
Задача
Модуль 3: React, 2 уровень, 6 лекция
Недоступна
Очень лёгкая задача
Очень лёгкая задача
1
Задача
Модуль 3: React, 2 уровень, 6 лекция
Недоступна
Лёгкая задача
Лёгкая задача
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ