React/Redux 渲染一个每秒更新的列表 [英] React/Redux rendering a list that's updating every second

查看:54
本文介绍了React/Redux 渲染一个每秒更新的列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个反应组件,它每秒从 redux 商店接收道具.新状态的数组与上一个数组不同.具体来说,每秒都会有一个元素添加到数组中.例如:在一种状态下,数组是:

[1, 2, 3, 4, 5, 6]

下一个状态

[1, 2, 3, 4, 5, 6, 7]

我的减速机:

return {...状态,myList: [有效载荷, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]}

现在,在我的 React 组件中,我订阅了这个状态,并且对于每次更改,列表都会重新呈现.

import React, { Component } from 'react';从'./MyRow'导入MyRow;类 MyList 扩展组件 {使成为() {返回 (<div>{this.props.myList.map((list, index) => (<MyRow key={list.id} data={list}/>))}

);}}功能选择({ myList }){返回 { myList };}导出默认连接(选择)(MyList);

在 MyRow.js 中

import { PureComponent } from 'react';类 MyRow 扩展 PureComponent {使成为() {const 数据 = this.props.data;返回 (<div>{data.id} - {data.name}

);}}导出默认的 MyRow;

现在,我的问题是:重新渲染已经渲染的每个元素对我来说成本很高.MyRow 大量使用样式组件和其他昂贵的操作.当状态更新时,这会导致 react 每秒重新渲染整个列表.如果更新时间少于 1 秒,这会变得更糟,比如每秒 4 次更新.在这种情况下,react 应用程序只会崩溃.

有没有办法只将新添加的项目添加到列表中而不重新渲染整个列表?

谢谢

解决方案

您正在使用 PureComponent,进行浅层比较,那么您的组件 MyRow 应该在添加的每个新项目上重新呈现(请按照我下面的代码示例).

<块引用>

有没有办法只将新添加的项目添加到列表中而不重新渲染整个列表?

根据您的问题 - 是的,使用 PureComponent 应该只渲染 1 次新项目:

这是 React 的文档 所说的:

<块引用>

如果你的 React 组件的 render() 函数在给定相同的 props 和 state 的情况下呈现相同的结果,你可以在某些情况下使用 React.PureComponent 来提升性能.

PureComponent的代码示例:

你可以查看我为你做的代码示例.

你会看到 Item 组件总是只渲染 1 次,因为我们使用了 React.PureComponent.为了证明我的说法,每次渲染 Item 时,我都会添加当前渲染时间.从示例中您将看到 Item Rendered at: 时间始终相同,因为它只渲染了 1 次.

const itemsReducer = (state = [], action) =>{if (action.type === 'ADD_ITEM') 返回 [ ...state, action.payload]返回状态}const addItem = item =>({类型:'ADD_ITEM',有效载荷:项目})类 Item 扩展 React.PureComponent {使成为 () {//正如你在这里看到的,`Item` 总是只渲染 1 次,//因为我们使用了 `React.PureComponent`.//您可以检查 `Item` `Rendered at:` 时间是否始终相同.//如果我们用 `React.Component` 来做,//然后`Item` 将在每次列表更新时重新渲染.返回 <div>{ this.props.name }, 渲染于:{ Date.now() }</div>}}类列表扩展了 React.Component {构造函数(道具){超级(道具)this.state = { intervalId: null }this.addItem = this.addItem.bind(this)}组件DidMount(){//每 1 秒添加一个新项目,//并保留它的`id`,以便稍后清除间隔const intervalId = setInterval(this.addItem, 1000)this.setState({ intervalId })}组件将卸载(){//使用状态中的intervalId清除间隔clearInterval(this.state.intervalId)}添加项目 () {const id = Date.now()this.props.addItem({ id, name: `Item - ${id}` })}渲染项目(){return this.props.items.map(item => )}使成为 () {返回 

{this.renderItems()}

}}const mapDispatchToProps = { addItem }const mapStateToProps = state =>({ 项目:状态})const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)const Store = Redux.createStore(itemsReducer)const 提供者 = ReactRedux.ProviderReactDOM.render(<提供者商店={商店}><ListContainer/></提供者>,document.getElementById('容器'))

<script src="https://unpkg.com/react@16/umd/react.development.js"><;/脚本><script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script><div id="容器"><!-- 此元素的内容将替换为您的组件.-->

解决方案:

  1. 如果性能问题是由MyRow重新渲染引起的,请找出重新渲染的原因,因为它不应该发生,因为使用PureComponent.
    • 您可以尝试简化您的reducer,以便检查/调试是否是reducer 导致问题.例如,只需将新项目添加到列表中(不做任何其他过滤、切片等操作):myList: [ ...state.myList, payload ]
    • 请确保您始终将相同的 key 传递给您的项目组件 <MyRow key={list.id} data={list}/>.如果 keydata 属性发生变化,则组件将被重新渲染.

