Disons que j'ai une liste de 1000 articles. Et je l'ai rendu avec React, comme ceci:
class Parent extends React.Component {
render() {
// this.state.list is a list of 1000 items
return <List list={this.state.list} />;
}
}
class List extends React.Component {
render() {
// here we're looping through this.props.list and creating 1000 new Items
var list = this.props.list.map(item => {
return <Item key={item.key} item={item} />;
});
return <div>{list}</div>;
}
}
class Item extends React.Component {
shouldComponentUpdate() {
// here I comparing old state/props with new
}
render() {
// some rendering here...
}
}
Avec une liste relativement longue, map () prend environ 10 à 20 ms et je peux remarquer un léger décalage dans l’interface.
Puis-je empêcher la recréation de 1000 objets React chaque fois que je n'ai besoin que d'en mettre à jour un?
Vous pouvez le faire en utilisant n'importe quelle bibliothèque de gestion d'état, de sorte que votre Parent
ne garde pas trace de this.state.list
=> votre List
ne soit restitué que lorsque la nouvelle Item
est ajoutée. Et l'individu Item
sera rendu de nouveau lorsqu'il sera mis à jour.
Disons que vous utilisez redux
.
Votre code deviendra quelque chose comme ceci:
// Parent.js
class Parent extends React.Component {
render() {
return <List />;
}
}
// List.js
class List extends React.Component {
render() {
var list = this.props.list.map(item => {
return <Item key={item.key} uniqueKey={item.key} />;
});
return <div>{list}</div>;
}
}
const mapStateToProps = (state) => ({
list: getList(state)
});
export default connect(mapStateToProps)(List);
// Item.js
class Item extends React.Component {
shouldComponentUpdate() {
}
render() {
}
}
const mapStateToProps = (state, ownProps) => ({
item: getItemByKey(ownProps.uniqueKey)
});
export default connect(mapStateToProps)(Item);
Bien entendu, vous devez implémenter le réducteur et les deux sélecteurs getList
et getItemByKey
.
Avec cela, vous aurez List
re-rendu déclencheur si de nouveaux éléments sont ajoutés, ou si vous changez item.key
(ce que vous ne devriez pas)
MODIFIER:
Mes suggestions initiales ne concernaient que les améliorations de l'efficacité des listes Rendues} et n'abordaient pas la question de la limitation du re-rendu. des composants à la suite du changement de liste.
Voir la réponse de @ xiaofan2406 pour une solution claire à la question initiale.
Les bibliothèques qui aident à rendre le rendu de longues listes plus efficace et plus facile}:
Lorsque vous modifiez vos données, l’opération par défaut est de restituer tous les composants enfants et de créer virtual dom pour déterminer le composant devant être rendu.
Donc, si nous pouvons laisser réagir, il ne reste plus qu’un élément à réémettre. Cela peut faire gagner du temps.
Vous pouvez utiliser shouldComponentsUpdate
dans votre composant de liste.
Si, dans cette fonction, renvoie false, réagit ne créera pas de comportement virtuel à juger.
Je suppose que vos données sont comme ceci [{name: 'name_1'}, {name: 'name_2'}]
class Item extends React.Component {
// you need judge if props and state have been changed, if not
// execute return false;
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.name === this.props.name) return false;
return true;
}
render() {
return (
<li>{this.props.name}</li>
)
}
}
En tant que réaction, restituez ce qui a été modifié comme composant. Donc, si vous ne modifiez que les données d'un élément, les autres ne seront pas rendus.
Il y a quelques choses que vous pouvez faire:
NODE_ENV=production npm run build
ou similaire. ReactJS effectue de nombreuses vérifications de sécurité lorsque NODE_ENV
n'est pas défini en production, comme les vérifications PropType. Si vous les désactivez, vous obtiendrez une amélioration> 2x de la performance pour le rendu React. Elle est essentielle pour la construction de votre production (même si elle est désactivée pendant le développement - ces contrôles de sécurité aident à prévenir les bugs!). Vous constaterez peut-être que c'est suffisant pour le nombre d'éléments à prendre en charge.firstRendered
lastRendered
à votre état List (c’est bien le premier _/inclusive et le dernier exclusif). Dans List.render
, restituez un filler vide div
(ou tr
si applicable) de la hauteur correcte (firstRendered * itemHeight
), puis de votre plage d'éléments rendus [firstRendered, lastRendered)
, puis d'un autre div de remplissage avec la hauteur restante ((totalItems - lastRendered) * itemHeight
). Assurez-vous de donner à vos fillers et à vos objets des clés fixes. Il vous suffit ensuite de gérer onScroll sur le div à défilement et de déterminer quelle est la plage correcte à restituer (en règle générale, vous souhaitez afficher un chevauchement décent en haut et en bas, vous devez également déclencher un setState pour modifier la plage lorsque vous vous approchez du bord). Une alternative plus folle consiste à rendre et à mettre en œuvre votre propre barre de défilement (ce qui correspond à la FixedDataTable de Facebook - https://facebook.github.io/fixed-data-table/ ). Il existe de nombreux exemples de cette approche générale ici https://react.rocks/tag/InfiniteScroll Une façon d'éviter de parcourir la liste des composants lors de chaque rendu consiste à le faire en dehors de la fonction de rendu et de l'enregistrer dans une variable.
class Item extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item;
}
render() {
return <li>{this.props.item}</li>;
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.items = [];
this.update = this.update.bind(this);
}
componentWillMount() {
this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
}
update(index) {
this.items[index] = <Item key={index} item={'item changed'} />
this.forceUpdate();
}
render() {
return <div>
<button onClick={() => { this.update(199); }}>Update</button>
<ul>{this.items}</ul>
</div>
}
}