【React】PureComponent 和 Component 的区别

[复制链接]
发表于 2025-11-3 19:06:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
媒介

在 React 中,PureComponent 和 Component 都是用于创建组件的基类,但它们有一个重要的区别:PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中,它对props 和 state (新老的属性/状态)会做一个浅比力,只比力第一层,状态重要指的是内存地点是否发生厘革。假如颠末浅比力,发现属性和状态并没有厘革,则返回fasle,也就是不继续更新组件,有厘革才会去更新。
一、shouldComponentUpdate 利用
  1. shouldComponentUpdate(nextProps, nextState) {
  2.         let { props, state } = this;
  3.         // props/state:修改之前的属性状态
  4.         // nextProps/nextState:将要修改的属性状态
  5.         return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
  6.         // return true 说明需要更新,false 说明不需要更新
  7.     }
复制代码
二、浅比力


上图是一个浅比力的逻辑。
这里我们实现一个浅比力的逻辑,重要是比力props
和state。起首须要一个判断是否为对象的方法。

  • 判断传过来的参数不为null,且参数范例为object大概function
  1. const isObject = function isObject(obj){
  2.      return obj !== null && /^(object|function)$/.test(typeof obj);
  3. }
复制代码
比力两个对象的属性和状态

  • 判断传过的对象是否为对象
  • 判断对象是否相当
  • 接下来就开始判断两个对象的key、value

    • 判断两个对象的长度是否相当(通过Reflect.ownkeys(obj)获取key)
    • 循环此中一个对象的key聚集,判断该对象的key是否在另一个对象里,假如在,则判断两个对象雷同key的value 值是否相当

  1. // 对象浅比较的方法
  2. const shallowEqual = function shallowEqual(objA, objB) {
  3.     if (!isObject(objA) || !isObject(objB)) return false;
  4.     if (objA === objB) return true;
  5.     // 先比较成员的数量
  6.     let keysA = Reflect.ownKeys(objA),
  7.         keysB = Reflect.ownKeys(objB);
  8.     if (keysA.length !== keysB.length) return false;
  9.     // 数量一致,再逐一比较内部的成员「只比较第一级:浅比较」
  10.     for (let i = 0; i < keysA.length; i++) {
  11.         let key = keysA[i];
  12.         // 如果一个对象中有这个成员,一个对象中没有;或者,都有这个成员,但是成员值不一样;都应该被判定为不相同!!
  13.         if (!objB.hasOwnProperty(key) || !Object.is(objA[key], objB[key])) {
  14.             return false;
  15.         }
  16.     }
  17.     // 以上都处理完,发现没有不相同的成员,则认为两个对象是相等的
  18.     return true;
  19. };
复制代码
三、浅比力组件内利用

在利用React.PureComponent时间,组件内会默认加一个shouldComponentUpdate周期函数实现浅比力的功能,但是利用React.Component 要想利用shouldComponentUpdate周期函数的功能,就须要手动添加shouldComponentUpdate,且手动判断属性和状态是否改变。
  1. class Demo extends React.Component {
  2.     state = {
  3.         arr: [10, 20, 30] //0x001
  4.     };
  5.     render() {
  6.         let { arr } = this.state; //arr->0x001
  7.         return <div>
  8.             {arr.map((item, index) => {
  9.                 return <span key={index} style={{
  10.                     display: 'inline-block',
  11.                     width: 100,
  12.                     height: 100,
  13.                     background: 'pink',
  14.                     marginRight: 10
  15.                 }}>
  16.                     {item}
  17.                 </span>;
  18.             })}
  19.             <br />
  20.             <button onClick={() => {
  21.                 arr.push(40); //给0x001堆中新增一个40
  22.                 // this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」
  23.                 // console.log(this.state.arr); //[10,20,30,40]
  24.                 /*
  25.                 // 无法更新的
  26.                 */
  27.                 // this.forceUpdate(); //跳过默认加的shouldComponentUpdate,直接更新
  28.                 this.setState({
  29.                     arr: [...arr] //我们是让arr状态值改为一个新的数组「堆地址」
  30.                 })
  31.             }}>新增SPAN</button>
  32.         </div >;
  33.     }
  34.     shouldComponentUpdate(nextProps, nextState) {
  35.         let { props, state } = this;
  36.         return !shallowEqual(props, nextProps) || !shallowEqual(state, nextState);
  37.     }
  38. }
复制代码
修改arr的值,并利用setState修改arr的状态,发现页面的状态并没有厘革,这是由于,状态变了,但是arr照旧之前的状态地点。
  1.   <button onClick={() => {
  2.                 arr.push(40); //给0x001堆中新增一个40
  3.                 this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」
  4.   }}>新增SPAN</button>
复制代码
此时想要页面状态改变则有两种方式:
(1)方式一:利用this.forceUpdate(),直接跳过shouldComponentUpdate周期,以是页面肯定会更改状态并渲染
  1.             <button onClick={() => {
  2.                arr.push(40); //给0x001堆中新增一个40
  3.                this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」
  4.                this.forceUpdate(); //跳过默认加的shouldComponentUpdate,直接更新
  5.             }}>新增SPAN</button>
复制代码
(2)方式二:修改arr的状态地点
利用this.setState({  arr: [...arr]    }),此时arr的引用地点就不是之前的引用地点,是一个全新的数组,则objA === objB就为false。
  1.        <button onClick={() => {
  2.               arr.push(40); //给0x001堆中新增一个40
  3.               this.setState({ arr }); //最新修改的转态地址,还是0x001「状态地址没有改」
  4.               this.setState({
  5.                    arr: [...arr] //我们是让arr状态值改为一个新的数组「堆地址」
  6.                 })
  7.         }}>新增SPAN</button>
复制代码
四、拓展

1、为什么利用Reflect.ownKeys(obj)获取对象的属性,而不是用Object.keys()?

特性Object.keys()Reflect.ownKeys()返回的键范例仅平常属性的键(字符串)平常属性键 + 符号属性键(字符串和符号)返回的属性范例仅可罗列属性包罗全部自有属性(可罗列与不可罗列属性)是否支持符号属性否是实用场景处理惩罚平常属性,只关心平常属性或只关心可罗列属性全面访问对象全部自有属性(包罗符号属性和不可罗列属性),用于更底层的操纵或元编程场景Reflect.ownKeys(obj) 是一种比 Object.keys(obj) 更通用的方式来获取对象的全部键名,包罗平常属性和Symbol 属性。
Object.keys(obj) 只能返回对象的平常属性(即非符号属性)。它会遍历对象的自有可罗列属性,并返回一个数组。不可罗列属性(好比通过 Object.defineProperty 设置为不可罗列的属性)和符号属性都不会被返回。
示例:
  1. const obj = {
  2.   normalKey: 'value',
  3.   [Symbol('symbolKey')]: 'symbolValue'
  4. };
  5. console.log(Object.keys(obj));  // 输出: ["normalKey"]
复制代码
在上述代码中,Object.keys(obj) 只返回了 normalKey,没有返回符号属性。
Reflect.ownKeys(obj) 是一个更通用的方式,它会返回对象的全部自有键名,无论是平常属性照旧符号属性。返回的结果是一个包罗字符串和符号的数组。
示例:
  1. const obj = {
  2.   normalKey: 'value',
  3.   [Symbol('symbolKey')]: 'symbolValue'
  4. };
  5. console.log(Reflect.ownKeys(obj));  // 输出: ["normalKey", Symbol(symbolKey)]
复制代码
在这个示例中,Reflect.ownKeys(obj) 返回了 normalKey 和符号属性 Symbol(symbolKey),这使得它比 Object.keys(obj) 更为强盛和机动。
示例:团结 Reflect.ownKeys() 利用:
  1. const obj = {
  2.   normalKey: 'value'
  3. };
  4. Object.defineProperty(obj, 'hiddenKey', {
  5.   value: 'hiddenValue',
  6.   enumerable: false
  7. });
  8. const sym = Symbol('symbolKey');
  9. obj[sym] = 'symbolValue';
  10. console.log(Object.keys(obj));  // 输出: ["normalKey"]
  11. console.log(Reflect.ownKeys(obj));  // 输出: ["normalKey", "hiddenKey", Symbol(symbolKey)]
复制代码
在这个示例中,Object.keys(obj) 只返回了 normalKey,而 Reflect.ownKeys(obj) 返回了全部自有键,包罗不可罗列的 hiddenKey 和符号属性 Symbol(symbolKey)。
2、为什么利用hasOwnProperty()获取对象的属性,而不是用in?

objB.hasOwnProperty(key) 是 JavaScript 中用于判断对象 objB 是否具有某个自有属性 key 的方法。
hasOwnProperty() 是 Object 的一个原型方法,属于全部对象实例。它用于查抄给定的属性是否是对象自身的直接属性,而不是从原型链继续来的属性。
语法:
  1. obj.hasOwnProperty(key)
复制代码
示例 1:查抄自有属性
  1. const objB = {
  2.   name: 'Alice',
  3.   age: 25
  4. };
  5. console.log(objB.hasOwnProperty('name'));  // 输出: true
  6. console.log(objB.hasOwnProperty('age'));   // 输出: true
  7. console.log(objB.hasOwnProperty('gender')); // 输出: false
复制代码
在这个例子中,objB 有 name 和 age 这两个自有属性,因此 hasOwnProperty('name') 和 hasOwnProperty('age') 返回 true。而 gender 并没有在 objB 上界说,以是返回 false。
示例 2:查抄继续的属性
  1. const person = {
  2.   name: 'Alice'
  3. };
  4. const employee = Object.create(person);
  5. employee.age = 25;
  6. console.log(employee.hasOwnProperty('age'));   // 输出: true
  7. console.log(employee.hasOwnProperty('name'));  // 输出: false
复制代码
employee 对象通过 Object.create(person) 创建,继续了 person 的属性。因此,employee.hasOwnProperty('name') 返回 false,由于 name 是从原型链继续来的,而 age 是 employee 本身界说的属性,以是返回 true。
与 in 操纵符的区别
in 操纵符会查抄对象及其原型链上是否存在指定的属性,而 hasOwnProperty() 只查抄对象自身的属性,不会查找原型链上的属性。
示例 3:区别
  1. const person = {
  2.   name: 'Alice'
  3. };
  4. const employee = Object.create(person);
  5. employee.age = 25;
  6. console.log('age' in employee);           // 输出: true
  7. console.log('name' in employee);          // 输出: true
  8. console.log(employee.hasOwnProperty('age')); // 输出: true
  9. console.log(employee.hasOwnProperty('name')); // 输出: false
复制代码

  • 'age' in employee 会返回 true,由于 employee 对象自身有 age 属性。
  • 'name' in employee 会返回 true,由于 name 是从原型 person 继续的。
  • employee.hasOwnProperty('age') 返回 true,由于 age 是 employee 自有的属性。
  • employee.hasOwnProperty('name') 返回 false,由于 name 是从原型链继续来的。
利用 hasOwnProperty 时的留意点

  • 性能:hasOwnProperty() 只查抄自有属性,性能上优于查抄属性是否存在于对象及其原型链中(比方,利用 in 操纵符或 for...in 循环)。
  • 克制属性名辩论:假如对象本身有一个名为 hasOwnProperty 的属性,大概会导致调用方法时发生辩论。因此,利用 hasOwnProperty 时可以通过 Object.prototype.hasOwnProperty.call() 来确保方法的正确调用:
