整体思路
- 在
diff方法中判断要更新的virtual dom是否是组件。 - 若是组件,继续判断该组件与更新前的组件是否为同一个组件。
- 若不是,则直接渲染新组件render返回的视图。
- 若是同一个组件,则进行组件更新操作。
- 组件更新的过程,其实就是获取最新的props,然后通过组件的render得到最新的virtual dom,最后通过
diff找出与原有的差异,并更新到视图上。 - 组件更新的过程中,组件实例需要在不同阶段调用生命周期函数。
代码实现
- 在
diff方法中增加处理virtual dom是组件的逻辑分支,交由diffComponent统一处理。diffComponent的四个参数分别为:vdom: 组件本身的virtual dom,通过它可获得最新的props。oldComponent: 要更新的组件的实例对象,通过它可- 调用组件的生命周期函数
- 更新组件的props
- 调用render获取最新的视图virtual dom
oldDOM: 要更新的DOM对象。可通过它获取原来的virtual dom,与新的virtual dom比对diff后,实现DOM的最小化更新。container: 容器,在渲染的是一个新组件的情况下,直接将新组件视图内容渲染到容器内
export default function diff(vdom, container, oldDOM) {
const oldVDom = oldDOM && oldDOM._vdom;
// 老
const oldComponent = oldVDom?.component;
if (!oldDom) {
// 处理挂载阶段...
} else if (oldVDom && vdom.type === oldVDom.type) {
// 处理非组件virtual dom(相同type)...
} else if (vdom.type !== oldVDom.type && !isComponent(vdom)) {
// 处理非组件virtual dom(不同type)...
} else if (isComponent(vdom)) {
// 处理组件virtual dom
diffComponent(vdom, oldComponent, oldDOM, container)
}
}
export default function diffComponent (vdom, oldComponent, oldDOM, container) {
// ...
}
- 在
diffComponent中通过isSameComponent判断要更新的组件与原有组件是否是同一个。 若不是同个组件,也就是说,渲染的是一个全新组件,那么直接渲染该组件即可。
/**
* 判断新旧组件是否相同
* @param {*} vdom 新的virtual dom(其type属性,指向组件类)
* @param {*} oldComponent 老的组件实例
* @returns
*/
function isSameComponent (vdom, oldComponent) {
// virtual dom.type === 老组件的构造器
return oldComponent && oldComponent.constructor === vdom.type;
}
export default function diffComponent (vdom, oldComponent, oldDOM, container) {
if (isSameComponent(vdom, oldComponent)) {
// 在原组件基础上的更新...
} else {
// 渲染一个全新组件
mountElement(vdom, container, oldDOM)
}
}
需要补充的是,在视图更新时,原来的DOM是需要在 mountNativeElement 中手动移除的
export default function mountNativeElement(vdom, container, oldDOM) {
if (oldDOM) {
unMountNode(oldDOM);
}
// 使用DOM方法挂载元素...
}
- 若是原组件的更新,则在
diffComponent内调用updateComponent方法进行处理。 同时,组件实例需要具备调用生命周期函数的能力,可以预先定义在父类Component上,子类可以选择覆盖与否。
export default function diffComponent (vdom, oldComponent, oldDOM, container) {
if (isSameComponent(vdom, oldComponent)) {
updateComponent(virtualDOM, oldComponent, oldDOM, container)
}
// ...
}
export default class Component {
// 生命周期函数
componentWillMount() {}
componentDidMount() {}
componentWillReceiveProps(nextProps) {}
shouldComponentUpdate(nextProps, nextState) {
return nextProps != this.props || nextState != this.state;
}
componentWillUpdate(nextProps, nextState) {}
componentDidUpdate(prevProps, preState) {}
componentWillUnmount() {}
// 其他方法...
}
- 在
updateComponent中,主要进行:- 生命周期函数的调用
- 获取最新的render virtual dom并通过调用
diff进行更新操作。
export default function updateComponent (vdom, oldComponent, oldDOM, container) {
const nextProps = vdom.props;
const nextState = oldComponent.state;
const prevProps = oldComponent.props;
const shouldComponentUpdate = oldComponent.shouldComponentUpdate(nextProps, nextState);
oldComponent.componentWillReceiveProps(nextProps);
if (shouldComponentUpdate) {
oldComponent.componentWillUpdate(nextProps, nextState);
oldComponent.updateProps(nextProps);
const virtualDOM = oldComponent.render();
virtualDOM.component = oldComponent;
diff(virtualDOM, container, oldDOM);
oldComponent.componentDidUpdate(prevProps);
}
}
Component 需要新增一个 updateComponent 方法
export default class Component {
updateProps(props) {
this.props = props
}
// ...
}