react中将更新抽象成了一个类Updater
如果某个场合下,用户触发点击时回调执行了setState,例如:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { number: 0, age: 10 };
}
handleClick = (event) => {
this.setState({ number: this.state.number + 1 });
}
下面的setState就会触发
class Component {
static isReactComponent = true //当子类继承父类的时候 ,父类的静态属性也是可以继承的
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState) {
this.updater.addState(partialState);
this.emitUpdate();
}
emitUpdate() {
this.updateComponent();
}
updateComponent() {
const { classInstance, pendingStates } = this;
if (pendingStates.length > 0) {
shouldUpdate(classInstance, this.getState());
}
}
getState() {
const { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach(partialState => {
state = { ...state, ...partialState }
});
pendingStates.length = 0;
return state;
}
//根据新的属性状态计算新的要渲染的虚拟DOM
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;//上一次类组件render方法计算得到的虚拟DOM
//let oldDOM = oldRenderVdom.dom;
let oldDOM = findDOM(oldRenderVdom);//获取 oldRenderVdom对应的真实DOM
//然后基于新的属性和状态,计算新的虚拟DOM
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
}
}
function shouldUpdate(classInstance, nextState) {
classInstance.state = nextState;//先把新状态赋值给实例的state
classInstance.forceUpdate();//强制更新
}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];//等待生效的数组
//this.callbacks = [];
}
addState(partialState) {
this.pendingStates.push(partialState);
this.emitUpdate();// 触发更新
}
emitUpdate() {
this.updateComponent();
}
updateComponent() {
const { classInstance, pendingStates } = this;
if (pendingStates.length > 0) {
shouldUpdate(classInstance, this.getState());
}
}
getState() {
const { classInstance, pendingStates } = this;
let { state } = classInstance;//{number:0} 老状态
pendingStates.forEach((partialState) => {//和每个分状态
state = { ...state, ...partialState }
});
pendingStates.length = 0;//清空等待生效的状态 的数组
return state;
}
}
react-dom -> findDOM
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {//vdom={type:'h1'}
return vdom.dom;
} else {
//类组件 还是函数组件,他们虚拟DOM身上没有dom属性,但是oldRenderVdom
return findDOM(vdom.oldRenderVdom);
}
}
上述代码中,需要注意,在forceUpdate中,调用了findDOM(oldRenderVdom)获取虚拟DOM对应的真实DOM,oldRenderVdom有以下方式生成:
1、ReactDOM.render -> mount(vdom, container) -> createDOM(vdom)
2、调用类组件的render方法
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
3、直接调用函数组件,返回值就是oldRenderVdom
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
可以看到得到vdom或renderVdom之后,都通过调用createDOM将其渲染为真实DOM,而在createDOM里面,我们在最后将创建出来的dom挂到了vdom或renderVdom上:
export function createDOM(vdom) {
if (!vdom) return null;
let { type, props } = vdom;
let dom;//真实DOM
if (type === REACT_TEXT) {//如果这个元素是一个文本的话
dom = document.createTextNode(props.content);
} else if (typeof type === 'function') {//如果类型是一个函数的话
if (type.isReactComponent) {//说明它是一个类组件
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);// div span p
}
//处理属性
if (props) {
updateProps(dom, {}, props);
if (props.children) {
let children = props.children;
if (typeof children === 'object' && children.type) {//说明这是一个React元素
mount(children, dom);
} else if (Array.isArray(children)) {
reconcileChildren(props.children, dom);
}
}
}
vdom.dom = dom;//让虚拟DOM的dom属性指向这个虚拟DOM对应的真实DOM
return dom;
}
不过需要注意,如果进入createDOM后走了mountClassComponent或者mountFunctionComponent分支,是不会执行vdom.dom = dom的,也就是说,类组件和函数组件对应的虚拟DOM对象上没有dom属性来表示它所对应的真实DOM
但是在mountClassComponent和mountFunctionComponent中我们也可以看到,vdom上挂了oldRenderVdom这个属性,通过它我们就可以得到类组件或函数组件渲染出的DOM对象了
function mountClassComponent(vdom) {
let { type: ClassComponent, props } = vdom;
let classInstance = new ClassComponent(props);
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}