Создание пользовательского хука для получения асинхронных данных: useAsync Hook с кешем

Создание пользовательского хука для получения асинхронных данных: useAsync Hook с кешем

17 ноября 2022 г.

Хорошей практикой является показать пользователю, что приложение загружает данные. Для этого отображается индикатор загрузки и скрывается содержимое до тех пор, пока данные не будут готовы. Большинство из нас будет поддерживать состояние в компоненте, который отслеживает, готовы данные или нет, и это повторяется в каждом компоненте, который вызывает API.

Рассмотрите следующий пример:

Компонент Todos

import React, { useState, useEffect } from "react";
const Todos = () => {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);  useEffect(() => {
    const init = async () => {
      try {
        setLoading(true);
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/todos"
        );
        const data = await response.json();
        setTodos(data);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };
    init();
  }, []);  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error</div>;
  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
};

Детали TODO

const Todo = ({ id }) => {
  const [todo, setTodo] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);  useEffect(() => {
    const init = async () => {
      try {
        setLoading(true);
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/todos/${id}`
        );
        const data = await response.json();
        setTodo(data);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };
    init();
  }, [id]);  if (loading) return <div>Loading 2...</div>;
  if (error) return <div>Error 2</div>;
  return (
    <div>
      <h1>{todo.title}</h1>
    </div>
  );
};

Как мы видим, в коде происходят три основные вещи:

  1. Во-первых, во время выборки мы показываем индикатор загрузки.
  2. Во-вторых, мы обрабатываем ошибку, если она есть.
  3. В-третьих, мы устанавливаем состояние задачи на данные, полученные от API.

Примечание. Логика получения данных одинакова в обоих компонентах. Мы можем создать собственный хук, который будет использоваться для обработки всех асинхронных выборок данных и обновления состояния.

Пользовательский хук (useAsync)

Перехватчики React – это набор функций, которые можно использовать для создания более гибкого компонента, чем традиционный жизненный цикл компонента.

Мы можем создать собственный хук, который будет использоваться для обработки всех асинхронных выборок данных и обновления состояния.

перехватчик useAsync

import React, { useState, useEffect } from "react";const useAsync = (defaultData) => {
  const [data, setData] = useState({
    data: defaultData ?? null,
    error: null,
    loading: false,
  });  const run = async (asyncFn) => {
    try {
      setData({ data: null, error: null, loading: true });
      const response = await asyncFn();
      const result = { data: response, error: null, loading: false };
      setData(result);
      return result;
    } catch (error) {
      const result = { data: null, error, loading: false };
      setData(result);
      return result;
    }
  };  return {
    ...data,
    run,
  };
};

Компонент Todos

import React, { useState, useEffect } from "react";
import { useAsync } from "./hooks";
const Todos = () => {
  const { data, loading, error, run } = useAsync([]);  useEffect(() => {
    run(() => fetch("https://jsonplaceholder.typicode.com/todos").then((res) => res.json()));
  }, []);  // Same as above
  return ...
};

Детали TODO

import React, { useState, useEffect } from "react";
import { useAsync } from "./hooks";
const Todo = ({ id }) => {
  const { data, loading, error, run } = useAsync(null);  useEffect(() => {
    run(() => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then((res) => res.json()));
  }, [id]);  // Same as above
  return ...
};

ПРИМЕЧАНИЕ. Мы сократили объем кода, который необходимо написать, с помощью пользовательского хука. Код также легче читать и поддерживать.

Давайте добавим больше функциональности в наш собственный хук

  1. Добавьте кэширование в пользовательский хук, чтобы предотвратить вызовы API, если данные уже присутствуют в состоянии.

import { useState, useCallback } from "react";const cache = new Map();
const defaultOptions = {
  cacheKey: "",
  refetch: false,
};export const useAsync = (defaultData?: any) => {
  const [data, setData] = useState({
    data: defaultData ?? null,
    error: null,
    loading: false,
  });  const run = useCallback(async (asyncFn, options = {}) => {
    try {
      // Merge the default options with the options passed in
      const { cacheKey, refetch } = { ...defaultOptions, ...options };      const result = { data: null, error: null, loading: false };      // If we have a cache key and not requesting a new data, then return the cached data
      if (!refetch && cacheKey && cache.has(cacheKey)) {
        const res = cache.get(cacheKey);
        result.data = res;
      } else {
        setData({ ...result, loading: true });
        const res = await asyncFn();
        result.data = res;
        cacheKey && cache.set(cacheKey, res);
      }
      setData(result);
      return result;
    } catch (error) {
      const result = { data: null, error: error, loading: false };
      setData(result);
      return result;
    }
  }, []);  return {
    ...data,
    run,
  };
};

Детали TODO

import React, { useState, useEffect } from "react";
import { useAsync } from "./hooks";
const Todo = ({ id }) => {
  const { data, loading, error, run } = useAsync(null);  useEffect(() => {
    run(() => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
        .then((res) => res.json()),
        {cacheKey: `todo-${id}`});
  }, [id]);  // Same as above
  return ...
};

Варианты:

  1. cacheKey: ключ, который мы будем использовать для хранения данных в кеше.
  2. обновить: если мы хотим повторно получить данные из API. Это полезно, когда мы хотим обновить данные в кеше.

ПРИМЕЧАНИЕ. Кэш доступен глобально, поэтому мы можем использовать его в других компонентах. Если мы используем useAsync в нескольких компонентах с одним и тем же cacheKey, то данные кеша будут общими для всех компонентов. Это полезно, когда мы хотим избежать ненужных вызовов API, если данные уже присутствуют в кеше.

React Query и SWR — две популярные библиотеки, которые можно использовать для обработки всех асинхронных выборок данных.

Живой пример, здесь


Изображения для ведущих источник.

Также опубликовано здесь

Спасибо, что читаете 😊

Есть вопросы или дополнения? пожалуйста, оставьте комментарий.


Оригинал

PREVIOUS ARTICLE
NEXT ARTICLE