<小时>

  1. 这里有一些其他的库,它们代表列表的高效呈现.我相信他们会给我们一些替代方案或见解:

    • react-virtualized - 用于高效呈现大型列表和表格数据的 React 组件
    • react-infinite - 一个基于 UITableView 的浏览器就绪高效滚动容器

<小时>

I have a react component that receives props from the redux store every second. The new state has an array that's different than the last array. To be specific, every second an element is added to the array. For example: in one state the array is:

[1, 2, 3, 4, 5, 6]

the next state

[1, 2, 3, 4, 5, 6, 7]

My reducer:

return {
  ...state,
  myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
}

Now, in my react component I am subscribing to this state and for every change, the list is re-rendered.

import React, { Component } from 'react';
import MyRow from './MyRow';

class MyList extends Component {

    render() {

        return (

        <div>

            {this.props.myList.map((list, index) => (
                <MyRow key={list.id} data={list}/>
            ))}

        </div>

        );
    }
}

function select({ myList }) {
    return { myList };
}

export default connect(select)(MyList);

In MyRow.js

import { PureComponent } from 'react';

class MyRow extends PureComponent {

    render() {

    const data = this.props.data;

        return (
            <div>
                {data.id} - {data.name}
            </div>
        );

    }
}
export default MyRow;

Now, my problem is: It's costly for me to re-render every element that has been already rendered. The MyRow heavily uses styled components and other expensive operations. This is causing react to re-render the whole list every second when the state is updated. This gets worst if updates come in less than 1 seconds, like 4 updates per second. The react app simply crashes in this case.

Is there any way to only add the newly added item to the list and not re-render the whole list?

Thanks

解决方案

You're using PureComponent, that do shallow comparison, then your component MyRow should not be rerendered on each new item being added (Please follow my code example below).

Is there any way to only add the newly added item to the list and not re-render the whole list?

According to your question - Yes, using PureComponent should render only 1 time the new item:

Here's what the React's docs says:

If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.

Code example of PureComponent:

You can check out the code sample, that I did for you.

You will see that the Item component is always rendered only 1 time, because we use React.PureComponent. To prove my statement, each time the Item is rendered, I added current time of rendering. From the example you will see that the Item Rendered at: time is always the same, because it's rendered only 1 time.

const itemsReducer = (state = [], action) => {
  if (action.type === 'ADD_ITEM') return [ ...state, action.payload]

  return state
}

const addItem = item => ({
  type: 'ADD_ITEM',
  payload: item
})

class Item extends React.PureComponent {
  render () {
    // As you can see here, the `Item` is always rendered only 1 time,
    // because we use `React.PureComponent`.
    // You can check that the `Item` `Rendered at:` time is always the same.
    // If we do it with `React.Component`,
    // then the `Item` will be rerendered on each List update.
    return <div>{ this.props.name }, Rendered at: { Date.now() }</div>
  }
}

class List extends React.Component {
  constructor (props) {
    super(props)
    this.state = { intervalId: null }
    this.addItem = this.addItem.bind(this)
  }

  componentDidMount () {
    // Add new item on each 1 second,
    // and keep its `id`, in order to clear the interval later
    const intervalId = setInterval(this.addItem, 1000)
    this.setState({ intervalId })
  }

  componentWillUnmount () {
    // Use intervalId from the state to clear the interval
    clearInterval(this.state.intervalId)
  }

  addItem () {
    const id = Date.now()
    this.props.addItem({ id, name: `Item - ${id}` })
  }

  renderItems () {
    return this.props.items.map(item => <Item key={item.id} {...item} />)
  }

  render () {
    return <div>{this.renderItems()}</div>
  }
}

const mapDispatchToProps = { addItem }
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)

const Store = Redux.createStore(itemsReducer)
const Provider = ReactRedux.Provider

ReactDOM.render(
  <Provider store={Store}>
    <ListContainer />
  </Provider>,
  document.getElementById('container')
)

<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>

<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

Solutions:

  1. If the performance problem is caused by MyRow rerending, please find out what's the reason of rerending, because it should not happen, because of PureComponent usage.
    • You can try to simplify your reducer, in order to check / debug, is the reducer causing the problem. For instance, just add the new item to the list (without doing anything else as filtrations, slice, etc): myList: [ ...state.myList, payload ]
    • Please make sure you always pass the same key to your item component <MyRow key={list.id} data={list} />. If the key or data props are changed, then the component will be rerendered.


  1. Here are some other libraries, these stand for efficient rendering of lists. I'm sure they will give us some alternatives or insights:

    • react-virtualized - React components for efficiently rendering large lists and tabular data
    • react-infinite - A browser-ready efficient scrolling container based on UITableView


这篇关于React/Redux 渲染一个每秒更新的列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