[Перевод] Как использовать хуки React для получения данных интерфейса API

React.js внешний фреймворк
[Перевод] Как использовать хуки React для получения данных интерфейса API

Оригинальный адрес:robinwieruchИспользуйте полнотекстовый перевод, не важно я не перевожу

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

Если вы ничего не знаете о новых функциях React, вы можете проверить этоReact hooksСоответствующее введение API. Если вы хотите увидеть полный код проекта о том, как использовать React Hooks для получения данных, вы можете проверитьрепозиторий github

Если вы просто хотите использовать React Hooks для сбора данных, напрямуюnpm i use-data-apiи согласноДокументацияработать. Если воспользуетесь им, не забудьте поставить мне звездочку~

Примечание. В будущем React Hooks не будут работать для получения данных в React. Об этом позаботится функция под названием Suspense. Следующее пошаговое руководство — отличный способ узнать больше о перехватчиках состояний и эффектов в React.

Получение данных с помощью хуков React

Если вы новичок в извлечении данных в React, ознакомьтесь с моей подробной статьей об извлечении данных в React. Он проведет вас через выборку данных с использованием компонентов, подобных React, как повторно использовать эти данные с помощью компонентов Render Prop и компонентов более высокого порядка, а также как он обрабатывает ошибки и загрузку.

  import React, { useState } from 'react';
  
  function App() {
    const [data, setData] = useState({ hits: [] });
  
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    );
  }
  
  export default App;

Компонент App отображает список элементов (hits=Hacker News article). Функции состояния и обновления состояния поступают из хука useState. Он отвечает за управление состоянием наших данных. Первое значение в userState — это начальное значение данных. По сути, это деструктивное задание.

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

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

Здесь мы используем хук эффекта useEffect для получения данных. И используйте setData в useState для обновления состояния компонента.

Но когда приведенный выше код запускается, вы обнаружите особенно раздражающую проблему зацикливания. Хук эффекта срабатывает не только при первой загрузке компонента, но и при каждом обновлении. Потому что мы устанавливаем состояние компонента после получения данных, а затем запускаем хук эффекта. Так что будет бесконечный цикл. Очевидно, это ошибка!Мы хотим получить данные только при первой загрузке компонента., поэтому вы можете предоставить пустой массив какuseEffectВторой параметр, чтобы не трогать его при обновлении компонента. Конечно, в этом случае он срабатывает при загрузке компонента.

  import React, { useState, useEffect } from 'react';
  import axios from 'axios';
  
  function App() {
    const [data, setData] = useState({ hits: [] });
  
    useEffect(async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );
  
      setData(result.data);
    }, []);
  
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    );
  }
  
  export default App;

Второй параметр может использоваться для определения всех переменных, от которых зависит хук (в этом массиве), если одна из переменных изменится, это вызовет запуск хука. Если передается пустой массив, он будет работать только при первой загрузке.

Это похоже на то, что это сделаноshouldComponentUpdateвещи

Здесь есть еще одна ловушка. Внутри этого кода мы используемasync/awaitДля получения данных интерфейса стороннего API, согласно документации, каждыйasyncвернет обещание:asyncОбъявление функции определяет асинхронную функцию, которая возвращает объект AsyncFunction. Асинхронные функции — это функции, которые работают асинхронно через цикл обработки событий, используя неявное обещание для возврата результата.然而,effect hook 不应该返回任何内容,或者清除功能。这也就是为啥你看到这个警告:07:41:22.910 index.js:1452 Предупреждение: функция useEffect должна возвращать функцию очистки или ничего. Promises и useEffect(async() => …) не поддерживаются, но вы можете вызвать асинхронную функцию внутри эффекта.. ` `

Вот почему мы не можемuseEffectиспользуется вasyncпричина. Но мы можем решить это:

  import React, { useState, useEffect } from 'react';
  import axios from 'axios';
  
  function App() {
    const [data, setData] = useState({ hits: [] });
  
    useEffect(() => {
      const fetchData = async () => {
        const result = await axios(
          'https://hn.algolia.com/api/v1/search?query=redux',
        );
  
        setData(result.data);
      };
  
      fetchData();
    }, []);
  
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    );
  }
  
  export default App;

Вышеупомянутое предназначено для получения данных API через хуки React. Однако, если вы обеспокоены обработкой ошибок, загрузкой, тем, как инициировать выборку данных из формы или как реализовать многоразовые ловушки для выборки данных. Читать дальше.

