前言

目前的前端框架可谓是风头正盛,React,Vue等框架已经成为了极度火热的话题。而我在使用过这两个框架之后对这些框架的原理产生了巨大的兴趣。这次我选择了preact 进行阅读解析,想为自己揭开框架所包含的技术原理。

Prect是一个轻量化的类React框架,能够使用React的绝大部分生态库,并具有gzip之后lib只有4k的巨大优势。

JSX

在开始之前,首先让我们先了解一下JSX,JSX作为React的主体模版语法,具有HTML+Script的特性,所以他又称为hyperscript。

那么,一个JSX是如何变成真正能放置到网页中的DOM节点的呢?实质上这个过程首先依赖的是语言编译器,比如babel。

如果我们在一个JS文件的头部设置一些给babel的提醒,一个JSX就能正确的被编译成一个函数,就像下面这样

/** @jsx h 意味编译成一个名为h的函数*/
<div id="foo">Hello!</div>;  

TO

h('div', {id:"foo"}, 'Hello!');  

也就是说只要通过一个函数来接受这些数据,我们就能用他来进行进一步的处理,让我继续往下走,解析preact的h函数,看看他是如何处理的。

h

简单来讲,preact的h函数会接收如上结构,并把它们创建成一个VNode(以供diff)对象,假设我们创建一个这样的JSX结构

let foo = <div id="foo">Hello!</div>;  

就能获取如下的对象

let VNode = {
  nodeName:'div'
  children:['hello'],
  attributes:{id:"foo"}
}
export function h(nodeName, attributes) {
	let children = EMPTY_CHILDREN, lastSimple, child, simple, i;
	
	// 存放所有子元素 入栈
	for (i=arguments.length; i-- > 2; ) {
		stack.push(arguments[i]);
	}
	if (attributes && attributes.children!=null) {
		if (!stack.length) stack.push(attributes.children);
		delete attributes.children;
	}
	// 处理子元素
	while (stack.length) {
		// 如果子元素为数组,置入栈中,等候处理
		if ((child = stack.pop()) && child.pop!==undefined) {
			for (i=child.length; i--; ) stack.push(child[i]);
		}
		else {
			if (typeof child==='boolean') child = null;
			
			// 处理子元素为简单值,如文本,数字
			if ((simple = typeof nodeName!=='function')) {
				if (child==null) child = '';
				else if (typeof child==='number') child = String(child);
				else if (typeof child!=='string') simple = false;
			}
			
			// 这个元素和上一个元素皆为简单值 合并他们
			if (simple && lastSimple) {
				children[children.length-1] += child;
			}
			else if (children===EMPTY_CHILDREN) {
				children = [child];
			}
			else {
				children.push(child);
			}

			lastSimple = simple;
		}
	}

	let p = new VNode();
	p.nodeName = nodeName;
	p.children = children;
	p.attributes = attributes==null ? undefined : attributes;
	p.key = attributes==null ? undefined : attributes.key;

	// if a "vnode hook" is defined, pass every created VNode to it
	if (options.vnode!==undefined) options.vnode(p);

	return p;
}

其实这个函数并没有太复杂的结构,仅仅只是处理了一些边界情况,并且进行的一部分的优化。

OK,现在我们已经明白了JSX的原理,那么,我们如何把这些虚拟节点插入到DOM文档之中呢,就需要Render函数处理他,并进行虚拟DOM的diff计算。