Comment fonctionne la comparaison superficielle dans React

91

Dans cette documentation de React, il est dit que

shallowCompare effectue une vérification d'égalité superficielle sur les objets props et nextProps actuels ainsi que sur les objets état actuel et nextState.

La chose que je ne peux pas comprendre est que si elle compare les objets de manière superficielle, la méthode shouldComponentUpdate retournera toujours true, car

Nous ne devons pas muter les États.

et si nous ne modifions pas les états, la comparaison retournera toujours false et la mise à jour shouldComponent retournera toujours true. Je ne sais pas comment cela fonctionne et comment allons-nous passer outre cela pour améliorer les performances.

Ajay Gaur
la source

Réponses:

125

La comparaison superficielle vérifie l'égalité. Lors de la comparaison de valeurs scalaires (nombres, chaînes), il compare leurs valeurs. Lors de la comparaison d'objets, il ne compare pas leurs attributs - seules leurs références sont comparées (par exemple "pointent-ils vers le même objet?).

Considérons la forme suivante de l' userobjet

user = {
  name: "John",
  surname: "Doe"
}

Exemple 1:

const user = this.state.user;
user.name = "Jane";

console.log(user === this.state.user); // true

Notez que vous avez changé le nom des utilisateurs. Même avec ce changement, les objets sont égaux. Leurs références sont exactement les mêmes.

Exemple 2:

const user = clone(this.state.user);
console.log(user === this.state.user); // false

Maintenant, sans aucune modification des propriétés des objets, ils sont complètement différents. En clonant l'objet d'origine, vous créez une nouvelle copie, avec une référence différente.

La fonction de clonage peut ressembler à ceci (syntaxe ES6)

const clone = obj => Object.assign({}, ...obj);

La comparaison superficielle est un moyen efficace de détecter les changements. Il s'attend à ce que vous ne mutiez pas les données.

Andreyco
la source
Donc, si nous écrivons du code, si nous avons des valeurs scalaires, devrions-nous les muter car si nous les clonons, le contrôle d'égalité retournera faux?
Ajay Gaur
29
@AjayGaur Bien que cette réponse puisse vous aider à comprendre l'égalité stricte (===) en JavaScript, elle ne vous dit rien sur la fonction shallowCompare () dans React (je suppose que le répondant a mal compris votre question). Ce que fait shallowCompare () est en fait dans le document que vous avez fourni: itérer sur les clés des objets comparés et renvoyer true lorsque les valeurs d'une clé dans chaque objet ne sont pas strictement égales. Si vous ne comprenez toujours pas cette fonction et pourquoi vous ne devriez pas muter l'état, je peux vous écrire une réponse.
sunquan
7
Ce n'est pas vrai. regarde ça. github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/…
Bright Lee
5
Cette réponse décrit la différence entre les opérateurs d'égalité (==) et d'égalité stricte (===) dans JS. La question concerne la comparaison superficielle qui, dans React, est implémentée en vérifiant l'égalité entre tous les accessoires de deux objets.
Mateo Hrastnik
@sunquan pouvez-vous s'il vous plaît écrire une réponse à ce sujet?
Ajay Gaur
29

La comparaison superficielle se produit lorsque les propriétés des objets comparés sont effectuées en utilisant "===" ou une égalité stricte et ne conduisent pas de comparaisons plus profondément dans les propriétés. par exemple

// a simple implementation of the shallowCompare.
// only compares the first level properties and hence shallow.
// state updates(theoretically) if this function returns true.
function shallowCompare(newObj, prevObj){
    for (key in newObj){
        if(newObj[key] !== prevObj[key]) return true;
    }
    return false;
}
// 
var game_item = {
    game: "football",
    first_world_cup: "1930",
    teams: {
         North_America: 1,
         South_America: 4,
         Europe: 8 
    }
}
// Case 1:
// if this be the object passed to setState
var updated_game_item1 = {
    game: "football",
    first_world_cup: "1930",
    teams: {
         North_America: 1,
         South_America: 4,
         Europe: 8 
    }
}
shallowCompare(updated_game_item1, game_item); // true - meaning the state
                                               // will update.

Bien que les deux objets semblent être identiques, ce game_item.teamsn'est pas la même référence que updated_game_item.teams. Pour que 2 objets soient identiques, ils doivent pointer vers le même objet. Il en résulte que l'état évalué doit être mis à jour

// Case 2:
// if this be the object passed to setState
var updated_game_item2 = {
    game: "football",
    first_world_cup: "1930",
    teams: game_item.teams
}
shallowCompare(updated_game_item2, game_item); // false - meaning the state
                                               // will not update.

Cette fois, toutes les propriétés renvoient true pour la comparaison stricte, car la propriété teams dans le nouvel et l'ancien objet pointe vers le même objet.

// Case 3:
// if this be the object passed to setState
var updated_game_item3 = {
    first_world_cup: 1930
}
shallowCompare(updated_game_item3, game_item); // true - will update

La updated_game_item3.first_world_cuppropriété échoue à l'évaluation stricte car 1930 est un nombre tandis que game_item.first_world_cupest une chaîne. Si la comparaison avait été lâche (==), cela aurait passé. Néanmoins, cela entraînera également une mise à jour de l'état.

Notes complémentaires:

  1. Faire une comparaison approfondie est inutile car cela affecterait considérablement les performances si l'objet d'état était profondément imbriqué. Mais si ce n'est pas trop imbriqué et que vous avez toujours besoin d'une comparaison approfondie, implémentez-le dans shouldComponentUpdate et vérifiez si cela suffit.
  2. Vous pouvez certainement muter directement l'objet d'état, mais l'état des composants ne serait pas affecté, car il se trouve dans le flux de méthode setState qui réagit implémente les hooks du cycle de mise à jour des composants. Si vous mettez à jour l'objet d'état directement pour éviter délibérément les hooks de cycle de vie des composants, vous devriez probablement utiliser une simple variable ou un objet pour stocker les données et non l'objet d'état.
supi
la source
Cela ne signifie-t-il pas que si je passe un objet via des accessoires ou que je compare l'état à l'état suivant, le composant ne sera jamais rendu car même si les propriétés de cet objet ont changé, il pointera toujours vers le même objet, ce qui entraînera faux, donc, pas de re-rendu?
javascript
@javascripting - c'est pourquoi vous clonerez (en utilisant par exemple Object.assign ()) vos objets lorsqu'ils changent au lieu de les muter afin que React sache quand la référence change et que le composant doit être mis à jour.
Mac_W
Si prevObjcontient une clé qui newObjn'en a pas, la comparaison échouera.
mzedeler
@mzedeler - ce ne sera pas le cas car le "for in" itère sur newObj et non sur prevObj. essayez d'exécuter le code tel quel dans la console développeur du navigateur. De plus, ne prenez pas cette implémentation de la comparaison peu profonde trop au sérieux, c'est juste pour démontrer le concept
supi
qu'en est-il des tableaux?
Juan De la Cruz
24

La comparaison superficielle fonctionne en vérifiant si deux valeurs sont égales dans le cas de types primitifs comme une chaîne, des nombres et dans le cas d'un objet, il vérifie simplement la référence . Donc, si vous comparez peu profondément un objet imbriqué profond, il vérifiera simplement la référence et non les valeurs à l'intérieur de cet objet.

akhil choudhary
la source
10

Il existe également une explication héritée de la comparaison superficielle dans React:

shallowCompare effectue une vérification d'égalité superficielle sur les objets props et nextProps actuels ainsi que sur les objets état actuel et nextState.

Il le fait en itérant sur les clés des objets comparés et en retournant true lorsque les valeurs d'une clé dans chaque objet ne sont pas strictement égales.

UPD : La documentation actuelle dit à propos de la comparaison superficielle:

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

Le shouldComponentUpdate () de React.PureComponent ne compare que superficiellement les objets. Si ceux-ci contiennent des structures de données complexes, cela peut produire des faux négatifs pour des différences plus profondes. N'étendez PureComponent que lorsque vous prévoyez d'avoir des accessoires et un état simples, ou utilisez forceUpdate () lorsque vous savez que les structures de données profondes ont changé

UPD2: Je pense que la réconciliation est également un thème important pour une compréhension superficielle des comparaisons.

Valex
la source
1

L'extrait d'égalité peu profond de @supi ci-dessus ( https://stackoverflow.com/a/51343585/800608 ) échoue s'il prevObjpossède une clé qui newObjn'en a pas. Voici une implémentation qui devrait en tenir compte:

const shallowEqual = (objA, objB) => {
  if (!objA || !objB) {
    return objA === objB
  }
  return !Boolean(
    Object
      .keys(Object.assign({}, objA, objB))
      .find((key) => objA[key] !== objB[key])
  )
}

Notez que ce qui précède ne fonctionne pas dans l'Explorateur sans polyfills.

mzedeler
la source
Ça a l'air bien, mais dans ce cas, passer deux NaN renvoie faux alors que dans la réponse précédente, c'est vrai.
Spadar Shut
0

Il existe une implémentation avec des exemples.

const isObject = value => typeof value === 'object' && value !== null;

const compareObjects = (A, B) => {
  const keysA = Object.keys(A);
  const keysB = Object.keys(B);
 
  if (keysA.length !== keysB.length) {
    return false;
  }
 
  return !keysA.some(key => !B.hasOwnProperty(key) || A[key] !== B[key]);
};

const shallowEqual = (A, B) => {
  if (A === B) {
    return true;
  }
 
  if ([A, B].every(Number.isNaN)) {
    return true;
  }
  
  if (![A, B].every(isObject)) {
    return false;
  }
  
  return compareObjects(A, B);
};

const a = { field: 1 };
const b = { field: 2 };
const c = { field: { field: 1 } };
const d = { field: { field: 1 } };

console.log(shallowEqual(1, 1)); // true
console.log(shallowEqual(1, 2)); // false
console.log(shallowEqual(null, null)); // true
console.log(shallowEqual(NaN, NaN)); // true
console.log(shallowEqual([], [])); // true
console.log(shallowEqual([1], [2])); // false
console.log(shallowEqual({}, {})); // true
console.log(shallowEqual({}, a)); // false
console.log(shallowEqual(a, b)); // false
console.log(shallowEqual(a, c)); // false
console.log(shallowEqual(c, d)); // false

Max Starling
la source