Как запускать хуки автоматически или вручную? (Как запустить хук программно/вручную?)

В настоящее время мы получили данные интерфейса при первой загрузке компонента. Но как через поле ввода сказать интерфейсу апи, что меня интересует какая тема? (Это то, как передавать данные в интерфейс. Исходный текст здесь немного многословен (и ключевые слова редуксируют, чтобы запутать аудиторию), я перейду непосредственно к коду)...

  ...

  function App() {
    const [data, setData] = useState({ hits: [] });
    const [query, setQuery] = useState('redux');
  
    useEffect(() => {
      const fetchData = async () => {
        const result = await axios(
          `http://hn.algolia.com/api/v1/search?query=${query}`,
        );
  
        setData(result.data);
      };
  
      fetchData();
    }, []);
  
    return (
      ...
    );
  }
  
  export default App;

Здесь я пропускаю абзац, исходный текст слишком подробен.

Одного не хватает: когда вы пытаетесь ввести что-то в поле ввода, это больше не будет вызывать запрос. потому что вы предоставляете пустой массив какuseEffectВторой параметр — это пустой массив, поэтому срабатывание хука эффекта не зависит ни от каких переменных, поэтому оно срабатывает только при первой загрузке компонента. Итак, здесь мы хотим инициировать поиск при изменении поля запроса.

...

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [query]);

  return (
    ...
  );
}

export default App;

Как и выше, мы просто ставимqueryПередается в качестве второго параметра хуку эффекта, чтобы поиск запускался при каждом изменении запроса. Однако будет другая проблема: каждое изменение поля запроса будет запускать поиск. Как предоставить кнопку для запуска запроса?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${search}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [search]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="button" onClick={() => setSearch(query)}>
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

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

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

Это пример использования хука эффекта для получения данных, вы можете решить, от какого состояния зависит хук эффекта. Как только вы нажмете или что-то еще для setState , запустится хук эффекта. Но в этом примере данные будут снова получены только при изменении вашего URL.

Использование Loading in Effect Hook (Индикатор загрузки с React Hooks)

Вот добавим в программу загрузку (загрузчик), здесь нужно другое состояние

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      const result = await axios(url);

      setData(result.data);
      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

Код относительно прост, не объяснен

Добавить обработку ошибок с помощью хуков эффектов (обработка ошибок с помощью хуков React)

Как сделать обработку ошибок в Effect Hook? Ошибка — это просто состояние.Как только программа находится в состоянии ошибки, компонент должен отобразить обратную связь с пользователем. когда мы используемasync/await, мы можем использоватьtry/catch,следующим образом:

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

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

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

Получение данных с помощью форм и React

function App() {
  ...

  return (
    <Fragment>
      <form onSubmit={event => {
        setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);

        event.preventDefault();
      }}>
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      ...
    </Fragment>
  );
}

Чтобы предотвратить перезагрузку браузера, мы добавилиevent.preventDefalut(), а затем другие операции являются операциями нормальной формы

Пользовательский хук для выборки данных

По сути, это инкапсуляция запроса

Чтобы иметь возможность извлечь пользовательский хук запроса, в дополнение к полю запроса, принадлежащему полю ввода, должны быть включены другие, включая загрузчик загрузки и функции обработки ошибок. Конечно, вам нужно убедиться, что все поля, требуемые компонентом приложения, возвращаются в ваш пользовательский хук.

const useHackerNewsApi = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
}

Теперь мы можем продолжить использовать ваш новый хук в компоненте

function App() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, doFetch] = useHackerNewsApi();

  return (
    <Fragment>
      <form onSubmit={event => {
        doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);

        event.preventDefault();
      }}>
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      ...
    </Fragment>
  );
}

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

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
};

function App() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, doFetch] = useDataApi(
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );

  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );

          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

Как и выше, мы используем кастомный хук для получения данных, сам хук ничего не знает об API, он принимает все параметры извне, но управляет только важными полями, такими как данные, загрузка, обработчик ошибок и т.д. Он выполняет запрос и возвращает все данные, необходимые компоненту.

Редукционный хук для выборки данных

