Como a comparação superficial funciona em reação

91

Em esta documentação de Reagir, diz-se que

shallowCompare executa uma verificação de igualdade superficial nos objetos atuais props e nextProps, bem como no estado atual e nos objetos nextState.

O que não consigo entender é se ele compara superficialmente os objetos, então o método shouldComponentUpdate sempre retornará verdadeiro, como

Não devemos transformar os estados.

e se não estivermos alterando os estados, a comparação sempre retornará falso e, portanto, a atualização shouldComponent sempre retornará verdadeiro. Estou confuso sobre como está funcionando e como vamos substituir isso para aumentar o desempenho.

Ajay Gaur
fonte

Respostas:

125

A comparação superficial verifica a igualdade. Ao comparar valores escalares (números, strings), ele compara seus valores. Ao comparar objetos, ele não compara seus atributos - apenas suas referências são comparadas (por exemplo, "eles apontam para o mesmo objeto?).

Vamos considerar seguir a forma do userobjeto

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

Exemplo 1:

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

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

Observe que você mudou o nome do usuário. Mesmo com essa mudança, os objetos são iguais. As referências são exatamente as mesmas.

Exemplo 2:

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

Agora, sem nenhuma alteração nas propriedades do objeto, elas são completamente diferentes. Ao clonar o objeto original, você cria uma nova cópia, com referência diferente.

A função de clone pode ter a seguinte aparência (sintaxe ES6)

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

A comparação superficial é uma maneira eficiente de detectar mudanças. Ele espera que você não modifique os dados.

Andreyco
fonte
Portanto, se estivermos escrevendo código, se tivermos valores escalares, devemos alterá-los porque, se os clonarmos, a verificação de igualdade retornará falso?
Ajay Gaur
29
@AjayGaur Embora esta resposta possa ajudá-lo a entender a igualdade estrita (===) em JavaScript, ela não diz nada sobre a função shallowCompare () em React (acho que o respondente entendeu mal sua pergunta). O que shallowCompare () faz é realmente no documento que você forneceu: iterando nas chaves dos objetos que estão sendo comparados e retornando true quando os valores de uma chave em cada objeto não são estritamente iguais. Se você ainda não entendeu essa função e por que não deve alterar o estado, posso escrever uma resposta para você.
sunquan
7
Não é verdade. Veja isso. github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/…
Bright Lee de
5
Esta resposta descreve a diferença entre os operadores de igualdade (==) e igualdade estrita (===) em JS. A questão é sobre a comparação superficial que, no React, é implementada verificando a igualdade entre todos os suportes de dois objetos.
Mateo Hrastnik
@sunquan, você pode escrever uma resposta sobre isso?
Ajay Gaur
29

comparação superficial é quando as propriedades dos objetos sendo comparados são feitas usando "===" ou igualdade estrita e não conduzirá comparações mais profundas nas propriedades. por exemplo

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

Embora ambos os objetos pareçam ser iguais, game_item.teamsnão é a mesma referência que updated_game_item.teams. Para que 2 objetos sejam iguais, eles devem apontar para o mesmo objeto. Portanto, isso resulta no estado em avaliação para ser atualizado

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

Desta vez, cada uma das propriedades retornam true para a comparação estrita, pois a propriedade teams no objeto novo e no antigo aponta para o mesmo 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

A updated_game_item3.first_world_cuppropriedade falha na avaliação estrita, pois 1930 é um número enquanto game_item.first_world_cupé uma string. Se a comparação tivesse sido solta (==), isso teria passado. No entanto, isso também resultará na atualização do estado.

Notas Adicionais:

  1. Fazer uma comparação profunda não tem sentido, pois afetaria significativamente o desempenho se o objeto de estado estivesse profundamente aninhado. Mas se não estiver muito aninhado e você ainda precisar de uma comparação profunda, implemente-o em shouldComponentUpdate e verifique se isso é suficiente.
  2. Você pode definitivamente transformar o objeto de estado diretamente, mas o estado dos componentes não seria afetado, uma vez que está no fluxo do método setState que reage implementa os ganchos do ciclo de atualização do componente. Se você atualizar o objeto de estado diretamente para evitar deliberadamente os ganchos do ciclo de vida do componente, provavelmente deverá usar uma variável ou objeto simples para armazenar os dados e não o objeto de estado.
supi
fonte
Isso não significa que se eu passar um objeto por meio de adereços ou comparar o estado com o próximo estado, o componente nunca será renderizado novamente, porque mesmo que as propriedades desse objeto tenham mudado, ele ainda apontará para o mesmo objeto, resultando em falso, portanto, não renderizando novamente?
javascripting de
@javascripting - é por isso que você clonaria (usando por exemplo Object.assign ()) seus objetos quando eles mudassem, em vez de alterá-los, para que o React soubesse quando a referência muda e o componente precisa ser atualizado.
Mac_W
Se prevObjcontiver uma chave que newObjnão tem, a comparação falhará.
mzedeler
@mzedeler - não vai porque o "for in" itera em newObj e não em prevObj. tente executar o código como está no console do desenvolvedor do navegador. Além disso, por favor, não leve essa implementação de comparação superficial muito a sério, isso é apenas para demonstrar o conceito
supi
e quanto a matrizes?
Juan De la Cruz,
24

A comparação superficial funciona verificando se dois valores são iguais no caso de tipos primitivos como string, números e no caso de objeto apenas verifica a referência . Portanto, se você comparar superficialmente um objeto aninhado profundo, ele apenas verificará a referência, não os valores dentro desse objeto.

Akhil Choudhary
fonte
10

Também há uma explicação legada de comparação superficial no React:

shallowCompare executa uma verificação de igualdade superficial nos objetos atuais props e nextProps, bem como no estado atual e nos objetos nextState.

Ele faz isso iterando nas chaves dos objetos que estão sendo comparados e retornando true quando os valores de uma chave em cada objeto não são estritamente iguais.

UPD : a documentação atual diz sobre comparação superficial:

Se a função render () do seu componente React renderizar o mesmo resultado dados os mesmos props e estado, você pode usar React.PureComponent para um aumento de desempenho em alguns casos.

O shouldComponentUpdate () de React.PureComponent compara apenas superficialmente os objetos. Se eles contiverem estruturas de dados complexas, podem produzir falsos negativos para diferenças mais profundas. Estenda PureComponent apenas quando você espera ter props e estado simples, ou use forceUpdate () quando você souber que as estruturas de dados profundas mudaram

UPD2: Eu acho que a reconciliação também é um tema importante para um entendimento superficial de comparação.

valex
fonte
1

O snippet igual superficial por @supi acima ( https://stackoverflow.com/a/51343585/800608 ) falha se prevObjtiver uma chave que newObjnão tem. Aqui está uma implementação que deve levar isso em consideração:

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

Observe que o acima não funciona no Explorer sem polyfills.

Mzedeler
fonte
Parece bom, mas neste caso, passar dois NaNs retorna falso, enquanto na resposta anterior é verdadeiro.
Spadar fechado em
0

Existe uma implementação com exemplos.

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
fonte