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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33080657/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 14:37:14  来源:igfitidea点击:

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

javascriptreactjs

提问by ch1p_

Let's say I have a list of 1000 items. And I rendering it with React, like this:

假设我有一个包含 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...
  }
}

With a relatively long list map() takes about 10-20ms and I can notice a small lag in the interface.

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

Can I prevent recreation of 1000 React objects every time when I only need to update one?

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

回答by xiaofan2406

You can do it by using any state management library, so that your Parentdoesn't keep track of this.state.list=> your Listonly re-renders when new Itemis added. And the individual Itemwill re-render when they are updated.

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

Lets say you use redux.

假设您使用redux.

Your code will become something like this:

你的代码会变成这样:

// 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);

Of course, you have to implement the reducer and the two selectors getListand getItemByKey.

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

With this, you Listre-render will be trigger if new elements added, or if you change item.key(which you shouldn't)

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

回答by Pineda

EDIT:

My inital suggestions only addressed possible efficiency improvements to rendered listsand did not address the question about limiting the re-rendering of components as a result of the list changing.

See @xiaofan2406's answer for a clean solution to the original question.

编辑

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

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



Libraries that help make rendering long lists more efficient and easy:

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

React Infinite

反应无限

React-Virtualized

反应虚拟化

回答by qiuyuntao

When you change your data, react default operation is to render all children components, and creat virtual dom to judge which component is need to be rerender.

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

So, if we can let react know there is only one component need to be rerender. It can save times.

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

You can use shouldComponentsUpdatein your list component.

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

If in this function return false, react will not create vitual dom to judge.

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

I assume your data like this [{name: 'name_1'}, {name: 'name_2'}]

我假设你的数据是这样的 [{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>
   )
  }
}

As react just render what have been changed component. So if you just change one item's data, others will not do render.

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

回答by jpdelatorre

One way to avoid looping through the component list every render would be to do it outside of render function and save it to a variable.

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

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>
    }
}

回答by TomW

There are a few things you can do:

您可以执行以下操作:

  1. When you build, make sure you are setting NODE_ENV to production. e.g. NODE_ENV=production npm run buildor similar. ReactJS performs a lot of safety checks when NODE_ENVis not set to production, such as PropType checks. Switching these off should give you a >2x performance improvement for React rendering, and is vital for your production build (though leave it off during development - those safety checks help prevent bugs!). You may find this is good enough for the number of items you need to support.
  2. If the elements are in a scrollable panel, and you can only see a few of them, you can set things up only to render the visible subset of elements. This is easiest when the items have fixed height. The basic approach is to add firstRendered/lastRenderedprops to your List state (that's first inclusiveand last exclusiveof course). In List.render, render a fillerblank div(or trif applicable) of the correct height (firstRendered * itemHeight), then your rendered range of items [firstRendered, lastRendered), then another filler div with the remaining height ((totalItems - lastRendered) * itemHeight). Make sure you give your fillers and items fixed keys. You then just need to handle onScroll on the scrollable div, and work out what the correct range to render is (generally you want to render a decent overlap off the top and bottom, also you want to only trigger a setState to change the range when you get near to the edge of it). A crazier alternative is to render and implement your own scrollbar (which is what Facebook's own FixedDataTable does I think - https://facebook.github.io/fixed-data-table/). There are lots of examples of this general approach here https://react.rocks/tag/InfiniteScroll
  3. Use a sideways loadingapproach using a state management library. For larger apps this is essential anyway. Rather than passing the Items' state down from the top, have each Item retrieve its own state, either from 'global' state (as in classical Flux), or via React context (as in modern Flux implementations, MobX, etc.). That way, when an item changes, only that item needs to re-render.
  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 等中)检索自己的状态。这样,当一个项目发生变化时,只有该项目需要重新渲染。

回答by x-rw

you have to compare a item with your items

你必须将一个项目与你的项目进行比较

UpdateComment=(comment)=>{
    let objIndex = this.state.comments.findIndex((obj => obj.id === comment.id));

    let comments = [...this.state.comments];
    comments[objIndex].content = comment.content
    comments[objIndex].idea = comment.idea
    this.setState({comments});
  }