React:更新列表中的一项而不重新创建所有项

IT技术 javascript reactjs
2021-05-05 07:03:57

假设我有一个包含 1000 个项目的列表。我用 React 渲染它,像这样:

class Parent extends React.Component {
  render() {
    // this.state.list is a list of 1000 items
    return <List list={this.state.list} />;
  }
}

class List extends React.Component {
  render() {
    // here we're looping through this.props.list and creating 1000 new Items
    var list = this.props.list.map(item => {
      return <Item key={item.key} item={item} />;
    });
    return <div>{list}</div>;
  }
}

class Item extends React.Component {
  shouldComponentUpdate() {
    // here I comparing old state/props with new
  }
  render() {
    // some rendering here...
  }
}

使用相对较长的列表 map() 大约需要 10-20 毫秒,我可以注意到界面中有一点延迟。

当我只需要更新一个时,我可以防止每次重新创建 1000 个 React 对象吗?

4个回答

您可以使用任何状态管理库来完成此操作,这样您Parent就不会跟踪this.state.list=>添加ListItem内容时唯一的重新渲染并且个人Item将在更新时重新渲染。

假设您使用redux.

你的代码会变成这样:

// Parent.js
class Parent extends React.Component {
  render() {        
    return <List />;
  }
}

// List.js
class List extends React.Component {
  render() {        
    var list = this.props.list.map(item => {
      return <Item key={item.key} uniqueKey={item.key} />;
    });
    return <div>{list}</div>;
  }
}

const mapStateToProps = (state) => ({
  list: getList(state)
});

export default connect(mapStateToProps)(List);


// Item.js
class Item extends React.Component {
  shouldComponentUpdate() {
  }
  render() {
  }
}

const mapStateToProps = (state, ownProps) => ({
  item: getItemByKey(ownProps.uniqueKey)
});

export default connect(mapStateToProps)(Item);

当然,你必须实现reducer和两个选择器getListgetItemByKey

有了这个,List如果添加了新元素,或者如果您更改item.key(您不应该这样做),您将重新渲染

编辑

我最初的建议只解决了渲染列表可能的效率改进问题,并没有解决由于列表更改而限制组件重新渲染的问题。

请参阅@ xiaofan2406的答案以获取原始问题的干净解决方案。


有助于使长列表渲染更高效和简单的库

react无限

react虚拟化

当你更改数据时,react 默认操作是渲染所有子组件,并创建虚拟 dom 来判断哪个组件需要重新渲染。

所以,如果我们能让 react 知道只有一个组件需要重新渲染。它可以节省时间。

您可以shouldComponentsUpdate在列表组件中使用。

如果在这个函数中返回false,react不会创建虚拟dom来判断。

我假设你的数据是这样的 [{name: 'name_1'}, {name: 'name_2'}]

class Item extends React.Component {
  // you need judge if props and state have been changed, if not
  // execute return false;
  shouldComponentUpdate(nextProps, nextState) { 
    if (nextProps.name === this.props.name) return false;

    return true;
  }
  render() {
    return (
      <li>{this.props.name}</li>
   )
  }
}

由于react只是渲染已更改的组件。因此,如果您只更改一项的数据,则其他项将不会进行渲染。

您可以执行以下操作:

  1. 构建时,请确保将 NODE_ENV 设置为生产。例如NODE_ENV=production npm run build或类似。ReactJS 在NODE_ENV未设置为生产执行很多安全检查,例如 PropType 检查。关闭这些应该能让你的 React 渲染性能提高 2 倍以上,这对你的生产构建至关重要(尽管在开发过程中将其关闭 - 这些安全检查有助于防止错误!)。您可能会发现这对于您需要支持的项目数量来说已经足够了。
  2. 如果元素位于可滚动面板中,并且您只能看到其中的几个,则可以设置仅呈现元素的可见子集。当项目具有固定高度时,这是最简单的。基本方法是将firstRendered/ lastRenderedprops添加到您的 List 状态(当然,这是第一个包含的和最后一个包含的)。在 中List.render,渲染正确高度 ( )填充空白div(或tr如果适用)firstRendered * itemHeight,然后是渲染的项目范围[firstRendered, lastRendered),然后是另一个具有剩余高度的填充 div ((totalItems - lastRendered) * itemHeight)。确保你给你的填充物和物品固定的钥匙。然后,您只需要处理可滚动 div 上的 onScroll,并计算出要呈现的正确范围是什么(通常您希望在顶部和底部呈现合适的重叠,并且您只想触发 setState 以在以下情况下更改范围你靠近它的边缘)。一个更疯狂的选择是渲染和实现你自己的滚动条(我认为这是 Facebook 自己的 FixedDataTable - https://facebook.github.io/fixed-data-table/)。这里有很多这种通用方法的例子https://react.rocks/tag/InfiniteScroll
  3. 使用使用状态管理库横向加载方法。对于较大的应用程序,这无论如何都是必不可少的。不是从顶部向下传递 Item 的状态,而是让每个 Item 从“全局”状态(如在经典 Flux 中)或通过 React 上下文(如在现代 Flux 实现、MobX 等中)检索自己的状态。这样,当一个项目发生变化时,只有该项目需要重新渲染。