การเปรียบเทียบตื้นทำงานอย่างไรในการตอบสนอง


97

ในเอกสารของ React นี้ได้กล่าวไว้ว่า

ตื้นเปรียบเทียบทำการตรวจสอบความเท่าเทียมกันแบบตื้น ๆ บนอุปกรณ์ประกอบฉากปัจจุบันและวัตถุ nextProps ตลอดจนสถานะปัจจุบันและวัตถุ nextState

สิ่งที่ฉันไม่เข้าใจคือถ้ามันเปรียบเทียบวัตถุอย่างตื้น ๆ เมธอด shouldComponentUpdate จะคืนค่าจริงเสมอเช่น

เราไม่ควรกลายพันธุ์ของรัฐ

และหากเราไม่ได้ทำการเปลี่ยนแปลงสถานะการเปรียบเทียบจะส่งกลับเท็จเสมอดังนั้นการอัปเดต shouldComponent จะคืนค่าจริงเสมอ ฉันสับสนเกี่ยวกับวิธีการทำงานและเราจะลบล้างสิ่งนี้เพื่อเพิ่มประสิทธิภาพได้อย่างไร

คำตอบ:


132

การเปรียบเทียบแบบตื้นจะตรวจสอบความเท่าเทียมกัน เมื่อเปรียบเทียบค่าสเกลาร์ (ตัวเลขสตริง) จะเปรียบเทียบค่าของมัน เมื่อเปรียบเทียบวัตถุจะไม่เปรียบเทียบแอตทริบิวต์ของวัตถุ - เปรียบเทียบเฉพาะการอ้างอิงเท่านั้น (เช่น "ชี้ไปที่วัตถุเดียวกันหรือไม่)

ลองพิจารณารูปร่างของ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);

การเปรียบเทียบแบบตื้นเป็นวิธีที่มีประสิทธิภาพในการตรวจจับการเปลี่ยนแปลง คาดว่าคุณจะไม่กลายพันธุ์ข้อมูล


ดังนั้นถ้าเราเขียนโค้ดแล้วถ้าเรามีค่าสเกลาร์เราควรจะกลายพันธุ์เพราะถ้าเราจะโคลนการตรวจสอบความเท่าเทียมกันจะส่งคืนเท็จ?
Ajay Gaur

30
@AjayGaur แม้ว่าคำตอบนี้จะช่วยให้คุณเข้าใจความเท่าเทียมกันอย่างเข้มงวด (===) ใน JavaScript แต่ก็ไม่ได้บอกอะไรคุณเกี่ยวกับฟังก์ชันตื้นเปรียบเทียบ () ในการตอบสนอง (ฉันเดาว่าผู้ตอบเข้าใจคำถามของคุณผิด) ตื้น ๆ เปรียบเทียบ () จริง ๆ แล้วในเอกสารที่คุณให้ไว้: การทำซ้ำบนคีย์ของวัตถุที่กำลังเปรียบเทียบและส่งคืนค่าจริงเมื่อค่าของคีย์ในแต่ละวัตถุไม่เท่ากันอย่างเคร่งครัด หากคุณยังไม่เข้าใจฟังก์ชันนี้และเหตุใดคุณจึงไม่ควรเปลี่ยนสถานะฉันสามารถเขียนคำตอบให้คุณได้
sunquan

7
มันไม่เป็นความจริง. ดูนี่. github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/…
ไบร์ทลี

5
คำตอบนี้อธิบายความแตกต่างระหว่างตัวดำเนินการความเท่าเทียมกัน (==) และความเท่าเทียมกันอย่างเข้มงวด (===) ใน JS คำถามเกี่ยวกับการเปรียบเทียบแบบตื้นซึ่งในการตอบสนองถูกนำไปใช้โดยการตรวจสอบความเท่าเทียมกันระหว่างอุปกรณ์ประกอบฉากทั้งหมดของวัตถุสองชิ้น
Mateo Hrastnik

@sunquan คุณช่วยเขียนคำตอบได้ไหม?
Ajay Gaur

35

การเปรียบเทียบแบบตื้นคือเมื่อทำการเปรียบเทียบคุณสมบัติของวัตถุโดยใช้ "===" หรือความเสมอภาคที่เข้มงวดและจะไม่ทำการเปรียบเทียบที่ลึกลงไปในคุณสมบัติ เช่น

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

คราวนี้ทุกคุณสมบัติจะคืนค่าจริงสำหรับการเปรียบเทียบที่เข้มงวดเนื่องจากคุณสมบัติของทีมในวัตถุใหม่และเก่าชี้ไปที่วัตถุเดียวกัน

// 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 ที่ตอบสนองจะใช้ hooks รอบการอัปเดตส่วนประกอบ หากคุณอัปเดตออบเจ็กต์สถานะโดยตรงเพื่อหลีกเลี่ยง hooks วงจรชีวิตของส่วนประกอบโดยเจตนาคุณอาจใช้ตัวแปรหรืออ็อบเจกต์ธรรมดาเพื่อเก็บข้อมูลไม่ใช่ออบเจ็กต์สถานะ