До сих пор мы использовали различные обработчики состояний для управления данными, загрузкой, обработчиками ошибок и т. д. Однако все эти состояния, благодаря своему собственному управлению состояниями, принадлежат одному целому, потому что состояния данных, о которых они заботятся, связаны с запросами. Как видите, все они используются в функции выборки. Еще одним хорошим признаком того, что они одного типа, являются функции, в которых они вызываются одна за другой (например, setIsError, setIsLoading). Давайте объединим эти три состояния с помощью Reducer Hook!

Перехватчик Reducer возвращает объект состояния и функцию, которая изменяет объект состояния. Эта функция является функцией отправки: действие с типом и параметрами.

По сути, эти понятия аналогичны редукции.

import React, {
  Fragment,
  useState,
  useEffect,
  useReducer,
} from 'react';
import axios from 'axios';

const dataFetchReducer = (state, action) => {
  ...
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  ...
};

Редукционный хук принимает в качестве параметров функцию редуктора и объект начального состояния. В нашем случае параметры начального состояния данных, состояния загрузки и ошибки не изменились, но они были агрегированы в объект состояния, управляемый хуком редьюсера, вместо одного хука состояния.

const dataFetchReducer = (state, action) => {
  ...
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };

    fetchData();
  }, [url]);

  ...
};

Теперь при получении данных вы можете использоватьdispathc functionприйти, чтобы датьreducerПередать параметры. Объекты, отправляемые с помощью функции отправки, имеют обязательное свойство типа и необязательное свойство полезной нагрузки. Тип сообщает функции редуктора, какой переход состояния необходимо применить, и редуктор может дополнительно использовать полезную нагрузку для извлечения нового состояния. В конце концов, у нас есть только три перехода между состояниями: инициализировать процесс извлечения, уведомить об успешных результатах извлечения данных и уведомить о неправильных результатах извлечения данных.

В нашем пользовательском хуке состояние возвращается как и раньше. Но так как у нас есть объект состояния вместо автономного состояния. Таким образом, люди, вызывающие пользовательский хук useDataApi, по-прежнему могут получить доступ к данным, isLoading и isError:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  ...

  return [state, setUrl];
};

Наконец, есть реализация нашей функции редуктора. Он должен воздействовать на три различных перехода состояний, называемых FETCH_INIT, FETCH_SUCCESS и FETCH_FAILURE. Каждый переход состояния должен возвращать новый объект состояния. Давайте посмотрим, как это реализовать с помощью оператора switch case:

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    default:
      throw new Error();
  }
};

Теперь каждое действие имеет соответствующий обработчик и возвращает новое состояние.

Таким образом, Reducer Hook гарантирует, что эта часть управления состоянием инкапсулирована собственной логикой. Кроме того, вы никогда не столкнетесь с недопустимым состоянием. Например, состояния isLoading и isError ранее могли быть случайно установлены в значение true. Что в этом случае должен показывать пользовательский интерфейс? Теперь каждый переход состояния, определенный функцией редуктора, приводит к допустимому объекту состояния.

Прервать получение данных в ловушке эффекта

Распространенная проблема в React заключается в том, что состояние компонента устанавливается, даже если компонент был размонтирован (например, из-за навигации с помощью React Router). Я уже писал об этой проблеме здесь, где описывается, как предотвратить установку состояния в компонентах, которые не загружены в различных сценариях. Давайте посмотрим, как мы можем предотвратить установку состояния в пользовательском хуке выборки:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        if (!didCancel) {
          dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: 'FETCH_FAILURE' });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  return [state, setUrl];
};

Каждый хук эффектов имеет функцию очистки. Эта функция запускается, когда компонент выгружен. Функция очистки — это функция, возвращаемая хуком. В нашем примере мы используем файл с именемdidCancelЛогическое значение для определения состояния компонента. Этот флаг должен быть установлен в значение true, если компонент размонтирован, что предотвратит установку состояния компонента после окончательного асинхронного разрешения выборки.

Примечание: получение данных на самом деле не прерывается — это можно сделать черезAxios Cancellationреализация - но дляunmountedКомпонент больше не выполняет переходы между состояниями. Поскольку Axios Cancellation, на мой взгляд, не лучший API, этот логический флаг, который предотвращает установку состояния, также выполняет свою работу.

учеба по обмену

Обратите внимание на официальный аккаунт: [Full-stack front-end selection] Получайте хорошие рекомендации статей каждый день. Вы также можете присоединиться к группе и учиться и общаться вместе~~