111 votes

Grande performance avec React

Je suis dans le processus de mise en œuvre d'une liste filtrable à Réagir. La structure de la liste, comme illustré dans l'image ci-dessous.

enter image description here

PRÉMISSE

Voici une description de la façon dont il est censé fonctionner:

  • L'état réside dans le niveau le plus élevé de la composante, l' Search de la composante.
  • L'état est décrit comme suit:
{
 visible : boolean,
 fichiers : array,
 filtré : array,
 requête : string,
 currentlySelectedIndex : integer
}
  • files est un très grand, un tableau contenant les chemins de fichiers (10000 entrées est plausible).
  • filtered est filtré tableau une fois que l'utilisateur tape au moins 2 caractères. Je sais que c'est dérivé de données et qu'un tel argument pourrait être faite à propos de les stocker dans l'état mais il est nécessaire pour
  • currentlySelectedIndex qui est l'index de l'élément sélectionné de la liste filtrée.

  • Types d'utilisateurs de plus de 2 lettres dans l' Input de la composante, le tableau est filtré et pour chaque entrée dans le tableau filtré un Result composant est rendu

  • Chaque Result composant affiche le chemin d'accès complet que partiellement correspondait à la requête, et la correspondance partielle une partie du chemin est en surbrillance. Par exemple, le DOM d'une composante de Résultat, si l'utilisateur a tapé "chier" serait quelque chose comme ceci :

    <li>this/is/a/fi<strong>le</strong>/path</li>

  • Si l'utilisateur appuie sur les touches up ou down, tandis que l' Input vise l' currentlySelectedIndex des modifications basées sur l' filtered tableau. Cela provoque l' Result de la composante qui correspond à l'index pour être sélectionné provoquant un nouveau rendu

PROBLÈME

J'ai d'abord testé avec un assez petit tableau d' files, à l'aide de la version de développement de Réagir, et tout a bien fonctionné.

Le problème est apparu lorsque j'ai eu à traiter avec un files tableau aussi grand que 10 000 entrées. En tapant 2 lettres dans l'Entrée serait de générer une grande liste de et quand j'ai appuyé sur les touches haut et bas pour naviguer, se serait très lag.

Au début, je n'ai pas la définition d'une composante de l' Result - éléments et j'ai été contente de faire la liste à la volée, sur chaque rendu de l' Search composant, comme tel:

results  = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query;

     matchIndex = file.indexOf(match);
     start = file.slice(0, matchIndex);
     end = file.slice(matchIndex + match.length);

     return (
         <li onClick={this.handleListClick}
             data-path={file}
             className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
             key={file} >
             {start}
             <span className="marked">{match}</span>
             {end}
         </li>
     );
}.bind(this));

Comme vous pouvez le dire, chaque fois que l' currentlySelectedIndex changé, il serait la cause d'une ré-rendu et la liste serait re-créé à chaque fois. J'ai pensé que, puisque j'avais mis un key de la valeur de chaque li élément Réagir permettrait d'éviter de re-rendu de tous les autres li élément qui n'a pas son className changer, mais apparemment ce n'était pas le cas.

J'ai fini par la définition d'une classe pour l' Result éléments, où il est explicitement vérifie si chaque Result élément de re-rendre basée sur le fait qu'il a été précédemment sélectionné et basée sur la saisie de l'utilisateur :

var ResultItem = React.createClass({
    shouldComponentUpdate : function(nextProps) {
        if (nextProps.match !== this.props.match) {
            return true;
        } else {
            return (nextProps.selected !== this.props.selected);
        }
    },
    render : function() {
        return (
            <li onClick={this.props.handleListClick}
                data-path={this.props.file}
                className={
                    (this.props.selected) ? "valid selected" : "valid"
                }
                key={this.props.file} >
                {this.props.children}
            </li>
        );
    }
});

Et la liste est maintenant créé comme tel:

results = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query, selected;

    matchIndex = file.indexOf(match);
    start = file.slice(0, matchIndex);
    end = file.slice(matchIndex + match.length);
    selected = (index === this.state.currentlySelected) ? true : false

    return (
        <ResultItem handleClick={this.handleListClick}
            data-path={file}
            selected={selected}
            key={file}
            match={match} >
            {start}
            <span className="marked">{match}</span>
            {end}
        </ResultItem>
    );
}.bind(this));
}

De ce fait la performance légèrement meilleure, mais c'est pas encore suffisant. C'est quand je l'ai testé sur la version de production de Réagir choses travaillé de beurre lisse, pas de lag du tout.

La ligne de FOND

Est un tel écart net entre le développement et la production de versions de Réagir normal?

Suis-je comprendre/faire quelque chose de mal quand je pense à comment Réagir gère la liste?

Mise à JOUR 14-11-2016

J'ai trouvé cette présentation de Michael Jackson, où il aborde une question très similaire à ceci: https://youtu.be/7S8v8jfLb1Q?t=26m2s

La solution est très similaire à celui proposé par AskarovBeknar de réponse, ci-dessous

Mise à JOUR 14-4-2018

Puisque c'est apparemment une question populaire et les choses ont avancé depuis que la question initiale a été posée, alors que je ne vous encourage à regarder la vidéo ci-dessus, afin d'obtenir une prise de un virtuel, mise en page, je vous encourage à utiliser le Réagir Virtualisé bibliothèque si vous ne voulez pas ré-inventer la roue.

23voto

deowk Points 2736

Comme beaucoup d'autres réponses à cette question, le principal problème réside dans le fait que le rendu tellement de nombreux éléments dans le DOM, en faisant le filtrage et la gestion des événements de touche est lente.

Vous ne faites rien d'intrinsèquement mauvais avec ce qui concerne à Réagir, qui est à l'origine du problème, mais comme la plupart des questions qui sont liées à la performance de l'INTERFACE utilisateur peut également prendre un grand pourcentage de la blâmer.

Si votre INTERFACE utilisateur n'est pas conçu avec l'efficacité à l'esprit même des outils comme Réagir qui sont conçus pour être performant en souffrira.

Le filtrage de l'ensemble de résultats est un excellent point de départ, comme mentionné par @Koen

J'ai joué avec l'idée un peu et a créé une application exemple illustrant comment je pourrais commencer à s'attaquer à ce genre de problème.

Ce n'est nullement production ready code mais il ne illustrent le concept de manière adéquate et peut être modifié pour être plus robuste, n'hésitez pas à jeter un coup d'oeil au code, je l'espère, à tout le moins, cela vous donne quelques idées...;)

https://github.com/deowk/react-large-list-example

enter image description here

19voto

Resonance Points 578

Mon expérience avec une très similaires problème est que réagir souffre réellement si il y a plus de 100-200 ou si les composants dans les DOM à la fois. Même si vous êtes super attention (par la mise en place de tous vos clés et/ou de la mise en œuvre d'un shouldComponentUpdate ") pour modifier uniquement un ou deux composants sur un nouveau rendu, vous allez encore être dans un monde de souffrance.

La partie lente de réagir pour le moment, c'est quand il compare la différence entre le virtuel DOM et le réel DOM. Si vous avez des milliers de composants, mais seulement de mettre à jour un couple, il n'a pas d'importance, réagir a encore une énorme opération de différence à faire entre les Dom.

Quand j'écris des pages maintenant, j'essaie de conception afin de minimiser le nombre de composants, une façon de le faire lors du rendu de grandes listes de composants de... eh bien... pas rendre de grandes listes de composants.

Ce que je veux dire, c'est: que rendre les composants que vous pouvez actuellement voir, de rendre plus que vous le faites défiler vers le bas, vous êtes l'utilisateur n'est pas susceptible de faire défiler vers le bas à travers des milliers de composants de toute façon.... J'ai de l'espoir.

Une grande bibliothèque pour le faire, c'est:

https://www.npmjs.com/package/react-infinite-scroll

Avec un grand comment faire ici:

http://www.reactexamples.com/react-infinite-scroll/

J'ai peur qu'elle ne veut pas supprimer des composants qui sont hors de la partie supérieure de la page, si vous faites défiler pour assez longtemps, vous êtes les problèmes de performances commencent à ressurgir.

Je sais que ce n'est pas une bonne pratique afin de fournir un lien comme réponse, mais les exemples qu'ils fournissent allons vous expliquer comment utiliser cette bibliothèque bien mieux que je peux. J'espère avoir expliqué pourquoi les grandes listes sont mauvais, mais également un travail autour de.

11voto

Pcriulan Points 249

Tout d'abord, la différence entre le développement et la production de la version de Réagir est énorme parce que dans la production il y a beaucoup de contourné les contrôles d'intégrité (comme prop types de vérification).

Alors, je pense que vous devriez reconsidérer l'aide de Redux, car il serait extrêmement utile pour ce que vous avez besoin (ou tout type de flux de mise en œuvre). Vous devriez jeter un oeil à cette présentation : la Grande Liste de Haute Performance Réagir Et Redux.

Mais avant de plonger dans redux, vous avez besoin à fait quelques ajustements à votre Réagissent code par fractionnement des composants en composants plus petits, parce qu' shouldComponentUpdate sera totalement ignorer le rendu des enfants, donc c'est un gain énorme.

Lorsque vous avez plus de composants granulaires, vous pouvez gérer l'état avec redux et réagir-redux afin de mieux organiser les flux de données.

J'ai récemment été confronté à un problème similaire quand j'ai besoin de rendre un millier de lignes et d'être en mesure de modifier chaque ligne par la modification de son contenu. Cette mini application affiche une liste des concerts avec des doublons potentiels des concerts et j'ai besoin de choisir pour chaque risque de double, si je veux marquer le potentiel de reproduire comme un concert original (pas une copie) en cochant la case, et, si nécessaire, modifiez le nom du concert. Si je ne fais rien pour un potentiel particulier l'article en double, il sera considéré comme double et seront supprimés.

Voici à quoi il ressemble :

enter image description here

En gros, il existe 4 principaux composants (il n'y a qu'une seule ligne ici, mais c'est pour le bien de l'exemple) :

enter image description here

Voici le code complet (travail CodePen : Liste Énorme avec Réagissent & Redux) à l'aide de redux, réagissent-redux, immuable, sélectionnez à nouveau et recomposer:

const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ })

const types = {
    CONCERTS_DEDUP_NAME_CHANGED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED',
    CONCERTS_DEDUP_CONCERT_TOGGLED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED',
};

const changeName = (pk, name) => ({
    type: types.CONCERTS_DEDUP_NAME_CHANGED,
    pk,
    name
});

const toggleConcert = (pk, toggled) => ({
    type: types.CONCERTS_DEDUP_CONCERT_TOGGLED,
    pk,
    toggled
});


const reducer = (state = initialState, action = {}) => {
    switch (action.type) {
        case types.CONCERTS_DEDUP_NAME_CHANGED:
            return state
                .updateIn(['names', String(action.pk)], () => action.name)
                .set('_state', 'not_saved');
        case types.CONCERTS_DEDUP_CONCERT_TOGGLED:
            return state
                .updateIn(['concerts', String(action.pk)], () => action.toggled)
                .set('_state', 'not_saved');
        default:
            return state;
    }
};

/* configureStore */
const store = Redux.createStore(
    reducer,
    initialState
);

/* SELECTORS */

const getDuplicatesGroups = (state) => state.get('duplicatesGroups');

const getDuplicateGroup = (state, name) => state.getIn(['duplicatesGroups', name]);

const getConcerts = (state) => state.get('concerts');

const getNames = (state) => state.get('names');

const getConcertName = (state, pk) => getNames(state).get(String(pk));

const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk));

const getGroupNames = reselect.createSelector(
    getDuplicatesGroups,
    (duplicates) => duplicates.flip().toList()
);

