Пропуск эффектов не работает для массива динамических URL - PullRequest
2 голосов
/ 16 марта 2019

У меня есть компонент React.SFC / реагировать без состояния / функциональный, который, к сожалению, слишком часто рендерится из-за избыточных данных, поступающих из-за избыточности в родительском компоненте. Пока я ничего не могу с этим поделать, поэтому я просто принимаю дополнительные повторные отображения и использую useEffect, чтобы убедиться, что данные выбираются только при изменении определенного свойства. В этом случае он называется «URL» и представляет собой массив URL (TypeScript URL Type).

Вот пример кода, иллюстрирующего проблему:

import React from "react";
import { useState, useEffect, useMemo } from "react";
import { render } from "react-dom";

import "./styles.css";

const useCustomHook = urls => {
  const [onlyChangeWhenUrlsChange, setOnlyChangeWhenUrlsChange] = useState(
    null
  );
  useEffect(
    () => {
      setOnlyChangeWhenUrlsChange(Math.random());
    },
    [urls]
  );
  return onlyChangeWhenUrlsChange;
};

const dynamicUrls = (pageRouteParamId, someDynamicUrlParam) => {
  return [
    {
      pageRouteParamId: 1337,
      urls: [new URL(`https://someurl.com/api?id=${someDynamicUrlParam}`)]
    }
  ];
};

const SomePage: React.SFC<any> = ({
  simulateFrequentUpdatingData,
  pageRouteParamId
}) => {
  const someOtherId = 1;
  // As suggested in SO answer, using useMemo seems to work, but will that not create a memory leak?
  // Is there any good alternative?
  // const urls = useMemo(() => dynamicUrls(pageRouteParamId, someOtherId).find(url => url.pageRouteParamId === pageRouteParamId).urls, [pageRouteParamId, someOtherId]);
  const urls = dynamicUrls(pageRouteParamId, 1).find(
    url => url.pageRouteParamId === 1337
  ).urls;
  return (
    <div>
      <p>parent</p>
      <p>{simulateFrequentUpdatingData}</p>
      <p>
        Page route param id (in real app this would come from react-router route
        param): {pageRouteParamId}
      </p>
      {urls && urls.length && <MyStateLessFunctionalComponent {...{ urls }} />}
      <p>
        Page route param id (in real app this would come from react-router route
        param): {pageRouteParamId}
      </p>
      {urls && urls.length && (
        <MyStateLessFunctionalComponentWithHook {...{ urls }} />
      )}
    </div>
  );
};

const MyStateLessFunctionalComponent: React.SFC<any> = ({ urls }) => {
  const [onlyChangeWhenUrlsChange, setOnlyChangeWhenUrlsChange] = useState(
    null
  );
  useEffect(
    () => {
      setOnlyChangeWhenUrlsChange(Math.random());
    },
    [urls]
  );
  return (
    <div>
      <p>MyStateLessFunctionalComponent</p>
      <p>{JSON.stringify(urls)}</p>
      <p>This should only change when urls change {onlyChangeWhenUrlsChange}</p>
    </div>
  );
};

const MyStateLessFunctionalComponentWithHook: React.SFC<any> = ({ urls }) => {
  const onlyChangeWhenUrlsChange = useCustomHook(urls);
  return (
    <div>
      <p>MyStateLessFunctionalComponentWithHook</p>
      <p>{JSON.stringify(urls)}</p>
      <p>This should only change when urls change {onlyChangeWhenUrlsChange}</p>
    </div>
  );
};

function App() {
  const [
    simulateFrequentUpdatingData,
    setSimulateFrequentUpdatingData
  ] = useState(null);
  const [pageRouteParamId, setPageRouteParamId] = useState(1337);
  useEffect(() => {
    setInterval(() => setSimulateFrequentUpdatingData(Math.random()), 1000);
  }, []);

  return (
    <div className="App">
      <SomePage {...{ simulateFrequentUpdatingData, pageRouteParamId }} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Edit:

Мне пришлось изменить заголовок и вопрос, так как при воспроизведении его с примером кода я понял, что проблема не в «Пропуске эффектов внутри пользовательского хука». Прежде чем я увидел разницу при использовании useEffect непосредственно по сравнению с пользовательским хуком, и, как справедливо упомянуто в комментариях, различий не должно быть - и я пришел к тому же выводу, воспроизводя мою проблему с этим примером кода:

Вы можете посмотреть живой пример здесь .

Как было предложено в ответе ниже, похоже, что useMemo решает проблему (см. Строку 36)

Ответы [ 2 ]

1 голос
/ 16 марта 2019

Я предполагаю, что urls объявляется внутри рендера выше по дереву и, таким образом, каждый раз получает новую идентичность. Вы можете либо useMemo в том месте, где оно объявлено, JSON.stringify urls в массиве deps или useRef, который работает как дополнительная защита от повторных запусков.

Редактировать: это обсуждают более умные люди, чем я: https://github.com/facebook/react/issues/14476#issuecomment-471199055.

0 голосов
/ 17 марта 2019

Если urls - это массив строк, вы можете передать это как второй аргумент для использованияEffect

useEffect(() => {
  loadData();
}, urls);

таким образом он будет проверять строковые значения вместо ссылки на массив.

...