Проблема с простым примером React Hooks useCallback

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

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

import React, { useCallback } from "react";

function Test(props) {
  function doSomething(a, b) {
    console.info("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.info("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

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
12
0
28 110
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

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

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

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs)

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

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

nonMemoizedCallback === memoizedCallback: false|true

Отдельно я показываю 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.info("recomputing expensive thing", a);
    return a + b;
  };
  const somethingExpensiveBasedOnA = computeSomethingExpensiveBasedOnA();
  const memoizedSomethingExpensiveBasedOnA = useMemo(
    () => computeSomethingExpensiveBasedOnA(),
    [a]
  );
  const nonMemoizedCallback = suffix => {
    console.info(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 Hooks useCallback заставляет дочерний элемент повторно отображать

В первом примере он по-прежнему выводит console.info 3 раза. Что я не получаю? (трудный вопрос, я думаю)

Peter Kellner 28.02.2019 22:26

Вы просто неправильно понимаете цель useCallback. useCallback не запоминает результаты функции — он запоминает саму функцию. В первом примере нет веской причины для его использования, но во втором примере это предотвратит изменение свойства onClickChild при повторном рендеринге. Если вы хотите запомнить результаты (чтобы предотвратить избыточное выполнение дорогостоящей функции), вы должны использовать useMemo.

Ryan Cogswell 28.02.2019 22:58

ты так прав, я не понимаю. Меня поставил в тупик этот комментарий в документах: useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs). Я собираю учебные материалы и немного теряюсь, ищу хорошие примеры.

Peter Kellner 28.02.2019 23:08

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

Ryan Cogswell 01.03.2019 00:03

Другие вопросы по теме