const makeGetConcertName = () => reselect.createSelector(
    getConcertName,
    (name) => name
);

const makeIsConcertOriginal = () => reselect.createSelector(
    isConcertOriginal,
    (original) => original
);

const makeGetDuplicateGroup = () => reselect.createSelector(
    getDuplicateGroup,
    (duplicates) => duplicates
);



/* COMPONENTS */

const DuplicatessTableRow = Recompose.onlyUpdateForKeys(['name'])(({ name }) => {
    return (
        <tr>
            <td>{name}</td>
            <DuplicatesRowColumn name={name}/>
        </tr>
    )
});

const PureToggle = Recompose.onlyUpdateForKeys(['toggled'])(({ toggled, ...otherProps }) => (
    <input type="checkbox" defaultChecked={toggled} {...otherProps}/>
));


/* CONTAINERS */

let DuplicatesTable = ({ groups }) => {

    return (
        <div>
            <table className="pure-table pure-table-bordered">
                <thead>
                    <tr>
                        <th>{'Concert'}</th>
                        <th>{'Duplicates'}</th>
                    </tr>
                </thead>
                <tbody>
                    {groups.map(name => (
                        <DuplicatesTableRow key={name} name={name} />
                    ))}
                </tbody>
            </table>
        </div>
    )

};

DuplicatesTable.propTypes = {
    groups: React.PropTypes.instanceOf(Immutable.List),
};

DuplicatesTable = ReactRedux.connect(
    (state) => ({
        groups: getGroupNames(state),
    })
)(DuplicatesTable);


let DuplicatesRowColumn = ({ duplicates }) => (
    <td>
        <ul>
            {duplicates.map(d => (
                <DuplicateItem
                    key={d}
                    pk={d}/>
            ))}
        </ul>
    </td>
);

DuplicatessRowColumn.propTypes = {
    duplicates: React.PropTypes.arrayOf(
        React.PropTypes.string
    )
};

const makeMapStateToProps1 = (_, { name }) => {
    const getDuplicateGroup = makeGetDuplicateGroup();
    return (state) => ({
        duplicates: getDuplicateGroup(state, name)
    });
};

DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn);


let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => {
    return (
        <li>
            <table>
                <tbody>
                    <tr>
                        <td>{ toggled ? <input type="text" value={name} onChange={(e) => onNameChange(pk, e.target.value)}/> : name }</td>
                        <td>
                            <PureToggle toggled={toggled} onChange={(e) => onToggle(pk, e.target.checked)}/>
                        </td>
                    </tr>
                </tbody>
            </table>
        </li>
    )
}

const makeMapStateToProps2 = (_, { pk }) => {
    const getConcertName = makeGetConcertName();
    const isConcertOriginal = makeIsConcertOriginal();

    return (state) => ({
        name: getConcertName(state, pk),
        toggled: isConcertOriginal(state, pk)
    });
};

DuplicateItem = ReactRedux.connect(
    makeMapStateToProps2,
    (dispatch) => ({
        onNameChange(pk, name) {
            dispatch(changeName(pk, name));
        },
        onToggle(pk, toggled) {
            dispatch(toggleConcert(pk, toggled));
        }
    })
)(DuplicateItem);


const App = () => (
    <div style={{ maxWidth: '1200px', margin: 'auto' }}>
        <DuplicatesTable />
    </div>
)

ReactDOM.render(
    <ReactRedux.Provider store={store}>
        <App/>
    </ReactRedux.Provider>,
    document.getElementById('app')
);

