В этой серии, а не используя библиотеку государства-менеджмента или предложение одного различия – все решения, мы начинаем с минимального минимума, и мы создаем наше государственное управление, поскольку нам это нужно.
- В этой первой статье мы опишем, как мы загружаем и отображаем данные с крючками.
- Во второй статье мы узнаем, как менять удаленные данные с крючками.
- В третьей статье мы увидим, как делиться данными между компонентами с контекстом React, не используя глобал, синглтонов или прибегающих к библиотекам государственных управлений, таких как MOBX или Redux.
- В четвертой статье мы увидим, как делиться данными между компонентами, использующими SWR , что, вероятно, что мы должны были сделать с самого начала.
Последний код можно найти в этом Github repo. . Это типограф, но аннотации типа минимальны. Также обратите внимание, что это не код производства. Для того, чтобы сосредоточиться на государственном управлении, многие другие аспекты не были рассмотрены (например, Инверсия зависимости , тестирование или оптимизация).
Загрузка данных с крючками
Допустим, у нас есть API отдыха со списком Commodore 64 Игры. Я имею в виду, почему бы не?
Требование: Мы хотим загрузить список и отображать игры.
1. Базовая выборка
Вот как мы извлекаем наш список игр с сервера:
const getGames = () => {
return fetch('http://localhost:3001/games/').then(response => response.json());
};
Мы можем использовать это в приложении React. Наша первая итерация выглядит так:
App.tsx (визуализируется index.tsx) ( см. репо )
import React from 'react';
const getGames = () => {
return fetch('http://localhost:3001/games/').then(response => response.json());
};
export const App = () => {
const [games, setGames] = React.useState([]);
React.useEffect(() => {
getGames().then(games => setGames(games));
}, []);
return {JSON.stringify(games, null, 2)};
};
На первом визуализации наших Приложение Компонент, Игры Массив будет пустым. Затем, когда обещание вернулось GetGames решает, Игры Массив содержит все наши игры, и они будут отображаться очень базовой образом.
2. Пользовательский реактивный крюк
Мы можем легко извлечь это на пользовательский реактивный крюк в отдельном файле.
usegames.ts ( Смотреть репо )
import React from 'react';
const getGames = () => {
return fetch('http://localhost:3001/games/').then(response => response.json());
};
export const useGames = () => {
const [games, setGames] = React.useState([]);
React.useEffect(() => {
getGames().then(games => setGames(games));
}, []);
return games;
};
App.tsx ( Смотреть репо )
import React from 'react';
import { useGames } from './useGames';
export const App = () => {
const games = useGames();
return {JSON.stringify(games, null, 2)};
};
3. Обработка ошибок и в ожидании состояния
Наш пользовательский крючок не обрабатывает в ожидании состояний ошибок. Там нет визуальной обратной связи, пока данные загружаются с сервера, и даже хуже: нет сообщения об ошибке, когда он не удается. Если сервер недоступен, список игр останется пустым, без ошибок.
Мы можем исправить это. Для этого есть библиотеки, самое популярное существо React-Async ; Но я еще не хочу добавлять зависимости. Посмотрим, какой минимальный код необходим для обработки ошибок и в ожидании состояний.
seasyncfunction
Мы пишем пользовательский крючок, который принимает ASYNC-функцию (которая возвращает обещание) и значение по умолчанию.
Этот крюк возвращает кортеж с 3 элементами: [Значение, Ошибка, Испыды] Отказ Он называет Async Function один раз *, и обновляет значение, когда он разрешается, если не будет ошибка, конечно.
function useAsyncFunction(asyncFunction: () => Promise , defaultValue: T) { const [state, setState] = React.useState({ value: defaultValue, error: null, isPending: true }); React.useEffect(() => { asyncFunction() .then(value => setState({ value, error: null, isPending: false })) .catch(error => setState({ ...state, error: error.toString(), isPending: false })); }, [asyncFunction]); // * const { value, error, isPending } = state; return [value, error, isPending]; }
* Useffect внутри нашего seasyncfunction Позвонит ASYNC функцию один раз, а затем каждый раз asyncfunction изменения. Для получения более подробной информации: Использование головного крюка , Использование эффекта крюка , Крючки API Ссылка Отказ
Теперь в usegames.ts мы можем просто использовать этот новый пользовательский крючок, передавая GetGames Функция и начальное значение пустого массива в качестве аргументов.
...
export const useGames = () => {
const games = useAsyncFunction(getGames, []); // 🤔 new array on every render?
return games;
};
Есть небольшая проблема, хотя. Мы передаем новый пустой массив каждый раз Usegames называется, что каждый раз наш Приложение Компонент оказывает. Это приводит к восстановлению наших данных на каждой рендере, но каждая извлекающаяся приводит к новым визуализации, поэтому он приводит к бесконечному циклам.
Мы можем избежать этого, сохраняя исходное значение в постоянной за крюк:
...
const emptyList = [];
export const useGames = () => {
const [games] = useAsyncFunction(getGames, emptyList);
return games;
};
Небольшой текстура
Вы можете пропустить этот раздел, если вы используете простой JavaScript.
Если вы используете строгий приведенный текст, вышеуказанный код не будет работать из-за опции компилятора «NoMplicitalaNy». Это потому, что Const Delpylist = []; неявно массив любой Отказ
Мы можем аннотировать это, как COND DEPTYLIST: любой [] = []; и двигаться дальше. Но мы используем Teadercript по причине. Что явный любой может (и должен) быть более конкретным.
Каковы элементы этого списка? Игры! Это список игр.
const emptyList: Game[] = [];
Конечно, теперь мы должен Определить Игра тип. Но не отчаивайся! У нас есть наш ответ JSON с сервера, когда каждый объект игры выглядит так:
{
"id": 5,
"title": "Kung-Fu Master",
"year": 1984,
"genre": "beat'em up",
"url": "https://en.wikipedia.org/wiki/Kung-Fu_Master_(video_game)",
"status": "in-progress",
"img": "http://localhost:3001/img/kung-fu-master.gif"
}
Мы можем использовать Transform.tools Чтобы преобразовать это в интерфейс TearmScript (или введите).
type Game = {
id: number;
title: string;
year: number;
genre: string;
url: string;
status: 'not-started' | 'in-progress' | 'finished';
img: string;
};
Еще кое-что:
Мы сказали seasyncfunction Вернул кортеж, но вывод TypectScript (@ 3.6.2) не понимает этого. Он выводит тип возврата как Массив <(Boolean | игра [] | NULL)> Отказ Мы можем явно аннотировать возвратный тип функции, чтобы быть [T, строка | null, boolean] где T это (универсальный) тип стоимость , (Строка | NULL) это тип Ошибка и логический это Испынды Отказ
export function useAsyncFunction( asyncFunction: () => Promise , defaultValue: T ): [T, string | null, boolean] { ... }
Теперь, когда мы используем функцию, TypeScript предлагает правильные типы.
const [games] = useAsyncFunction(getGames, emptyList); // games is of type Game[]
Конец Skyscript Interlude.
Составление наших пользовательских крючков
seasyncfunction.ts Теперь выглядит так: ( Смотреть репо )
import React from 'react'; export function useAsyncFunction( asyncFunction: () => Promise , defaultValue: T ): [T, string | null, boolean] { const [state, setState] = React.useState({ value: defaultValue, error: null, isPending: true }); React.useEffect(() => { asyncFunction() .then(value => setState({ value, error: null, isPending: false })) .catch(error => setState({ value: defaultValue, error: error.toString(), isPending: false }) ); }, [asyncFunction, defaultValue]); const { value, error, isPending } = state; return [value, error, isPending]; }
И мы используем его в нашем Usegames крюк:
usegames.ts ( Смотреть репо )
import { useAsyncFunction } from './useAsyncFunction';
const getGames = (): Promise => {
return fetch('http://localhost:3001/games/').then(response => response.json());
};
type Game = {
id: number;
title: string;
year: number;
genre: string;
url: string;
status: 'not-started' | 'in-progress' | 'finished';
img: string;
};
const emptyList: Game[] = [];
export const useGames = () => {
const [games] = useAsyncFunction(getGames, emptyList);
return games;
};
Изменение UI для отображения ошибок и ожидающих состояний
Здорово! Но мы все еще не обращаемся к ошибкам и ожидающим состояниях. Нам нужно изменить наши Приложение компонент:
import React from 'react';
import { useGames } from './useGames';
export const App = () => {
const { games, error, isPending } = useGames();
return (
<>
{error && ERROR! {error}...}
{isPending && LOADING...
}
{JSON.stringify(games, null, 2)}
);
};
И наше Usegames Крючок должен вернуть объект с тремя клавишами: Игры , Ошибка , Испынды Отказ
export const useGames = () => {
const [games, error, isPending] = useAsyncFunction(getGames, emptyList);
return { games, error, isPending };
};
Мы также улучшаем наши GetGames Функция для обработки кодов состояния HTTP отличается от 200 в качестве ошибок:
const getGames = (): Promise=> { return fetch('http://localhost:3001/games/').then(response => { if (response.status !== 200) { throw new Error(`${response.status} ${response.statusText}`); } return response.json(); }); };
Наш код пока выглядит так: ( См. Репо ).
Заключение
Мы видели, как загружать данные из API отдыха с помощью реактивных крюков.
В Следующая статья Посмотрим, как изменить удаленные данные, используя HTTP Патч Запрос и как обновить наши данные клиента, когда запрос успешен.
Ресурсы
Дальнейшее чтение:
- Использование головного крюка
- Используя эффективный крюк
- Крючки API Ссылка
- Когда в соответствии с USEMEMO и USECallback
- Отмена обещания с React.useeffect
Оригинал: “https://dev.to/juliang/loading-and-displaying-data-with-hooks-jlj”