Задача: Асинхронный загрузчик данных и обработка состояния | Вопросы для собеседования | Skilio
Задача: Асинхронный загрузчик данных и обработка состояния
Вопрос:

Реализовать универсальный TypeScript React-компонент для обработки асинхронной загрузки данных. Показать, как обеспечить типобезопасность для состояний успеха и ошибки.

Подсказки:

  1. Рассмотрите использование хуков useState и useEffect React для управления данными.
  2. Подумайте о том, как обработать безопасным для типов способом состояния загрузки, успеха и ошибки.
  3. Возможно, стоит изучить discriminated unions для управления состоянием.

Выше ожиданий:

  • Расширенный вывод типов с условными типами
  • Типы высших порядков для паттернов обработки ошибок
  • Понимание ключевого слова infer и предикатных типов TypeScript
  • Знание интеграции React Suspense
Ответ:

План ответа:

  1. Создать базовые типы для обработки асинхронных состояний (загрузка/успех/ошибка)
  2. Реализовать компонент общего назначения для получения данных с типо-безопасностью
  3. Вывод типов (type inference) для различных состояний
  4. Обработка ошибок с помощью предикатных типов
  5. Добавить интеграцию React Suspense для лучшего пользовательского опыта

1. Базовые типы и управление состояниями

Сначала определим типобезопасное управление состояниями, используя discriminated unions, для обработки различных состояний асинхронной операции:

// Базовые типы для различных состояний
type LoadingState = {
  status: 'loading';
};

type SuccessState<T> = {
  status: 'success';
  data: T;
};

type ErrorState = {
  status: 'error';
  error: Error;
};

// Объединенный тип с помощью discriminated union
type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

2. Универсальный асинхронный компонент

Создадим переиспользуемый компонент, который обрабатывает асинхронное получение данных с соответствующей типизацией:

// Тип функции для получения данных
type FetchFn<T> = () => Promise<T>;

// Основной компонент с универсальным параметром типа
function AsyncDataLoader<T>({
  fetchFn,
  render
}: {
  fetchFn: FetchFn<T>;
  render: (state: AsyncState<T>) => React.ReactNode;
}) {
  // Типобезопасное управление состоянием
  const [state, setState] = useState<AsyncState<T>>({ status: 'loading' });

  useEffect(() => {
    const loadData = async () => {
      try {
        const data = await fetchFn();
        setState({ status: 'success', data });
      } catch (error) {
        setState({ 
          status: 'error', 
          error: error instanceof Error ? error : new Error('Неизвестная ошибка') 
        });
      }
    };

    loadData();
  }, [fetchFn]);

  return render(state);
}

3. Предикаты типов для безопасного управления состояниями

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

// Предикаты типов для проверки состояния
function isLoading<T>(state: AsyncState<T>): state is LoadingState {
  return state.status === 'loading';
}

function isSuccess<T>(state: AsyncState<T>): state is SuccessState<T> {
  return state.status === 'success';
}

function isError<T>(state: AsyncState<T>): state is ErrorState {
  return state.status === 'error';
}

4. Пример использования с безопасностью типов

// Пример интерфейса для данных
interface User {
  id: number;
  name: string;
}

// Использование компонента
function UserProfile() {
  const fetchUser: FetchFn<User> = async () => {
    const response = await fetch('/api/user');
    return response.json();
  };

  return (
    <AsyncDataLoader
      fetchFn={fetchUser}
      render={(state) => {
        // TypeScript гарантирует правильную проверку типов
        if (isLoading(state)) {
          return <div>Загрузка...</div>;
        }

        if (isError(state)) {
          return <div>Ошибка: {state.error.message}</div>;
        }

        // state интерпретируется как SuccessState<User>
        return <div>Добро пожаловать, {state.data.name}!</div>;
      }}
    />
  );
}

5. Расширенный шаблон с интеграцией Suspense

Реализуем более сложный шаблон с использованием React Suspense:

// Тип ресурса для Suspense
type Resource<T> = {
  read(): T | undefined;
};

// Создать ресурс, совместимый с Suspense
function createResource<T>(fetchFn: FetchFn<T>): Resource<T> {
  let status: 'pending' | 'success' | 'error' = 'pending';
  let result: T;
  let error: Error;

  const promise = fetchFn()
    .then(
      (data: T) => {
        status = 'success';
        result = data;
      },
      (e: Error) => {
        status = 'error';
        error = e;
      }
    );

  return {
    read() {
      switch (status) {
        case 'pending':
          throw promise;
        case 'error':
          throw error;
        case 'success':
          return result;
      }
    }
  };
}

// Использование с Suspense
function SuspenseUser() {
  const resource = createResource<User>(fetchUser);

  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <UserData resource={resource} />
    </Suspense>
  );
}

// Типобезопасный компонент, использующий ресурс
function UserData({ resource }: { resource: Resource<User> }) {
  const user = resource.read();
  return <div>Добро пожаловать, {user?.name}!</div>;
}

Эта реализация обеспечивает:

  • Типо-безопасность во всех асинхронных состояниях
  • Универсальную реализацию для любого типа данных
  • Обработку ошибок с соответствующей типизацией
  • Интеграцию React Suspense
  • Предикаты типов для безопасного сужения состояний
0
TypeScript Средний Опубликовано
© Skilio, 2025
Условия использования
Политика конфиденциальности
Мы используем файлы cookie, для персонализации сервисов и повышения удобства пользования сайтом. Если вы не согласны на их использование, поменяйте настройки браузера.