¿Cómo reacciona el trabajo de comparación superficial?


97

En esta documentación de React, se dice que

shallowCompare realiza una verificación de igualdad superficial en los objetos de utilería actuales y nextProps, así como en el estado actual y los objetos nextState.

Lo que no puedo entender es que si compara superficialmente los objetos, el método shouldComponentUpdate siempre devolverá verdadero, como

No debemos mutar los estados.

y si no estamos mutando los estados, la comparación siempre devolverá falso y, por lo tanto, la actualización shouldComponent siempre devolverá verdadero. Estoy confundido acerca de cómo está funcionando y cómo anularemos esto para mejorar el rendimiento.

Respuestas:


132

La comparación superficial verifica la igualdad. Al comparar valores escalares (números, cadenas), compara sus valores. Cuando se comparan objetos, no se comparan sus atributos, solo se comparan sus referencias (por ejemplo, "¿apuntan al mismo objeto?).

Consideremos seguir la forma del userobjeto

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

Ejemplo 1:

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

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

Observe que cambió el nombre de los usuarios. Incluso con este cambio, los objetos son iguales. Las referencias son exactamente iguales.

Ejemplo 2:

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

Ahora, sin ningún cambio en las propiedades del objeto, son completamente diferentes. Al clonar el objeto original, crea una nueva copia, con una referencia diferente.

La función de clonación podría verse así (sintaxis ES6)

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

La comparación superficial es una forma eficaz de detectar cambios. Espera que no mutes los datos.


Entonces, si estamos escribiendo código, si tenemos valores escalares, ¿deberíamos mutarlos porque si los clonaremos, la verificación de igualdad devolverá falso?
Ajay Gaur

30
@AjayGaur Aunque esta respuesta puede ayudarlo a comprender la igualdad estricta (===) en JavaScript, pero no le dice nada sobre la función shallowCompare () en React (supongo que el respondedor entendió mal su pregunta). Lo que hace shallowCompare () está en el documento que proporcionaste: iterar sobre las claves de los objetos que se comparan y devolver verdadero cuando los valores de una clave en cada objeto no son estrictamente iguales. Si aún no comprende esta función y por qué no debería mutar el estado, puedo escribir una respuesta para usted.
sunquan


5
Esta respuesta describe la diferencia entre los operadores de igualdad (==) e igualdad estricta (===) en JS. La pregunta es sobre una comparación superficial que, en React, se implementa al verificar la igualdad entre todos los accesorios de dos objetos.
Mateo Hrastnik

@sunquan, ¿puedes escribir una respuesta sobre eso?
Ajay Gaur

35

La comparación superficial es cuando las propiedades de los objetos que se comparan se realizan utilizando "===" o igualdad estricta y no realizarán comparaciones más profundas en las propiedades. por ejemplo

// 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.

Aunque ambos objetos parecen ser iguales, game_item.teamsno es la misma referencia que updated_game_item.teams. Para que dos objetos sean iguales, deben apuntar al mismo objeto. Por lo tanto, esto da como resultado que el estado se evalúe para actualizarse

// 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.

Esta vez, cada una de las propiedades devuelve verdadero para la comparación estricta, ya que la propiedad de teams en el objeto nuevo y antiguo apunta al mismo objeto.

// 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_cuppropiedad no pasa la evaluación estricta ya que 1930 es un número mientras que game_item.first_world_cupes una cadena. Si la comparación hubiera sido floja (==), esto habría pasado. No obstante, esto también resultará en una actualización del estado.

Notas adicionales:

  1. Hacer una comparación profunda no tiene sentido, ya que afectaría significativamente el rendimiento si el objeto de estado está profundamente anidado. Pero si no está demasiado anidado y aún necesita una comparación profunda, impleméntelo en shouldComponentUpdate y verifique si eso es suficiente.
  2. Definitivamente puede mutar el objeto de estado directamente, pero el estado de los componentes no se vería afectado, ya que está en el flujo del método setState que reacciona implementa los ganchos del ciclo de actualización del componente. Si actualiza el objeto de estado directamente para evitar deliberadamente los ganchos del ciclo de vida del componente, probablemente debería usar una variable u objeto simple para almacenar los datos y no el objeto de estado.

¿No significa que si paso un objeto a través de accesorios o comparo el estado con el siguiente estado, el componente nunca volvería a renderizarse porque incluso si las propiedades de ese objeto han cambiado, seguirá apuntando al mismo objeto, por lo tanto, resulta falso, por lo tanto, no volver a renderizar?
javascripting

@javascripting: es por eso que clonarías (usando por ejemplo Object.assign ()) tus objetos cuando cambian en lugar de mutarlos para que React sepa cuándo cambia la referencia y el componente necesita actualizarse.
Mac_W

Si prevObjcontiene una clave que newObjno tiene, la comparación fallará.
mzedeler

@mzedeler - no lo hará porque el "for in" itera en newObj y no en prevObj. intente ejecutar el código como está en la consola del desarrollador del navegador. Además, no tome esta implementación de comparación superficial demasiado en serio, esto es solo para demostrar el concepto
supi

¿qué pasa con las matrices?
Juan De la Cruz

27

La comparación superficial funciona verificando si dos valores son iguales en el caso de tipos primitivos como cadena, números y, en el caso de un objeto, solo verifica la referencia . Entonces, si compara superficialmente un objeto anidado profundo, solo verificará la referencia, no los valores dentro de ese objeto.


11

También hay una explicación heredada de la comparación superficial en React:

shallowCompare realiza una verificación de igualdad superficial en los objetos de utilería actuales y nextProps, así como en el estado actual y los objetos nextState.

Lo hace iterando en las claves de los objetos que se comparan y devolviendo verdadero cuando los valores de una clave en cada objeto no son estrictamente iguales.

UPD : la documentación actual dice acerca de la comparación superficial:

Si la función render () de su componente React genera el mismo resultado con los mismos accesorios y estado, puede usar React.PureComponent para aumentar el rendimiento en algunos casos.

El shouldComponentUpdate () de React.PureComponent solo compara superficialmente los objetos. Si estos contienen estructuras de datos complejas, pueden producir falsos negativos para diferencias más profundas. Solo amplíe PureComponent cuando espere tener apoyos y estados simples, o use forceUpdate () cuando sepa que las estructuras de datos profundas han cambiado

UPD2: Creo que la reconciliación también es un tema importante para la comprensión comparativa superficial.


¿No debería ser "falso" enand returning true when the values
rahulg

2

El fragmento igual superficial de @supi anterior ( https://stackoverflow.com/a/51343585/800608 ) falla si prevObjtiene una clave que newObjno tiene. Aquí hay una implementación que debería tener eso en cuenta:

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

Tenga en cuenta que lo anterior no funciona en Explorer sin polyfills.


Se ve bien, pero en este caso, pasar dos NaN devuelve falso mientras que en la respuesta anterior es verdadero.
Cierre del spadar el

0

Hay una implementación con ejemplos.

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.