Проблема с простым примером использования React HooksCallback - PullRequest
0 голосов
/ 28 февраля 2019

Я пытаюсь сделать простой пример, который следует примеру React Hooks в документе на странице https://reactjs.org/docs/hooks-reference.html#usecallback

Без useCallback код работает, как в этом примере:

import React, { useCallback } from "react";

function Test(props) {
  function doSomething(a, b) {
    console.log("doSomething called");
    return a + b;
  }

  return (
    <div>
      {Array.from({ length: 3 }).map(() => (
        <div>{doSomething('aaa','bbb')}</div>
      ))}
    </div>
  );
}

export default Test;

Однако, когда я добавляю, как мне кажется, правильный код для useCallback следующим образом, я получаю сообщение об ошибке (a не определено)

import React, { useCallback } from "react";

function Test(props) {
  function doSomething(a, b) {
    console.log("doSomething called");
    return a + b;
  }

  const memoizedCallback = useCallback(
    () => {
      doSomething(a, b);
    },
    [a, b]
  );

  return (
    <div>
      {Array.from({ length: 3 }).map(() => (
        <div>{memoizedCallback("aaa", "bbb")}</div>
      ))}
    </div>
  );
}

export default Test;

Код проблемы здесь:

https://stackblitz.com/edit/react-usememo2?file=Hello.js

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Цель useCallback состоит в том, чтобы иметь возможность использовать реквизит или состояние, которые находятся в текущей области, и которые могут измениться при повторном рендеринге.Затем массив зависимостей сообщает React, когда вам нужна новая версия обратного вызова.Если вы пытаетесь запоминать дорогостоящие вычисления, вам нужно использовать useMemo.

В приведенном ниже примере демонстрируются различия между useCallback и useMemo и последствия их неиспользования.В этом примере я использую React.memo для предотвращения повторного рендеринга Child, если только его реквизиты или состояние не изменятся.Это позволяет увидеть преимущества useCallback.Теперь, если Child получит новую onClick опору, это вызовет повторный рендеринг.

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

дочерний элемент 2 использует запомненный обратный вызов onClick, возвращаемый из useCallback, а дочерний 3 использует эквивалент через useMemo дляпродемонстрировать значение

useCallback (fn, input) эквивалентно useMemo (() => fn, input)

Для дочерних элементов 2 и 3 обратный вызов по-прежнему получаетвыполняется каждый раз, когда вы щелкаете дочерний элемент 2 или 3, useCallback просто гарантирует, что одна и та же версия функции onClick будет передана, когда зависимости не изменились.

Следующая часть экрана помогает указать, что происходит:

nonMemoizedCallback === memoizedCallback: false | true

Отдельно яm с отображением somethingExpensiveBasedOnA и записанной версией с использованием useMemo.В демонстрационных целях я использую неверный массив зависимостей (я намеренно пропустил b), чтобы вы могли видеть, что запомненная версия не изменяется при изменении b, но меняется при изменении a.Незамеченная версия изменяется всякий раз, когда a или b изменяется.

import ReactDOM from "react-dom";

import React, {
  useRef,
  useMemo,
  useEffect,
  useState,
  useCallback
} from "react";

const Child = React.memo(({ onClick, suffix }) => {
  const numRendersRef = useRef(1);
  useEffect(() => {
    numRendersRef.current++;
  });

  return (
    <div onClick={() => onClick(suffix)}>
      Click Me to log a and {suffix} and change b. Number of Renders:{" "}
      {numRendersRef.current}
    </div>
  );
});
function App(props) {
  const [a, setA] = useState("aaa");
  const [b, setB] = useState("bbb");

  const computeSomethingExpensiveBasedOnA = () => {
    console.log("recomputing expensive thing", a);
    return a + b;
  };
  const somethingExpensiveBasedOnA = computeSomethingExpensiveBasedOnA();
  const memoizedSomethingExpensiveBasedOnA = useMemo(
    () => computeSomethingExpensiveBasedOnA(),
    [a]
  );
  const nonMemoizedCallback = suffix => {
    console.log(a + suffix);
    setB(prev => prev + "b");
  };
  const memoizedCallback = useCallback(nonMemoizedCallback, [a]);
  const memoizedCallbackUsingMemo = useMemo(() => nonMemoizedCallback, [a]);
  return (
    <div>
      A: {a}
      <br />
      B: {b}
      <br />
      nonMemoizedCallback === memoizedCallback:{" "}
      {String(nonMemoizedCallback === memoizedCallback)}
      <br />
      somethingExpensiveBasedOnA: {somethingExpensiveBasedOnA}
      <br />
      memoizedSomethingExpensiveBasedOnA: {memoizedSomethingExpensiveBasedOnA}
      <br />
      <br />
      <div onClick={() => setA(a + "a")}>Click Me to change a</div>
      <br />
      <Child onClick={nonMemoizedCallback} suffix="1" />
      <Child onClick={memoizedCallback} suffix="2" />
      <Child onClick={memoizedCallbackUsingMemo} suffix="3" />
    </div>
  );
}

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

Edit useCallback and useMemo

Вот соответствующий ответ: React HooksuseCallback приводит к повторному рендерингу child

0 голосов
/ 28 февраля 2019

У вас есть дополнительные { } после =>, что создает блок и область видимости, просто удалите их и ;, так как это делает это оператором.В противном случае передайте ему аргументы, используйте это

    const memoizedCallback = useCallback(
    () => doSomething(a, b)
    ,
    [a, b]
  );

Там написано undefined, поскольку область действия => { } не получает a,b.Надеюсь, это поможет.

...