2

我有两个“昂贵”的功能,我想在我的反应应用程序中记住它们,因为它们需要很长时间才能呈现。昂贵的函数用于数组映射。我想记住数组映射的每个结果,以便如果数组的一个元素发生更改,则只会重新计算该元素的昂贵函数。(并且要独立记忆昂贵的函数,因为有时只需要重新计算一个。)我正在努力解决如何记忆和传递当前数组值。

这是工作演示,没有记忆:

import React, { useMemo, useState } from "react";

const input = ["apple", "banana", "cherry", "durian", "elderberry", "apple"];

export default function App() {
  const output = input.map((msg, key) => createItem(msg, key));
  return <div className="App">{output}</div>;
}

const createItem = (message, key) => {   // expensive function 1
  console.log("in createItem:", key, message);

  return (
    <div key={key}>
      <strong>{"Message " + (1 + key)}: </strong>
      {message} =>{" "}
      <span
        id={"preview-" + key}
        dangerouslySetInnerHTML={{ __html: msgSub(message) }}
      />
    </div>
  );
};

const msgSub = message => {   // expensive function 2
  const messageSub = message.replace(/[a]/g, "A").replace(/[e]/g, "E");
  console.log("in msgSub:", message, messageSub);
  return messageSub;
};

(我没有让它在 SO 的编辑器上运行,所以在codeandbox上查看并运行它。)

这是使用自定义钩子和 useMemo 钩子的尝试之一。

任何指导将不胜感激!

以及说明如何在 SO 的编辑器中对工作做出反应的奖励积分!

4

2 回答 2

1

我的第一步是进行更改createItem,使 Item 成为其唯一的功能组件。这意味着我可以记住该<Item/>组件,以便它仅在道具更改时才会呈现,重要的是消息和键/索引(就像您之前呈现的值一样)。

这个https://codesandbox.io/s/funny-chaplygin-pvfoj的工作示例。

const Item = React.memo(({ message, index }) => {
  console.log("Rendering:", index, message);

  const calculatedMessage = msgSub(message);

  return (
    <div>
      <strong>{"Message " + (1 + index)}: </strong>
      {message} =>{" "}
      <span
        id={"preview-" + index}
        dangerouslySetInnerHTML={{ __html: calculatedMessage }}
      />
    </div>
  );
});

const msgSub = message => {
  // expensive function 2
  const messageSub = message.replace(/[a]/g, "A").replace(/[e]/g, "E");
  console.log("in msgSub:", message, messageSub);
  return messageSub;
};

您可以看到,在初始渲染时,它会渲染所有项目,message尤其是apple两次。

如果两个组件相互独立渲染,并且碰巧使用相同的道具,这两个组件将被渲染。React.memo 不保存组件渲染。

它必须渲染 Item 组件两次<Item message="apple" />,一次apple在索引 0 处,另一次在索引 5 处apple


您会注意到我在 App 中放置了一个按钮,单击该按钮将更改数组的内容。

稍微更改原始数组,我将其放置carrot在原始数组的索引 4 处,并将其移动到新数组中的索引 2,当我更新它时。

  const [stringArray, setStringArray] = React.useState([
    "apple",
    "banana",
    "cherry",
    "durian",
    "carrot",
    "apple" // duplicate Apple
  ]);

  const changeArray = () =>
    setStringArray([
      "apple",
      "banana",
      "carrot",  // durian removed, carrot moved from index 4 to index 2
      "dates", // new item
      "elderberry", // new item
      "apple"
    ]);

如果您查看控制台,您会在看到的第一个渲染中看到它,in msgSub: carrot cArrot但是当我们更新数组时,in msgSub: carrot cArrot会再次调用它。这是因为 key on<Item />所以它被迫重新渲染。这是正确的,因为我们的键是基于索引的,并且胡萝卜改变了位置。但是,你说msgSub的是一个昂贵的功能......


本地记忆

我注意到在你的阵列中你有apple两次。

常量输入 = [“苹果”、“香蕉”、“樱桃”、“榴莲”、“接骨木”、“苹果”];

我觉得你想记住计算,这样apple就不会再计算了,如果apple以前计算过的话。

我们可以将计算的值存储在我们自己的记忆状态中,这样我们就可以查找消息值,看看我们之前是否计算过。

const [localMemoization, setLocalMemoization] = useState({});

我们希望确保在stringArray更改时更新此 localMemoization。

  React.useEffect(() => {
    setLocalMemoization(prevState =>
      stringArray.reduce((store, value) => {
        const calculateValue =
          prevState[value] ?? store[value] ?? msgSub(value);
        return store[value] ? store : { ...store, [value]: calculateValue };
      })
    );
  }, [stringArray]);

这条线const calculateValue = prevState[value] ?? store[value] ?? msgSub(value);

  • 检查先前的状态以查看它是否具有先前的值(对于胡萝卜将第一个数组移动到第二个数组的情况)。
  • 检查当前商店以查看它是否具有该值(对于第二个苹果案例)。
  • 最后,如果什么都没看到,我们第一次使用昂贵的函数来计算值。

使用与之前渲染相同的逻辑,我们现在查找计算的值并将其传递给<Item>React.memo,以便在 App 再次渲染时阻止该组件的重新渲染。

const output = stringArray.map((msg, key) => {
    const expensiveMessage = localMemoization[msg];
    return (
      <Item
        key={key}
        message={msg}
        calculatedValue={expensiveMessage}
        index={key}
      />
    );
  });

此处的工作示例https://codesandbox.io/s/attempt-with-custom-hooks-and-usememo-y3lm5?file=/src/App.js:759-1007

由此,在控制台中,我们可以看到在第一次渲染时,apple只计算了一次。

当数组改变时,我们不再计算carrot,只计算改变的项dateselderberry

于 2020-07-06T21:49:56.953 回答
0

使 item 成为纯组件:

const id = ((id) => () => id++)(1); //IIFE creating id
//pure component
const Item = React.memo(function Item({ increase, item }) {
  console.log('rendering id:', item.id);
  return (
    <ul>
      <button onClick={() => increase(item.id)}>
        {item.count}
      </button>
    </ul>
  );
});
const App = () => {
  const [items, setItems] = React.useState([]);
  //use callback, increase only created on app mount
  const increase = React.useCallback(
    (id) =>
      setItems((items) =>
        //use state setter callback
        //no dependencies for useCallback and no stale
        //closures
        items.map((item) =>
          item.id === id
            ? { ...item, count: item.count + 1 }
            : item
        )
      ),
    [] //no dependencies
  );
  return (
    <div>
      <div>
        <button
          onClick={() =>
            setItems((items) => [
              { id: id(), count: 0 },
              ...items,
            ])
          }
        >
          add counter
        </button>
      </div>
      <ul>
        {items.map((item) => (
          <Item
            key={item.id}
            item={item}
            increase={increase}
          />
        ))}
      </ul>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

于 2020-07-06T20:48:33.290 回答