ไม่ได้หมายความว่าถ้าฉันส่งวัตถุผ่านอุปกรณ์ประกอบฉากหรือเปรียบเทียบสถานะเป็นสถานะถัดไปส่วนประกอบจะไม่แสดงผลซ้ำเพราะแม้ว่าคุณสมบัติของวัตถุนั้นจะเปลี่ยนไป แต่ก็จะยังคงชี้ไปที่วัตถุเดียวกันดังนั้นจึงส่งผลให้ เท็จจึงไม่เรนเดอร์?
จาวาสคริปต์

@javascripting - นั่นคือเหตุผลที่คุณต้องโคลน (โดยใช้ตัวอย่าง Object.assign ()) วัตถุของคุณเมื่อมีการเปลี่ยนแปลงแทนที่จะกลายพันธุ์เพื่อให้ React ทราบเมื่อการอ้างอิงเปลี่ยนไปและส่วนประกอบจำเป็นต้องอัปเดต
Mac_W

หากprevObjมีคีย์ที่newObjไม่มีการเปรียบเทียบจะล้มเหลว
mzedeler

@mzedeler - จะไม่เป็นเพราะ "for in" วนซ้ำใน newObj ไม่ใช่ใน prevObj ลองรันโค้ดตามที่อยู่ในคอนโซลนักพัฒนาของเบราว์เซอร์ ยิ่งกว่านั้นโปรดอย่าใช้การเปรียบเทียบแบบตื้น ๆ นี้อย่างจริงจังเกินไปนี่เป็นเพียงการแสดงให้เห็นถึงแนวคิด
supi

แล้วอาร์เรย์ล่ะ?
Juan De la Cruz

27

ตื้นเปรียบเทียบผลงานโดยการตรวจสอบถ้าสองค่าเท่ากันในกรณีของชนิดดั้งเดิมเช่นสตริงตัวเลขและในกรณีของวัตถุมันเป็นเพียงแค่การตรวจสอบการอ้างอิง ดังนั้นหากคุณเปรียบเทียบวัตถุที่ซ้อนกันแบบตื้น ๆ ก็จะตรวจสอบการอ้างอิงไม่ใช่ค่าภายในวัตถุนั้น


11

นอกจากนี้ยังมีคำอธิบายแบบดั้งเดิมของการเปรียบเทียบแบบตื้นใน React:

ตื้นเปรียบเทียบทำการตรวจสอบความเท่าเทียมกันแบบตื้น ๆ บนอุปกรณ์ประกอบฉากปัจจุบันและวัตถุ nextProps ตลอดจนสถานะปัจจุบันและวัตถุ nextState

ทำได้โดยการทำซ้ำบนคีย์ของวัตถุที่กำลังเปรียบเทียบและส่งคืนค่าจริงเมื่อค่าของคีย์ในแต่ละวัตถุไม่เท่ากันอย่างเคร่งครัด

UPD : เอกสารปัจจุบันกล่าวเกี่ยวกับการเปรียบเทียบแบบตื้น:

หากฟังก์ชัน render () ของคอมโพเนนต์ React ของคุณแสดงผลเหมือนกันโดยใช้อุปกรณ์ประกอบฉากและสถานะเดียวกันคุณสามารถใช้ React.PureComponent เพื่อเพิ่มประสิทธิภาพได้ในบางกรณี

React.PureComponent's shouldComponentUpdate () จะเปรียบเทียบวัตถุอย่างตื้น ๆ เท่านั้น หากสิ่งเหล่านี้มีโครงสร้างข้อมูลที่ซับซ้อนอาจทำให้เกิดผลลบเท็จสำหรับความแตกต่างที่ลึกซึ้งยิ่งขึ้น ขยาย PureComponent เฉพาะเมื่อคุณคาดว่าจะมีอุปกรณ์ประกอบฉากและสถานะที่เรียบง่ายหรือใช้ forceUpdate () เมื่อคุณรู้ว่าโครงสร้างข้อมูลเชิงลึกมีการเปลี่ยนแปลง

UPD2:ฉันคิดว่าการปรองดองยังเป็นประเด็นสำคัญสำหรับการเปรียบเทียบความเข้าใจแบบตื้น ๆ


ไม่ควร "เป็นเท็จ" ในand returning true when the values
rahulg

2

ตัวอย่างข้อมูลตื้นเท่ากับโดย @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])
  )
}

โปรดทราบว่าข้างต้นใช้ไม่ได้ใน Explorer ที่ไม่มี polyfills


ดูดี แต่ในกรณีนี้การส่งคืน NaN สองค่าเป็นเท็จในขณะที่คำตอบก่อนหน้านี้เป็นจริง
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.