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
, callback
。partialState可以是对象,也可以是函数,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;
}