繁体   English   中英

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

[英]React: update one item in a list without recreating all items

假设我有一个包含 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 对象吗?

您可以使用任何状态管理库来完成此操作,这样您的Parent级就不会跟踪this.state.list => 您的List只会在添加新Item时重新呈现。 并且单个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

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

编辑

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

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


有助于更高效、更轻松地呈现长列表的库

反应无限

反应虚拟化

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

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

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

如果在这个函数中返回false,react不会创建vitual 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>
   )
  }
}

作为反应,只渲染已更改的组件。 所以如果你只是改变一个项目的数据,其他人不会做渲染。

您可以做几件事:

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

避免在每次渲染时循环遍历组件列表的一种方法是在渲染函数之外执行此操作并将其保存到变量中。

class Item extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return this.props.item != nextProps.item;
    }

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

class List extends React.Component {
    constructor(props) {
        super(props);
        this.items = [];
        this.update = this.update.bind(this);
    }

    componentWillMount() {
        this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
    }

    update(index) {

        this.items[index] = <Item key={index} item={'item changed'} />
        this.forceUpdate();
    }

    render() {
        return <div>
            <button onClick={() => { this.update(199); }}>Update</button>
            <ul>{this.items}</ul>
        </div>
    }
}

假设我有一个包含 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 对象吗?

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM