简体   繁体   English

React - 如何从正在更改的对象数组中仅重新渲染一个组件

[英]React - How to rerender only one component from an array of objects that is changing

I have a simple question that has to do with React rendering.我有一个与 React 渲染有关的简单问题。 Here's the link to the code sandbox before I explain: https://codesandbox.io/s/list-rerendering-y3iust?file=/src/App.js这是我解释之前代码沙箱的链接: https://codesandbox.io/s/list-rerendering-y3iust?file=/src/App.js

Here's the deal, I have an array of objects stored in a parent component called App.这是交易,我有一个对象数组存储在一个名为 App 的父组件中。 Each object has a 'checked' field which I want to toggle on clicking on that object's respective checkbox.每个 object 都有一个“选中”字段,我想在单击该对象的相应复选框时进行切换。 I loop through the array of objects and display them each within a Child component.我遍历对象数组并在子组件中显示它们。 When I click on a checkbox, the handleChange function executes and that list is updated within the parent component, causing App to rerender along with ALL of the Child components.当我点击一个复选框时,handleChange function 执行并且该列表在父组件中更新,导致 App 与所有子组件一起重新呈现。 The problem I want to solve is, how can I make it so I only rerender the Child component that was clicked instead of all of them?我要解决的问题是,我怎样才能让它只重新渲染被点击的子组件而不是全部?

I tried using useCallback along with a functional update to the list state, but that didn't do anything and it still rerenders all of the child components instead of the one being toggled.我尝试使用 useCallback 以及对列表 state 的功能更新,但这没有做任何事情,它仍然重新呈现所有子组件,而不是被切换的子组件。 I have a hunch that I'm using useCallback incorrectly, and that a brand new function is being created.我有一种预感,我正在错误地使用 useCallback,并且正在创建一个全新的 function。 I'd like an explanation of how React does it's rerendering when it comes to arrays, comparing the previous array against the new array.我想解释一下当涉及到 arrays 时,React 如何重新渲染,并将以前的数组与新数组进行比较。 I understand that in my code I'm providing a copy of the original list by destructuring it and then putting it inside a new array, which obviously is not a reference to the original list so React sets the copy as the new state:我知道在我的代码中,我通过解构它然后将其放入一个新数组中来提供原始列表的副本,这显然不是对原始列表的引用,因此 React 将副本设置为新的 state:

App.js应用程序.js

import { useCallback, useState } from "react";
import Child from "./Child";
import "./styles.css";

const mockList = [
  { text: "1", id: 1, checked: false },
  { text: "2", id: 2, checked: false },
  { text: "3", id: 3, checked: false },
  { text: "4", id: 4, checked: false },
  { text: "5", id: 5, checked: false }
];

export default function App() {
  const [list, setList] = useState(mockList);

  const handleChange = useCallback((checked, id) => {
    setList((oldList) => {
      for (let i = 0; i < oldList.length; i++) {
        if (oldList[i].id === id) {
          oldList[i].checked = checked;
          break;
        }
      }
      return [...oldList];
    });
  }, []);

  return (
    <div className="App">
      {list.map((item) => (
        <Child
          key={item.id}
          text={item.text}
          checked={item.checked}
          handleChange={(checked) => handleChange(checked, item.id)}
        />
      ))}
    </div>
  );
}

Child.js Child.js

const Child = ({ text, checked, handleChange }) => {
  console.log("Child rerender");
  return (
    <div
      style={{
        display: "flex",
        border: "1px solid green",
        justifyContent: "space-around"
      }}
    >
      <p>{text}</p>
      <input
        style={{ width: "20rem" }}
        type="checkbox"
        checked={checked}
        onChange={(e) => handleChange(e.checked)}
      />
    </div>
  );
};

export default Child;

Here's how you optimize it, first you use useCallback wrong, because every rerender the (e) => handleChange(e.checked) is a new instance, hence even if we memo the Child it will still rerender because props is always new.这是你如何优化它,首先你使用useCallback错误,因为每次重新渲染(e) => handleChange(e.checked)都是一个新实例,因此即使我们记下Child它仍然会重新渲染,因为 props 总是新的。
So we need to useCallback to the function that invoke handleChange see my forked codesandbox所以我们需要使用回调到调用 handleChange 的handleChange参见我的分叉代码和框

https://codesandbox.io/s/list-rerendering-forked-tlkwgh?file=/src/App.js https://codesandbox.io/s/list-rerendering-forked-tlkwgh?file=/src/App.js

React has nothing to do with how you manipulate your arrays or objects. React 与您如何操作 arrays 或对象无关。 It simply goes on rendering your app tree and there are certain rules that decide when to stop rerendering a certain branch within the tree.它只是继续渲染您的应用程序树,并且有一些规则可以决定何时停止重新渲染树中的某个分支。 Rendering simply means React calls the component functions which themselves return a sub-tree or nodes.渲染只是意味着 React 调用组件函数,这些函数本身返回一个子树或节点。 Please see https://reactjs.org/docs/reconciliation.html请参阅https://reactjs.org/docs/reconciliation.html

Try wrapping the Child with React.memo() :尝试用React.memo()包装Child

const Child = ({ text, checked, handleChange }) => {
  console.log("Child rerender");
  return (
    <div
      style={{
        display: "flex",
        border: "1px solid green",
        justifyContent: "space-around"
      }}
    >
      <p>{text}</p>
      <input
        style={{ width: "20rem" }}
        type="checkbox"
        checked={checked}
        onChange={(e) => handleChange(e.checked)}
      />
    </div>
  );
};

export default React.memo(Child);

What this React.memo() does is essentially compare previous props with next props and only rerenders the Child component if any of the props have changed and doesn't re-render if the props have stayed the same.这个React.memo()所做的本质上是将先前的道具与下一个道具进行比较,并且仅在任何道具发生更改时重新渲染Child组件,并且如果道具保持不变则不会重新渲染。

As a side note: Please read this https://kentcdodds.com/blog/usememo-and-usecallback作为旁注:请阅读此https://kentcdodds.com/blog/usememo-and-usecallback
TLDR: Over optimisation at the cost of code complexity is not recommended. TLDR:不建议以代码复杂性为代价进行过度优化。 And preventing “unnecessary” renders probably will have a drawback in the form of extra memory usage and computation: so only need to do it if very sure it will help in a specific problem.并且防止“不必要的”渲染可能会有额外的 memory 使用和计算形式的缺点:所以只有在非常确定它有助于解决特定问题时才需要这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 当更改作为数组传递的道具时,React功能组件不会重新渲染 - React functional component doesnt rerender when changing props passed as an array 防止重新渲染反应对象数组 - Prevent rerender react array of objects 如何根据计时器触发重新渲染,但仅在 React 中的特定组件上触发 - How do trigger a rerender based on a timer but only on a specific component in React 如何避免在 React 中重新渲染组件? - How to avoid rerender of a component in React? 从子组件更改 state 时,反应避免重新渲染父组件 - React avoid rerender of parent component when changing state from child component 如何强制将一个Vue组件从另一个组件重新渲染 - How to force rerender of one Vue component from another 如何在反应中将对象数组从父组件传递到子组件 - How to pass Array of Objects from Parent Component to Child Component in react 错误:对象作为 React 子项无效(尽管仅使用一组对象属性渲染一个组件) - Error: Objects are not valid as a React child (despite only rendering one component with an array of objects prop) 删除一个组件后,React 列表组件不会重新渲染 - React list component won't rerender after deleting one component from it 其中一个字段是 React 组件的对象数组? - Array of Objects with one of the fields being a React component?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM