React源码16.12.x --- Component 与 PureComponent

1. 一个React Component 实例中有些什么?

基于实践的真理,才经得起推敲,开始与混沌斗争的第一步:写个例子看看。

import React from 'react';
class Test extends React.Component {
  state = {
    title: "空"
  };
  updateTitleHander = () => {
    this.setState({ title: "我是test" });
  }
  render() {
    const {title} = this.state;
    return (
      <div>
        <h1>{ title }</h1>
        <div onClick = {this.updateTitleHander} >
          Hello, 我是Test。
        </div>
      </div>
    );
  }
}
export default Test; 

断点看看一个React Component实例到底什么样。可以看到这个实例有很多构造方法和属性,并且原型链指向了Component,这,毫无疑问。
在这里插入图片描述

Component构造函数

主要维护props和context两个对象,以及一个更新器updater。

function Component(props, context, updater) {
  this.props = props;
  this.context = context; // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component原型链方法包含setState方法和forceUpdate

原型链之setState

setState接受两个参数partialState, callbackpartialState可以是对象,也可以是函数,callback将在state更新完毕后执行。

Component.prototype.setState = function (partialState, callback) {
  if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
    {
      throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");
    }
  }
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
原型链之forceUpdate

forceUpdate只接受一个回调函数。

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

我们可以看到,以上两个更新方法都用到了构造函数中的updater

updater是什么

构造函数中有这样一句话this.updater = updater || ReactNoopUpdateQueue;,先查看ReactNoopUpdateQueue,可以认为是一个state的更新器,提供了强制跟新(立即更新)和 批量更新。,那么这俩个方法都是通过调用warnNoop实现的,warnNoop又是什么呢?==而且调用warnNoop时只传递了这个公共实例,并没传递setState传入的待更新的部分state。==问题先放这里,继续往下走。

var ReactNoopUpdateQueue = {
  // 检查是否已挂载此复合组件
  isMounted: function (publicInstance) {
    return false;
  },
 // 强制更新。只有在确定   **不是**    在React代理的DOM事务中时,才应该调用这个函数。
  enqueueForceUpdate: function (publicInstance, callback, callerName) {
    warnNoop(publicInstance, 'forceUpdate');
  },
  
 // 替换所有状态。 始终使用此方法或`setState`来改变state。 您应该将“ this.state”视为不可变的。
 // 无法保证“this.state”会立即更新,因此调用此方法后访问“this.state”可能会返回旧值。
  enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {
    warnNoop(publicInstance, 'replaceState');
  },

  // 设置状态的子集。 这仅是因为_pendingState是内部的。
  // 这提供了合并策略,该合并策略不适用于深层属性,这很令人困惑。
  // 暴露pendingState或在合并期间不要使用它。
  enqueueSetState: function (publicInstance, partialState, callback, callerName) {
    warnNoop(publicInstance, 'setState');
  }
};

但是!ReactNoopUpdateQueue只是未传入updater时的备用选择,在我进行单步调试的时候,实际上进入了react-dom.development.js下的classComponentUpdater.enqueueSetState。看看去,此次略去N秒,这classComponentUpdater提供的方法和ReactNoopUpdateQueue提供的一样,但是方法内部的操作可就有意思了。先来看看enqueueSetState。

var classComponentUpdater = {
  isMounted: isMounted,
  enqueueSetState: function (inst, payload, callback) {},
  enqueueReplaceState: function (inst, payload, callback) { },
  enqueueForceUpdate: function (inst, callback) { }
};
classComponentUpdater.enqueueSetState

enqueueSetState接受三个参数,前面以及提到过了,inst是Component实例,payload是部分更新的state,callback还是那个更新结束后执行的那个callback。

  enqueueSetState: function (inst, payload, callback) {
    var fiber = get(inst);  // 基于实例,生成了一个FiberNode对象
    var currentTime = requestCurrentTimeForUpdate();
    var suspenseConfig = requestCurrentSuspenseConfig();
    var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig); // 计算过期时间
    var update = createUpdate(expirationTime, suspenseConfig);  // 计算更新优先级
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      {
        warnOnInvalidCallback$1(callback, 'setState');
      }
      update.callback = callback;
    }
    enqueueUpdate(fiber, update); //创建更新队列(createUpdateQueue),加到新队列队尾(appendUpdateToQueue),此时update中保存着待更新的部分state、过期时间及更新优先级等
    scheduleWork(fiber, expirationTime); // 生成fiberRootNode
  },

最后两个方法涉及Fiber的运作原理,此次单步调试还看到了React的顶层事件代理机制,以及最后flushSyncCallbackQueue进行批量更新,内容众多,将分次进行理解和记录吧。

2. PureComponent是继承而来吗?

使用空函数原型链继承方式,拷贝继承了React Component的所有原型方法和属性。彻底继承,隔离了与Component的关系。PureComponent构造函数与Component构造函数简直一般无二。特别的在原型链上添加了isPureReactComponent的属性,用于后续更新阶段的判断。

function ComponentDummy() {}

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context; // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods.

_assign(pureComponentPrototype, Component.prototype); // _assign = require('object-assign'); // Object.assign() ponyfill

pureComponentPrototype.isPureReactComponent = true;

这里简单回顾下现有的几种继承方法,从而对比分析React纯组件继承普通组件的优势何在。

PureComponent.prototype指向了一个名为ComponentDummy的空函数作为构造函数的实例,此时PureComponent.prototype可以继承这个空函数原型链上的所有原型属性和方法,然后使用Objec.assign将Component原型链是的方法和属性都拷贝过来了,直接拷贝,和Component并不会产生任何继承相关的副作用。

3. PureComponent的更新策略怎么实现的?

React PureComponent 源码解析
Component & PureComponent这两个类基本相同,唯一的区别是PureComponent的原型上多了一个标识。
这是检查组件是否需要更新的一个判断,ctor就是你声明的继承自Component or PureComponent的类,他会判断你是否继承自PureComponent,如果是的话就shallowEqual比较state和props。
顺便说一下:React中对比一个ClassComponent是否需要更新,只有两个地方。一是看有没有shouldComponentUpdate方法,二就是这里的PureComponent判断

react更新前做判断是否更新的源码

if (ctor.prototype && ctor.prototype.isPureReactComponent) { //判断要不要进行浅比较
  return (
    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
  );
}

看看浅比较的方法,基于Object.is()

function shallowEqual(objA, objB) {
// 判别基本类型异同
  if (is$1(objA, objB)) { // is$1 = Object.is() 判断两个值是否相同,与强等行为基本一致
						  // 特别的Object.is(+0, -0) // false
						  // Object.is(NaN, NaN) // true
    return true;
  }
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
    return false;
  }
 // 判别引用类型
  var keysA = Object.keys(objA); // 取键值数组
  var keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  } 

  for (var i = 0; i < keysA.length; i++) {
  // hasOwnProperty$2 = Object.prototype.hasOwnProperty
  // 检测objB是否有keysA这个属性, 再对这个属性做Object.is比较,Object.is比较引用对象与强等一致,都是比较引用地址的
    if (!hasOwnProperty$2.call(objB, keysA[i]) || !is$1(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }

  return true;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值