Как сделать выборку с помощью React Hooks;ESLint применяет правило `исчерпывающего deps`, которое вызывает бесконечный цикл - PullRequest
0 голосов
/ 18 июня 2019

Я довольно новичок в хуках React в целом и очень плохо знаком с useSelector и useDispatch в react-redux, но у меня возникают проблемы с выполнением простого запроса get при загрузке моего компонента. Я хочу, чтобы get происходил только один раз (при начальной загрузке компонента). Я думал, что знаю, как это сделать, но я столкнулся с проблемой ESLint, которая мешает мне делать то, что я понимаю как юридический код.

У меня есть этот хук, где я пытаюсь абстрагировать свой код состояния:

export const useState = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.data);

  return {
    data: data,
    get: (props) => dispatch(actionCreators.get(props))
  };
};

За вышеупомянутой функцией есть сетевой запрос, который выполняется через redux-saga и axios и уже некоторое время выполняется в рабочем коде. Все идет нормально. Теперь я хочу использовать его в функциональном компоненте, поэтому я написал это:

import * as React from 'react';
import { useState } from './my-state-file';

export default () => {
  const myState = useState();

  React.useEffect(
    () => {
      myState.get();
      return () => {};
    },
    []
  );
  return <div>hello, world</div>;
};

Я ожидал, что из-за того, что у моего useEffect в качестве второго аргумента будет пустой массив, он будет выполнен только один раз, поэтому get произойдет, когда компонент загрузится, и все.

Тем не менее, у меня ESLint работает при сохранении в Atom, и каждый раз, когда я сохраняю, он изменяет второй аргумент [] на [myState], результат которого:

import * as React from 'react';
import { useState } from './my-state-file';

export default () => {
  const myState = useState();

  React.useEffect(
    () => {
      myState.get();
      return () => {};
    },
    [myState]
  );
  return <div>hello, world</div>;
};

Если я загружу этот компонент, то get запускает каждый рендер, что, конечно, является полной противоположностью того, что я хочу, чтобы произошло. Я открыл этот файл в текстовом редакторе, в котором не запускается ESLint при сохранении, поэтому, когда я смог сохранить useEffect с пустым [], он работал.

Так что я сбит с толку. Я предполагаю, что шаблон, который я использую выше, не верен, но я понятия не имею, что такое «правильный» шаблон.

Любая помощь приветствуется.

Спасибо!

UPDATE:

Основываясь на ответе Роберта Купера и связанной статье Дана Абрамова, я провел еще несколько экспериментов. Я еще не до конца, но мне удалось заставить все работать.

Большое изменение заключалось в том, что мне нужно было добавить useCallback вокруг моих функций отправки, например:

export const useState = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.data);
  const get = React.useCallback((props) => dispatch({type: 'MY_ACTION', payload:props}), [
    dispatch
  ]);

  return {
    data: data,
    get: get,
  };
};

Должен признать, я не до конца понимаю, зачем мне нужен обратный вызов, но он работает.

В любом случае, мой компонент выглядит так:

import * as React from 'react';
import { useState } from './my-state-file';

export default () => {
  const {get, data}  = useState();

  React.useEffect(
    () => {
      get();
      return () => {};
    },
    [get]
  );
  return <div>{do something with data...}</div>;
};

Реальный код немного сложнее, и я надеюсь полностью выделить вызов useEffect из компонента и поместить его либо в пользовательский хук useState, либо в другой хук, импортированный из того же my-state-file файл.

1 Ответ

1 голос
/ 18 июня 2019

Я считаю, что проблема, с которой вы сталкиваетесь, заключается в том, что значение myState в вашем массиве зависимостей не совпадает со значением или имеет разные ссылки на объекты JavaScript при каждом рендеринге. Чтобы обойти это, можно передать записанную или кэшированную версию myState в качестве зависимости от вашего useEffect.

Вы можете попытаться использовать useMemo для возврата запомненной версии вашего состояния, возвращенной вашим пользовательским useState. Это может выглядеть примерно так:

export const useState = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.data);

  return useMemo(() => ({
    data: data,
    get: (props) => dispatch(actionCreators.get(props))
  }), [props]);
};

Вот что Дан Абрамов должен сказать о бесконечных циклах в useEffect методах:

Вопрос: Почему я иногда получаю бесконечный цикл повторной загрузки?

Это может произойти, если вы выполняете выборку данных в эффекте без второго аргумента зависимостей. Без этого эффекты запускаются после каждого рендера - и установка состояния снова запускает эффекты. Бесконечный цикл также может произойти, если вы укажете значение, которое всегда изменяется в массиве зависимостей. Вы можете сказать, какой из них, удаляя их по одному. Однако удаление используемой зависимости (или слепое указание []) обычно является неправильным решением. Вместо этого исправьте проблему в ее источнике. Например, функции могут вызывать эту проблему, и их использование в эффектах, их поднятие или обертывание с помощью useCallback помогает. Чтобы избежать воссоздания объектов, useMemo может служить аналогичной цели.

Полная статья здесь: https://overreacted.io/a-complete-guide-to-useeffect/

...