В этой серии, а не используя библиотеку государства-менеджмента или предложение одного различия – все решения, мы начинаем с минимального минимума, и мы создаем наше государственное управление, поскольку нам это нужно.
- В этой первой статье мы опишем, как мы загружаем и отображаем данные с крючками.
- Во второй статье мы узнаем, как менять удаленные данные с крючками.
- В третьей статье мы увидим, как делиться данными между компонентами с контекстом 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”