Последние несколько месяцев я работаю над приложением на NextJS. С каждой неделей оно становится все больше и больше. В приложении используется axios для вызовов API и unstated-next для управления состоянием. Вызовов API достаточно много, но мы не хотим, чтобы пользователи видели кучу загрузочных экранов. Поэтому мы храним результаты вызовов axios в unstated хранилищах.
Однако мы столкнулись с проблемой. Хранилища сами по себе становятся все более и более сложными. Порой страницы требуют нескольких вызовов API, а они в свою очередь полагаются на результаты других вызовов API. Дни превращались в недели, а недели в месяцы, тем временем наши unstated хранилища становились все более и более громоздкими. Мы сталкивались со странными ошибками, поскольку наша самодельная логика кэширования изо всех сил старалась справиться с неожиданными крайними случаями.
Тогда мы подумали о том, что должен быть путь проще.
И вот он. И он даже разработан Vercel, создателем NextJS.
Познакомимся с SWR
Название SWR происходит от stale-while-revalidate, способа кэширования, который сейчас набирает популярность в frontend-разработке. Он позволяет загружать кэшированный контент сразу же, и сразу же его и обновляет, чтобы в будущем обрабатывать уже новый контент. В нашем случае мы получили идеальный компромисс между производительностью и пользовательским опытом.
Как пользоваться useSWR
useSWR – это библиотека хуков React, разработанная Vercel. Она позволяет извлекать данные из API или другого внешнего источника, сохранять их в кэше, а затем рендерить.
Начнем с того, что посмотрим на пример компонента React, который получает список TODO с JSON-сервера, и рендерит его.
import React from "react"; import "./App.css"; const todosEndpoint = "http://localhost:3001/todos"; const TodoApp = () => { const [todos, setTodos] = React.useState([]); React.useEffect(() => { const getData = async () => { const response = await fetch(todosEndpoint); const data = await response.json(); setTodos(data); }; getData(); }, []); return ( <div className="App"> {todos.map((todo) => ( <div key={todo.id}>{todo.title}</div> ))} </div> ); }; export default TodoApp;
А теперь давайте посмотрим на этот же компонент, но переписанный с помощью useSWR.
import React from "react"; import useSWR from "swr"; const todosEndpoint = "http://localhost:3001/todos"; const getData = async () => { const response = await fetch(todosEndpoint); return await response.json(); }; const TodoApp = () => { const { data: todos } = useSWR(todosEndpoint, getData); return ( <div> {todos && todos.map(todo => ( <div key={todo.id}>{todo.title}</div> )} </div> ); }; export default TodoApp;
Как видите, очень похоже на предыдущую реализацию.
В этом примере мы используем useSWR(key, fetcher, options), чтобы получить наш список TODO. Key в useSWR нужен для кэширования. В нашем случае мы использовали todosEndpoint. В качестве fetcher мы передали асинхронную функцию, которая извлекает список TODO.
Необходимо отметить, что useSWR не имеет ни малейшего понятия о том, как вы извлекаете данные. Вы можете использовать любую асинхронную стратегию извлечения данных, которая вам нравится. Вы можете использовать fetch, axios или даже GraphQL. Пока ваша функция асинхронно возвращает данные, useSWR будет счастлива.
Вызов useSWR возвращает следующие параметры:
- data: данные по ключу, полученные с помощью fetcher (не определены, если еще не загружены);
- error: ошибка, вызванная fetcher;
- isValidating: логическая переменная, которая говорит, выполняется ли запрос или ревалидация;
- mutate(data?, shouldRevalidate): функция для изменения кэшированных данных.
Более сложный пример
Давайте посмотрим на более сложный пример, в котором используется больше параметров, возвращаемых useSWR. В этом примере мы будем извлекать один элемент списка TODO и рендерить чекбокс для статуса complete нашей задачи из списка. Когда на чекбокс щелкают, мы отправляем PUT-запрос на обновление TODO, а затем вызываем mutate для обновления кэша useSWR.
import React from "react"; import useSWR from "swr"; import "./App.css"; const todosEndpoint = "http://localhost:3001/todos"; const getTodo = async (id) => { const response = await fetch(`${todosEndpoint}/${id}`); return await response.json(); }; const updateTodo = async (id, todo) => { const response = await fetch(`${todosEndpoint}/${id}`, { method: "PUT", headers: { "Content-type": "application/json; charset=UTF-8", }, body: JSON.stringify(todo), }); return await response.json(); }; const TodoApp = () => { const todoId = 1; const key = `${todosEndpoint}/${todoId}`; const { data: todo, mutate } = useSWR(key, () => getTodo(todoId) ); const toggleCompleted = async () => { const newTodo = { ...todo, completed: !todo.completed, }; await updateTodo(todoId, newTodo); mutate(newTodo); }; if (!todo) { return <div>Loading...</div>; } return ( <div> <p>{todo.title}</p> <div> <input type="checkbox" id="completed" name="completed" value="completed" checked={todo.completed} onChange={toggleCompleted} /> <label htmlFor="completed">Completed</label> </div> </div> ); };
Использование mutate — это отличный способ повысить видимую производительность вашего веб-приложения. Мы можем изменять данные локально и изменять представление, при этом не дожидаясь пока обновится удаленный источник данных. useSWR даже проведет ревалидацию и заменит их последними данными на бэкграунде.
Почему для этого мне нужна библиотека?
Вы можете задаться этим вопросом. Возможно, у вас уже есть механизм управления состоянием в вашем приложении, и вы не видите смысла в использовании сторонней библиотеки для чего-то такого простого, как извлечение данных и кэширование.
И я отвечу вам, что кэшировать данные – это непросто. И по мере роста вашего приложения это становится еще сложнее. Вы столкнетесь с крайними случаями, и обрабатывая их, получите сложные хранилища и провайдеров, которых трудно понять и трудно поддерживать. Баги будут сыпаться из ниоткуда.
«В компьютерных науках есть только две сложные проблемы: инвалидация кэша и придумывание имен» — Фил Карлтон
Вместо того, чтобы внедрять свое собственное решение для извлечения данных, почему бы не положиться на проверенное, разработанное одной из самых уважаемых компаний в экосистеме React?
Как нам помогла useSWR
Перенос вопроса извлечения данных в нашем приложении на useSWR дал множество преимуществ.
1. Он позволил нам удалять код
Pull-request’ы, которые удаляют лишний код, а не добавляют новый – мои любимые. Чем меньше кода в приложении, тем меньше вероятность появления ошибок. И это хорошо. С возрастом я все больше начинаю ценить простоту. useSWR позволила нам удалять unstated хранилища полностью, что упростило наше приложение, и сделало его более легким для понимания.
2. Он упростил подключение новых разработчиков к проекту
useSWR, как и большинство проектов с открытым исходным кодом, может похвастаться отличной документацией. Внедрение нашего собственного решения означало бы, что нам нужно писать собственную документацию и учить новых разработчиков в проекте обращаться с извлечением данных. Теперь, когда мы используем useSWR, нам не нужно этим заниматься.
3. Он упростил трудные вещи
Наше приложение, как и большинство других приложений, содержит множество вызовов API. Некоторые из этих вызовов зависят от других вызовов API. С useSWR легко писать хуки для зависимых запросов.
4. Он улучшил видимую производительность
Приложение выглядит более быстрым. Пользователи оценили это и дали хорошую обратную связь.
5. Он обновляет устаревшие данные
useSWR обновляет устаревшие данные в фокусе. Это значит, что у пользователей всегда самая актуальная версия данных, без учета времени загрузки.
Заключение
useSWR оказала на наше приложение большое воздействие. Эта библиотека упростила наш код и улучшила пользовательский опыт. Я не могу даже представить себе другую библиотеку, которая была бы настолько простой в реализации и несла бы в себе столько же преимуществ, сколько несет useSWR. Чтобы узнать больше о useSWR, вы можете посетить сайт или репозиторий на GitHub.
Альтернативные библиотеки
useSWR – это не единственная хорошая библиотека для извлечения данных. В то время как она хорошо себя показала в нашем случае, возможно, вам больше подойдет другая библиотека для извлечения данных.
React-Query
react-query – это библиотека, состоящая из хуков для извлечения, кэширования и обновления асинхронных данных в React. Это популярное решение, написанное Таннером Линси, очень похоже по сфере применения на useSWR.
Apollo
Клиент Apollo – это клиент GraphQL, который позаботится об извлечении и кэшировании ваших данных, а также об обновлении пользовательского интерфейса. Если вы используете GraphQL, то Apollo будет отличным решением.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/506158/
Добавить комментарий