示例 4:克制属性辩论
  1. const objB = {
  2.   hasOwnProperty: 'some value',
  3.   name: 'Alice'
  4. };
  5. console.log(objB.hasOwnProperty('name'));  // 输出: TypeError: objB.hasOwnProperty is not a function
  6. // 使用 Object.prototype.hasOwnProperty 来避免冲突
  7. console.log(Object.prototype.hasOwnProperty.call(objB, 'name'));  // 输出: true
复制代码
3、为什么利用Object.is(objA,objB)?

Object.is(objA[key], objB[key]) 是 JavaScript 中用于比力两个值是否严酷相当的方法,类似于 ===(严酷相当操纵符),但在某些特殊情况下举动有所差异。Object.is 方法比力的是两个值是否雷同,返回 true 表现相当,返回 false 表现不相当。
Object.is 和 === 在大多数情况下举动雷同,但是它们有两个明显的差异点:

  • NaN:在 === 中,NaN 不即是 NaN,但是在 Object.is 中,NaN 即是 NaN。
  • +0 和 -0:在 === 中,+0 和 -0 被以为是相当的,但是在 Object.is 中,+0 和 -0 被以为是差异的。
示例 1:NaN 比力
  1. console.log(Object.is(NaN, NaN));  // 输出: true
  2. console.log(NaN === NaN);          // 输出: false
复制代码
在这个例子中,Object.is(NaN, NaN) 返回 true,而 NaN === NaN 返回 false。这是 Object.is 和 === 的区别之一。
示例 2:+0 和 -0 比力
  1. console.log(Object.is(+0, -0));  // 输出: false
  2. console.log(+0 === -0);          // 输出: true
复制代码
在这个例子中,Object.is(+0, -0) 返回 false,而 +0 === -0 返回 true。这是由于 Object.is 会区分 +0 和 -0。
利用场景
Object.is 实用于须要正确比力两个值是否完全雷同,特殊是在以下情况下:

  • 比力 NaN 是否相当。
  • 比力 +0 和 -0 是否相当。
  • 比力对象或数组的引用是否雷同。
示例 3:在比力对象属性时利用 Object.is
  1. const objA = { key: 42 };
  2. const objB = { key: 42 };
  3. console.log(Object.is(objA.key, objB.key));  // 输出: true,因为值相等
复制代码
示例 4:比力引用范例(对象、数组)
  1. const arrA = [1, 2, 3];
  2. const arrB = [1, 2, 3];
  3. console.log(Object.is(arrA, arrB));  // 输出: false,因为它们是不同的引用
  4. const arrC = arrA;
  5. console.log(Object.is(arrA, arrC));  // 输出: true,因为它们引用的是同一个数组
复制代码
四、利用===判断对象是否相当

if (objA === objB) return true; 的寄义是判断 objA 和 objB 是否是相当的,并在它们相当时返回 true。这里利用的是严酷相当运算符 ===。

  • === 运算符:

    • 严酷相当:=== 是严酷相当运算符,它会同时比力值和范例。假如两个操纵数的范例差异,返回 false。
    • 对于引用范例(比方对象、数组、函数等),=== 比力的是内存地点,即两个变量是否引用同一个对象。

  • 对象的比力:

    • 假如 objA 和 objB 都是对象范例(包罗数组、函数等),=== 会查抄它们是否指向同一个对象。
    • 假如它们指向同一个内存地点(便是同一个对象),则返回 true。
    • 假如它们是差异的对象,固然对象的内容大概雷同,但它们的内存地点差异,因此返回 false。

示例:
  1. let objA = { name: 'Alice' };
  2. let objB = { name: 'Alice' };
  3. let objC = objA;
  4. console.log(objA === objB); // false, 因为它们是不同的对象,虽然内容相同
  5. console.log(objA === objC); // true, 因为 objC 是 objA 的引用,指向同一个对象
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表