React:更新列表中的一项(setState)而不是全部

React: updating one item of the list (setState) instead of all

我有以下代码。当改变一个元素的值时,整个列表被重新渲染。只重绘一个元素如何避免呢?我使用 setState 和 class 组件。

import React from "react";
import "./styles.css";

class ListItem extends React.Component {
  
  handleUpdate = () => {
    this.props.onUpdate(this.props.index);
  };

  totalRenders = 0;

  render() {
    
    const { data: { id, value } } = this.props;

    this.totalRenders += 1;
    console.log("Row rendered");

    return (
      <div>
        <strong>id: </strong>{id}: &nbsp;
        <strong>value: </strong>{value} &nbsp;
        <strong>renders count: </strong>{this.totalRenders} &nbsp;
        <button className="button" onClick={this.handleUpdate}> &nbsp;
          Change row value
        </button>
      </div>
    );
  }
}

export default class App extends React.Component {
  state = { list: [{id: 'id 1', value: '11'}, {id: 'id 2', value: '22'}]};

  handleUpdate = (index) => {
    let newState = this.state.list.slice()
    newState[index].value = Math.round(Math.random() + 10);

    this.setState(() => ({
      list: newState
    }));
  };

  render() {
    console.log("App rendered");

    return (
      <div>
        {this.state.list.map((el, index) => (
          <ListItem
            key={el.id}
            data={el}
            index={index}
            onUpdate={this.handleUpdate}
          />
        ))}
      </div>
    );
  }
}

沙盒:https://codesandbox.io/s/autumn-architecture-ubgh51?file=/src/App.js

在您的情况下,您可以使用 shouldComponentUpdate,参考:React document

简单示例:

  shouldComponentUpdate(nextProps, nextState) {
    // Custom update conditions.
    return nextProps.data.id !== this.props.data.id;
  }

完整样本:

※补充说明

如果用function component,可以用memo来做,参考:React document.

如果您更新您的应用程序状态,那么该组件应该被更新。预期的行为,这就是它应该如何表现。现在来回答你的问题。如果连续更改不应该使您的整个应用程序 re-render。然后你应该在你的项目中管理你的状态,并且应该在其中维护任何不必要的更改 re-render.

这是一个如何实现它的例子。您可以用这个替换您的 ListItem 并自行检查。

function UpdatedListItem({ data }) {
  const [row, setRow] = React.useState(data);

  React.useEffect(() => setRow(data), [data]);

  const { id, value } = row;
  console.log("Changes in: ", data);

  function handleChange() {
    setRow({
      ...row,
      value: Math.round(100 + Math.random() * 5)
    });
  }

  return (
    <div>
      <strong>id: </strong>
      {id}: &nbsp;
      <strong>value: </strong>
      {value} &nbsp;
      <strong>renders count: </strong>
      {this.renders} &nbsp;
      <button className="button" onClick={handleChange}>
        &nbsp; Change row value
      </button>
    </div>
  );
}

解决此问题的一种方法是使用实​​现 shouldcomponentupdate 但是当您需要一些性能改进而不仅仅是停止重新渲染时,应该实施该方法

This method only exists as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.

接下来您可以考虑使用 PureComponent

要以这种方式实施,您需要更改处理状态更新的方式

handleMyChange = (index) => {

// Don't mutate the state in react it could lead in to unexpected results
// let newState = [...this.state.list];
// newState[index].value = Math.round(Math.random() + 10);

  const newState = this.state.list.map((item, idx) => {
    if (idx === index) {
     // Replacing with a new item, so the other component can understand it's a new item and re-render it.
      return {
        ...item,
        value: Math.round(100 + Math.random() * 5)
      };
    }

    // Return the same item, so the other component do not render since it's the same item.
    return item;
  });

  this.setState(() => ({
    list: newState
  }));
};

class ListItem extends React.PureComponent {
......

React.PureComponent 通过浅层 prop 和状态比较来实现它,为了将其检测为新的更新,我们必须告诉 ListItem 组件 props 不同,为此我们应该避免在数组,相反我们必须创建一个新项目并替换旧的数组元素。

这是一个很好的例子

这是更新后的 sandbox