现在我们已经知道了diff与JSX到底是怎么回事?那么我们的组件是如何追踪我们的数据变化,并利用diff来进行视图更新的呢?

我相信所有人都知道React等框架的setState,其实React等框架追踪数据更新的就是这个函数,这个函数是组件对象的内置方法,他的定义如下

export function Component(props, context) {
	this._dirty = true;

	/** @public
	 *	@type {object}
	 */
	this.context = context;

	/** @public
	 *	@type {object}
	 */
	this.props = props;

	/** @public
	 *	@type {object}
	 */
	this.state = this.state || {};
}


extend(Component.prototype, {


	// 刷新状态 并且保存之前的状态到对象中,加入到重新渲染的队列
	setState(state, callback) {
		let s = this.state;
		if (!this.prevState) this.prevState = extend({}, s);
		extend(s, typeof state==='function' ? state(s, this.props) : state);
		if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
		enqueueRender(this);
	},


});

他会保存状态,并将这个组件交由一个重渲染队列处理

let items = [];

export function enqueueRender(component) {
	// 隐形的往items中push 只有当队列为空 才会加入到loop之中, 也就是避免多队列,统一交给rerender处理
	if (!component._dirty && (component._dirty = true) && items.push(component)==1) {
		// 推入到重渲染队列。如果有设置节流渲染,则使用节流。一般来说在浏览器空闲时会触发rerender
		(options.debounceRendering || defer)(rerender);
	}
}

export function rerender() {
	let p, list = items;
	items = [];
	while ( (p = list.pop()) ) {
		if (p._dirty) renderComponent(p);
	}
}

当队列接受到这个组件之后,就会在之后的某个事件对这个队列内部的所有待更新组件进行重新渲染,这时候他就要依赖renderComponent函数。这个函数他只接受一个参数,也就是需要重新渲染的组件对象,而这个组件对象内部其实包含了所有需要的内容,比如state,props,所关联的DOM。由于其内容较多,我就不贴了,感兴趣的可以阅读Peact的源码,vdom里的 components.js

renderComponent的主要职责有如下几点

  • 触发相对应的生命周期
  • 如果子组件为高阶组件()进行实例化处理,并更新他的props和DOM
  • 如果子组件为普通实例组件,递归处理
  • 利用更新的state和props去生成一个虚拟DOM树 VNode 并利用这棵树触发diff算法
  • 测试前后的差异,并进行收尾处理

总得来说,他的作用就是利用数据生成树,用树去和真实的DOM进行合并。

其实到这里已经发现绝大部分的MVVM的特性已经展露了,组件,VDOM,模版渲染,数据绑定。当然,一个真正的组件会需要考虑非常多的情景,非常多的边界情况,做出所有的平滑处理。他的难度无疑是非常高的。

最后,感谢这些框架的缔造者。