React/Redux 渲染一个每秒更新的列表

IT技术 javascript reactjs redux react-redux
2021-05-01 10:14:24

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

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

下一个状态

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

我的减速机:

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

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

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

在 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;

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

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

谢谢

4个回答

您使用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') 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>

解决方案:

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

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


PureComponent 将浅层比较 props 和 state。所以我的猜测是这些项目在某种程度上是比之前传递的props新的对象,因此重新渲染。

一般来说,我建议只在纯组件中传递原始值:

class MyList extends Component {
    render() {
        return (
            <div>
                {this.props.myList.map((item, index) => (
                    <MyRow key={item.id} id={item.id} name={data.name} />
                    //or it's alternative 
                    <MyRow key={item.id} {...item} />
                ))}
            </div>
        );
    }
}

//...

class MyRow extends PureComponent {
    render() {
        const {id, name} = this.props;
        return (
            <div>
                {id} - {name}
            </div>
        );
    }
}

问题确实存在于减速器中。

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

使用 slice(0,-1) 实现的逻辑是什么?

它是这里的罪魁祸首。

从你的问题我了解到 [1,2,3] 之后的下一个状态将是 [1,2,3,4]。

但是您的代码将给出 [4,1,2],然后是 [5,4,1],然后是 [6,5,4]。

现在状态中的所有元素都是新的,而不是初始状态。看到状态不仅仅是被附加,它正在完全改变。

请查看是否通过避免切片获得了所需的结果。

 myList: [ payload, ...state.myList.filter(item => payload.id !== item.id)]

对此有一个非常简单的解决方案。React VDOM 只是一种差异算法。您的 JSX 唯一缺少的是称为key 的东西,它就像一个 id,diffing 算法使用并呈现特定元素。只需使用 KEY 标记元素,例如https://reactjs.org/docs/lists-and-keys.html#keys

<li key={number.toString()}>
    {number}   </li>