浅い比較はどのように反応しますか?


91

、このドキュメントリアクトの、次のことが言われています

shallowCompareは、現在のpropsオブジェクトとnextPropsオブジェクト、および現在の状態オブジェクトとnextStateオブジェクトの浅い等価チェックを実行します。

私が理解できないことは、オブジェクトを浅く比較すると、shouldComponentUpdateメソッドは常にtrueを返すことです。

国家を変異させてはならない。

状態を変更しない場合、比較は常にfalseを返すため、shouldComponentの更新は常にtrueを返します。私はそれがどのように機能しているか、そしてこれをどのようにオーバーライドしてパフォーマンスを向上させるかについて混乱しています。

回答:


125

浅い比較は等しいかどうかをチェックします。スカラー値(数値、文字列)を比較する場合、それらの値を比較します。オブジェクトを比較する場合、オブジェクトの属性は比較されません。参照のみが比較されます(たとえば、「同じオブジェクトを指しているか」など)。

userオブジェクトの次の形状を考えてみましょう

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

例1:

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

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

ユーザー名を変更したことに注意してください。この変更でも、オブジェクトは同じです。それらの参照はまったく同じです。

例2:

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

これで、オブジェクトのプロパティに変更を加えなくても、それらは完全に異なります。元のオブジェクトを複製することにより、異なる参照で新しいコピーを作成します。

クローン機能は次のようになります(ES6構文)

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

浅い比較は、変更を検出する効率的な方法です。データを変更しないことを期待しています。


したがって、コードを記述している場合、スカラー値がある場合は、それらを変更する必要があります。それは、それらを複製する場合、等価チェックがfalseを返すからです。
Ajay Gaur 2016年

29
@AjayGaurこの回答は、JavaScriptの厳密な等価性(===)を理解するのに役立ちますが、ReactのshallowCompare()関数については何も通知しません(回答者が質問を誤解したと思います)。提供されたドキュメントには、shallowCompare()が実際に実行するものがあります。比較対象のオブジェクトのキーを反復処理し、各オブジェクトのキーの値が厳密に等しくない場合にtrueを返します。それでもこの関数が理解できない場合、および状態を変更すべきでない理由については、私が答えを書くことができます。
2016年

7
それは真実ではない。これを見てください。github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/...
明るいリー

5
この答えは、JSの等価(==)演算子と厳密な等価(===)演算子の違いを説明しています。問題は浅い比較です。これは、Reactで2つのオブジェクトのすべての小道具間の同等性をチェックすることによって実装されます。
Mateo Hrastnik

@sunquan答えを書いていただけますか?
Ajay Gaur 2018

29

浅い比較とは、比較されるオブジェクトのプロパティが「===」または厳密な等価性を使用して実行され、プロパティのより深い比較が行われない場合です。例えば

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

両方のオブジェクトgame_item.teamsは同じように見えますが、と同じ参照ではありませんupdated_game_item.teams。2つのオブジェクトが同じであるためには、同じオブジェクトを指す必要があります。したがって、これにより、状態が更新されていると評価されます。

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

今回は、新しいオブジェクトと古いオブジェクトのteamsプロパティが同じオブジェクトを指しているため、厳密な比較ではすべてのプロパティがtrueを返します。

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

updated_game_item3.first_world_cupプロパティは、1930年と厳しい評価を失敗しながら、数あるgame_item.first_world_cup文字列です。比較が緩やかだった場合(==)、これは成功します。それでも、これにより状態が更新されます。

その他の注意事項:

  1. 状態オブジェクトが深くネストされている場合、パフォーマンスに大きな影響を与えるため、ディープコンペアを実行しても意味がありません。しかし、入れ子になっておらず、さらに深い比較が必要な場合は、shouldComponentUpdateに実装して、それで十分かどうかを確認してください。
  2. 状態オブジェクトを直接変更することはできますが、コンポーネントの状態は影響を受けません。これは、反応するsetStateメソッドフローで、コンポーネントの更新サイクルフックが実装されるためです。状態オブジェクトを直接更新して、コンポーネントのライフサイクルフックを意図的に回避する場合は、おそらく状態オブジェクトではなく、単純な変数またはオブジェクトを使用してデータを格納する必要があります。

小道具を介してオブジェクトを渡したり、状態を次の状態と比較したりしても、コンポーネントは再レンダリングされません。そのオブジェクトのプロパティが変更されたとしても、同じオブジェクトを指すため、結果としてfalse、したがって、再レンダリングしませんか?
javascripting

@javascripting-オブジェクトを変更するのではなく、変更したときにオブジェクトを複製(たとえば、Object.assign()を使用)するので、参照が変更され、コンポーネントを更新する必要があるときにReactが認識します。
Mac_W

ないprevObjキーが含まれている場合newObj、比較は失敗します。
mzedeler

@mzedeler-「for in」がprevObjではなくnewObjで反復するため、そうなりません。ブラウザの開発者コンソールでコードをそのまま実行してみてください。さらに、この浅い比較の実装をあまり真剣に
受け取ら

配列はどうですか?
ファンデラクルス

24

浅い比較は、文字列や数値などのプリミティブ型の場合は2つの値が等しいかどうかをチェックし、オブジェクトの場合は参照をチェックするだけで機能します。したがって、深くネストされたオブジェクトを浅く比較すると、そのオブジェクト内の値ではなく参照のみがチェックされます。


10

Reactのシャローコンペアの従来の説明もあります

shallowCompareは、現在のpropsオブジェクトとnextPropsオブジェクト、および現在の状態オブジェクトとnextStateオブジェクトの浅い等価チェックを実行します。

これは、比較されるオブジェクトのキーを反復し、各オブジェクトのキーの値が厳密に等しくない場合にtrueを返すことによって行われます。

UPD現在のドキュメントは浅い比較について述べています

Reactコンポーネントのrender()関数が同じ小道具と状態で同じ結果をレンダリングする場合、場合によってはReact.PureComponentを使用してパフォーマンスを向上させることができます。

React.PureComponentのshouldComponentUpdate()は、オブジェクトを浅く比較するだけです。これらに複雑なデータ構造が含まれている場合、より深い差異に対して偽陰性が生じる可能性があります。単純な小道具と状態が予想される場合にのみPureComponentを拡張するか、深いデータ構造が変更されたことがわかっている場合はforceUpdate()を使用します

UPD2:和解も浅い比較理解のための重要なテーマだと思います。


1

上記の@supi(https://stackoverflow.com/a/51343585/800608)による浅い等しいスニペットprevObjは、newObj持っていないキーを持っていると失敗します。これを考慮に入れるべき実装は次のとおりです。

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

上記は、ポリフィルなしのエクスプローラでは機能しないことに注意してください。


見た目は良いですが、この場合、2つのNaNを渡すとfalseが返されますが、前の回答ではtrueです。
Spadar Shut

0

例のある実装があります。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.