Les enseignements appris en faisant cette mini application lorsque vous travaillez avec d'énormes dataset

  • Réagir composants fonctionnent mieux quand ils sont petits
  • Sélectionnez à nouveau devenir très utile pour éviter de recalcul et de garder le même objet de référence (lors de l'utilisation de immutable.js) les mêmes arguments.
  • Créer connected composant par composant qui sont les plus proches des données dont ils ont besoin pour éviter d'avoir composante de la transmission des accessoires qu'ils n'utilisent pas
  • L'utilisation de tissu de la fonction à créer mapDispatchToProps quand vous en avez besoin seulement l'initiale prop donné en ownProps est nécessaire pour éviter d'inutiles re-rendu
  • Réagir et redux définitivement rock ensemble !

5voto

Koen. Points 3570

Comme je l'ai mentionné dans mon commentaire, je doute que les utilisateurs ont besoin de tous ces 10000 résultats dans le navigateur à la fois.

Si vous à la page dans les résultats, et toujours juste vous montrer une liste de 10 résultats.

J'ai créé un exemple à l'aide de cette technique, sans l'aide de n'importe quelle autre bibliothèque comme Redux. Actuellement, seule la navigation au clavier, mais pourrait facilement être étendu à travailler sur le défilement.

L'exemple est constitué de 3 composants, le conteneur de l'application, un composant de recherche et une liste de composants. Presque toute la logique a été déplacé vers le composant conteneur.

L'essentiel réside dans le maintien de la trace de l' start et de la selected résultat, et de déplacement des personnes sur le clavier de l'interaction.

nextResult: function() {
  var selected = this.state.selected + 1
  var start = this.state.start
  if(selected >= start + this.props.limit) {
    ++start
  }
  if(selected + start < this.state.results.length) {
    this.setState({selected: selected, start: start})
  }
},

prevResult: function() {
  var selected = this.state.selected - 1
  var start = this.state.start
  if(selected < start) {
    --start
  }
  if(selected + start >= 0) {
    this.setState({selected: selected, start: start})
  }
},

Tout simplement le passage de tous les fichiers au travers d'un filtre:

updateResults: function() {
  var results = this.props.files.filter(function(file){
    return file.file.indexOf(this.state.query) > -1
  }, this)

  this.setState({
    results: results
  });
},

Et le découpage de résultats sur la base start et limit dans la render méthode:

render: function() {
  var files = this.state.results.slice(this.state.start, this.state.start + this.props.limit)
  return (
    <div>
      <Search onSearch={this.onSearch} onKeyDown={this.onKeyDown} />
      <List files={files} selected={this.state.selected - this.state.start} />
    </div>
  )
}

Violon contenant une gamme complète de travail exemple: https://jsfiddle.net/koenpunt/69z2wepo/47841/

4voto

AskarovBeknar Points 104
  1. Réagir dans la version de développement de contrôles pour proptypes de chaque composant afin de faciliter le processus de développement, tandis que dans la production, il est omis.

  2. Filtrage de la liste des chaînes est très onéreuse pour chaque keyup. il peut causer des problèmes de performance en raison de mono-thread nature de JavaScript. La Solution pourrait être d'utiliser antirebond méthode pour retarder l'exécution de votre fonction de filtre jusqu'à ce que le délai est expiré.

  3. Un autre problème est peut-être l'énorme liste elle-même. Vous pouvez créer virtuel disposition et la réutilisation des postes créés juste le remplacement de données. En gros, vous créez de défilement composant conteneur avec hauteur fixe, à l'intérieur de laquelle vous allez placer conteneur de liste. La hauteur de la liste contenant doit être réglée manuellement (itemHeight * numberOfItems) en fonction de la longueur de la liste visible, d'avoir une barre de défilement de travail. Créez ensuite un peu de point de composants de sorte qu'ils vont remplir les conteneurs défilants hauteur et peut-être ajouter de l'un ou deux d'imiter continue de liste effet. faire de position absolue et sur de défilement, il suffit de bouger de leur position, de sorte qu'il va imiter liste continue(je pense que vous trouverez comment le mettre en œuvre:)

  4. Une chose est écrit à DOM est également coûteux, surtout si vous le faites mal. Vous pouvez utiliser de la toile pour l'affichage des listes et de créer lisse expérience sur parchemin. La caisse réagir la toile composants. J'ai entendu dire qu'ils ont déjà fait un peu de travail sur les Listes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X