web-dev-qa-db-fra.com

React / Redux rendant une liste qui se met à jour chaque seconde

J'ai un composant React qui reçoit les accessoires du magasin redux à chaque seconde. Le nouvel état a un tableau différent du dernier tableau. Pour être précis, chaque seconde un élément est ajouté au tableau. Par exemple: dans un état, le tableau est:

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

l'état suivant

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

Mon réducteur:

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

Maintenant, dans mon composant React, je souscris à cet état et pour chaque modification, la liste est restituée.

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

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

Maintenant, mon problème est: il est coûteux pour moi de restituer chaque élément qui a déjà été rendu. Le MyRow utilise fortement des composants de style et d'autres opérations coûteuses. Cela provoque React pour restituer la liste entière toutes les secondes lorsque l'état est mis à jour. Cela devient pire si les mises à jour arrivent en moins de 1 seconde, comme 4 mises à jour par seconde. L'application react se bloque simplement dans ce cas.

Existe-t-il un moyen d'ajouter uniquement l'élément nouvellement ajouté à la liste et de ne pas restituer la liste entière?

Merci

17
Reza

Vous utilisez PureComponent , qui fait une comparaison superficielle, puis votre composant MyRow ne doit pas être rendu sur chaque nouvel élément ajouté ( Veuillez suivre mon exemple de code ci-dessous).

Existe-t-il un moyen d'ajouter uniquement l'élément nouvellement ajouté à la liste et de ne pas restituer la liste entière?

Selon votre question - Oui, l'utilisation de PureComponent ne devrait rendre qu'une seule fois le nouvel élément :

Voici ce que les documents de React disent:

Si la fonction render () de votre composant React rend le même résultat avec les mêmes accessoires et le même état, vous pouvez utiliser React.PureComponent pour améliorer les performances dans certains cas.

Exemple de code de PureComponent:

Vous pouvez consulter l'exemple de code que j'ai fait pour vous.

Vous verrez que le composant Item est toujours rendu une seule fois, car nous utilisons React.PureComponent. Pour prouver ma déclaration, chaque fois que le Item est rendu, j'ai ajouté l'heure actuelle du rendu. Dans l'exemple, vous verrez que le ItemRendered at: le temps est toujours le même, car il n'est rendu qu'une seule fois .

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. Si le problème de performances est causé par la re-tendance de MyRow, veuillez découvrir quelle est la raison de la re-tendance, car cela ne devrait pas se produire, en raison de l'utilisation de PureComponent.
    • Vous pouvez essayer de simplifier votre réducteur , afin de vérifier/déboguer, est le réducteur à l'origine du problème. Par exemple, ajoutez simplement le nouvel élément à la liste (sans rien faire d'autre comme filtrations, tranche, etc.): myList: [ ...state.myList, payload ]
    • Veuillez vous assurer de toujours transmettre la même chose key à votre composant d'article <MyRow key={list.id} data={list} />. Si les accessoires key ou data sont modifiés, le composant sera à nouveau rendu.

  1. Voici quelques autres bibliothèques, elles représentent un rendu efficace des listes. Je suis sûr qu'ils nous donneront des alternatives ou des idées:

    • react-virtualized - Réagissez aux composants pour rendre efficacement de grandes listes et des données tabulaires
    • react-infinite - Un conteneur de défilement efficace prêt pour le navigateur basé sur UITableView

10
Jordan Enev

PureComponent comparera superficiellement les accessoires et l'état. Donc, je suppose que les éléments sont en quelque sorte de nouveaux objets que les précédents accessoires passés, donc le rendu.

Je conseillerais, en général, de ne passer que des valeurs primitives dans des composants purs:

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

Il existe une solution assez simple pour cela. React VDOM est juste un algorithme différent. Le seul élément manquant avec votre JSX est quelque chose appelé clé qui est comme un id que l'algo différent utilise et rend l'élément particulier. Il suffit de marquer l'élément avec une clé quelque chose comme ça https://reactjs.org/docs/lists-and-keys.html#keys

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

Le problème existe vraiment dans le réducteur.

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

Quelle est la logique implémentée à l'aide de la tranche (0, -1)?

C'est le coupable ici.

D'après votre question, j'ai compris que le prochain état après [1,2,3] sera [1,2,3,4].

Mais votre code donnera [4,1,2], puis [5,4,1] puis [6,5,4].

Maintenant, tous les éléments de l'état sont nouveaux, pas à l'état initial. Voir l'état n'est pas seulement ajouté, il change complètement.

Veuillez voir si vous obtenez le résultat souhaité en évitant la découpe.

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