【问题标题】:React/Redux rendering a list that's updating every secondReact/Redux 渲染一个每秒更新的列表
【发布时间】:2018-10-30 00:35:49
【问题描述】:

我有一个 react 组件,每秒从 redux 存储接收道具。新状态有一个与上一个数组不同的数组。具体来说,每秒钟就有一个元素被添加到数组中。例如: 在一种状态下,数组是:

[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 大量使用样式化组件和其他昂贵的操作。 这导致响应在状态更新时每秒重新渲染整个列表。如果更新时间少于 1 秒,例如每秒 4 次更新,情况会变得更糟。在这种情况下,react 应用程序只会崩溃。

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

谢谢

【问题讨论】:

  • 关于性能优化的非常好的文章你可以找到here
  • 看起来您正确使用了键,这可以让反应了解哪些元素是新的,因此 vDOM 不应重新渲染所有项目,而是根据需要更新 reactjs.org/docs/lists-and-keys.html
  • @Reza 你可以看看我的详细回答,如果有不清楚的地方,请告诉我。

标签: javascript reactjs redux react-redux


【解决方案1】:

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

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

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

React's docs 是这样说的:

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

PureComponent的代码示例:

您可以查看我为您制作的代码示例。

你会看到Item组件总是只渲染1次,因为我们使用React.PureComponent。为了证明我的说法,每次渲染 Item 时,我都会添加当前渲染时间。从例子中你会看到ItemRendered 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使用。
    • 您可以尝试简化您的reducer,以便检查/调试,是导致问题的reducer。例如,只需将新项目添加到列表中(不做任何其他事情,如过滤、切片等):myList: [ ...state.myList, payload ]
    • 请确保始终将相同的 key 传递给您的项目组件 &lt;MyRow key={list.id} data={list} /&gt;。如果 keydata 属性发生变化,那么组件将被重新渲染。

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

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

【讨论】:

  • 谢谢。我错误地假设 PureComponent 与无状态组件相同。他们不是。 PureComponent 有额外的逻辑,它本质上包含更严格的shouldComponentUpdate 版本。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    问题确实存在于reducer中。

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

    【讨论】:

    • 如果没有PureComponent,列表中的每个项目都将在添加新项目时重新呈现。你可以玩我上面的例子。所以PureComponent是必需的。
    • 是的。我的错。编辑了答案。谢谢
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      看起来您每次都在 reducer 中创建一个新数组,其中所有数组索引都需要重新计算。您是否尝试过将新节点追加到列表的末尾而不是前置?

      【讨论】:

      • 这不是要改变状态吗?此外,数组键是 DB 中的固定值。
      • 它改变了 redux 状态,是的,但我不认为状态突变是导致你的问题的原因。我怀疑在你的 reducer 中,当接收到动作并计算 myList 时,你强制 React 重新渲染所有内容,因为数组中的第一个也是所有项目都发生了变化。如果你要附加 payload,而不是附加 payload,React 可能能够更好地处理你的新数据。
      • 原来的问题是处理redux状态的正确方法。它只是做一个浅拷贝(即使这些值是原语,而不是引用,所以无论如何它们都会传递===
      猜你喜欢
      • 2020-11-15
      • 1970-01-01
      • 1970-01-01
      • 2018-10-03
      • 1970-01-01
      • 1970-01-01
      • 2020-11-27
      • 2018-03-24
      • 1970-01-01
      相关资源
      最近更新 更多