React/Redux рендеринг списка, который обновляется каждую секунду

У меня есть компонент реакции, который получает реквизит из магазина 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) ]
}

Теперь, в моем реагирующем компоненте, я подписываюсь на это состояние и для каждого изменения список перерисовывается.

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 обновления в секунду. В этом случае приложение-ответ просто выйдет из строя.

Есть ли способ добавить только добавленный элемент в список, а не повторно отобразить весь список?

Спасибо

Ответ 1

Вы используете PureComponent, которые делают неглубокое сравнение, тогда ваш компонент MyRow не должен быть перезагружен при добавлении каждого нового элемента (пожалуйста, следуйте приведенному ниже примеру кода).

Есть ли способ добавить только добавленный элемент в список, а не повторно отобразить весь список?

Согласно вашему вопросу - Да, использование PureComponent должно отображать только один раз новый элемент:

Вот что говорит React docs:

Если ваша функция React components render() отображает тот же результат при одинаковых реквизитах и состоянии, вы можете использовать React.PureComponent для повышения производительности в некоторых случаях.

Пример кода PureComponent:

Вы можете проверить образец кода, который я сделал для вас.

Вы увидите, что компонент Item всегда отображается только 1 раз, потому что мы используем React.PureComponent. Чтобы доказать мое утверждение, каждый раз, когда Item отображается, я добавил текущее время рендеринга. В примере вы увидите, что Item Rendered at: time всегда один и тот же, потому что он отображается только 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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/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 contents will be replaced with your component. -->
</div>

Ответ 2

Проблема действительно существует в редукторе.

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

Какова логика, реализованная с использованием среза (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)]

Ответ 3

PureComponent будет неточно сравнивать реквизит и состояние. Поэтому я предполагаю, что элементы - это как-то новые объекты, чем предыдущие пропущенные реквизиты, таким образом, рендеринг.

В общем, я бы советовал только передавать примитивные значения в чистых компонентах:

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 alternative 
                    <MyRow key={item.id} {...item} />
                ))}
            </div>
        );
    }
}

//...

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

Ответ 4

Для этого есть довольно простое решение. React VDOM - это просто отличный алгоритм. Единственный фрагмент, отсутствующий в вашем JSX, - это что-то, называемое ключом, которое подобно id, которое использует различный алгоритм и отображает конкретный элемент. Просто пометьте элемент с помощью KEY что-то вроде этого https://reactjs.org/docs/lists-and-keys.html#keys

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

Ответ 5

похоже, вы каждый раз создаете новый массив в редукторе, в котором все индексы массива должны быть пересчитаны. вы пробовали добавить новый узел в конец списка вместо добавления?