Реализовать универсальный TypeScript React-компонент для обработки асинхронной загрузки данных. Показать, как обеспечить типобезопасность для состояний успеха и ошибки.
Подсказки:
- Рассмотрите использование хуков
useState
иuseEffect
React для управления данными. - Подумайте о том, как обработать безопасным для типов способом состояния загрузки, успеха и ошибки.
- Возможно, стоит изучить discriminated unions для управления состоянием.
Выше ожиданий:
- Расширенный вывод типов с условными типами
- Типы высших порядков для паттернов обработки ошибок
- Понимание ключевого слова
infer
и предикатных типов TypeScript - Знание интеграции React Suspense
План ответа:
- Создать базовые типы для обработки асинхронных состояний (загрузка/успех/ошибка)
- Реализовать компонент общего назначения для получения данных с типо-безопасностью
- Вывод типов (type inference) для различных состояний
- Обработка ошибок с помощью предикатных типов
- Добавить интеграцию 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
- Предикаты типов для безопасного сужения состояний