javascript - 使用 JSX 的 map 时如何避免卸载子组件?

标签 javascript reactjs unmount

这是 a question I raised previously 的更简洁版本.希望它能得到更好的解释和更容易理解。

这是一个小应用程序,它有 3 个需要数字的输入(请忽略您也可以输入非数字,这不是重点)。它计算所有显示数字的总和。如果您将其中一个输入更改为另一个数字,则总和会更新。

App capture

这是它的代码:

import { useCallback, useEffect, useState } from 'react';

function App() {

  const [items, setItems] = useState([
    { index: 0, value: "1" },
    { index: 1, value: "2" },
    { index: 2, value: "3" },
  ]);

  const callback = useCallback((item) => {
    let newItems = [...items];
    newItems[item.index] = item;
    setItems(newItems);
  }, [items]);

  return (
    <div>
      <SumItems items={items} />
      <ul>
        {items.map((item) =>
          <ListItem key={item.index} item={item} callback={callback} />
        )}
      </ul>
    </div>
  );
}


function ListItem(props) {

  const [item, setItem] = useState(props.item);

  useEffect(() => {
    console.log("ListItem ", item.index, " mounting");
  })

  useEffect(() => {
    return () => console.log("ListItem ", item.index, " unmounting");
  });

  useEffect(() => {
    console.log("ListItem ", item.index, " updated");
  }, [item]);

  const onInputChange = (event) => {
    const newItem = { ...item, value: event.target.value };
    setItem(newItem);
    props.callback(newItem);
  }

  return (
    <div>
      <input type="text" value={item.value} onChange={onInputChange} />
    </div>);
};

function SumItems(props) {
  return (
    <div>Sum : {props.items.reduce((total, item) => total + parseInt(item.value), 0)}</div>
  )

}

export default App;

下面是启动时以及将第二个输入 2 更改为 4 后的控制台输出:

ListItem  0  mounting App.js:35
ListItem  0  updated App.js:43
ListItem  1  mounting App.js:35
ListItem  1  updated App.js:43
ListItem  2  mounting App.js:35
ListItem  2  updated App.js:43
ListItem  0  unmounting react_devtools_backend.js:4049:25
ListItem  1  unmounting react_devtools_backend.js:4049:25
ListItem  2  unmounting react_devtools_backend.js:4049:25
ListItem  0  mounting react_devtools_backend.js:4049:25
ListItem  1  mounting react_devtools_backend.js:4049:25
ListItem  1  updated react_devtools_backend.js:4049:25
ListItem  2  mounting react_devtools_backend.js:4049:25

如您所见,更新单个输入时,不会重新渲染所有子项,它们会先卸载,然后重新装载。太浪费了,所有的输入都已经是正确的状态,只需要更新总和。想象一下有数百个这样的输入。

如果只是重新渲染的问题,我可以看看内存。但那是行不通的,因为 callback 的更新正是因为 items 发生了变化。不,我的问题是关于所有 child 的卸载。

问题 1:是否可以避免卸载?

如果我相信this article by Kent C. Dodds ,答案是否定的(强调我的):

React's key prop gives you the ability to control component instances. Each time React renders your components, it's calling your functions to retrieve the new React elements that it uses to update the DOM. If you return the same element types, it keeps those components/DOM nodes around, even if all* the props changed.

(...)

The exception to this is the key prop. This allows you to return the exact same element type, but force React to unmount the previous instance, and mount a new one. This means that all state that had existed in the component at the time is completely removed and the component is "reinitialized" for all intents and purposes.

问题 2:如果这是真的,那么我应该考虑什么样的设计来避免看似不必要并导致问题的情况 in my real应用程序,因为每个输入组件中都发生了异步处理?

最佳答案

As you can see, when a single input is updated, all the children are not re-rendered, they are first unmounted, then re-mounted. What a waste, all the input are already in the right state, only the sum needs to be updated. And imagine having hundreds of those inputs.

不,您从 useEffect 看到的日志不代表组件安装/卸载。您可以检查 DOM 并验证只有一个输入被更新,即使所有三个组件都被重新呈现。

If it was just a matter of re-rendering, I could look at memoization. But that wouldn't work because the callback is updated precisely because items change. No, my question is about the unmounting of all the children.

这是您将使用功能状态更新来访问以前的状态并返回新状态的地方。

const callback = useCallback((item) => {
    setItems((prevItems) =>
      Object.assign([...prevItems], { [item.index]: item })
    );
}, []);

现在,您可以使用 React.memo,因为回调不会改变。这是更新后的演示:

Edit inspiring-bash-utk30

如您所见,只有相应的输入日志被记录,而不是当其中一个被更改时记录所有三个。

关于javascript - 使用 JSX 的 map 时如何避免卸载子组件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69251637/

相关文章:

javascript - 在大型 TypeScript 项目中共享类型的最佳方式?

android挂载卸载sd卡

c++ - 我怎样才能卸载事件分区?

javascript - 单击按钮更改响应式网站的主题

javascript - 关于 Javascript 中的排行榜(数组)请帮助我

javascript - angular 2 settimeout 这是未定义的

javascript - 使用 jQuery 检查用户是否滚动到 div 底部

ios - React Native Hot Reloading 不适用于 IOS 模拟器

reactjs - 如何更改 MUI Datagrid 中的导出标签

android - 以编程方式在 android 4.2.1 上安装 USB 存储