用过Preact的人都知道,在每个JS文件之前,都需要写 /** @jsx h */
,那这句话是什么作用呢?
先从JSX说起。
解析与编译
JSX实际上就是一个语法糖,让我们能以下简单明了的形式来定义UI长什么样,但是它需要编译器帮助编译,这里使用Babel。
Before:
代码语言:javascript复制/** @jsx h */
let foo = <div id="foo">Hello!</div>;
/** @jsx h */
这句话就是为了告诉编辑器,每个解析后的jsx节点都需要调用h这个函数解析。
经过Babel编译之后就变成了,
代码语言:javascript复制var foo = h('div', {id:"foo"}, 'Hello!');
h()
接下来看看 h 做了一件什么事,它其实相当于react.createElement(),用于创建虚拟DOM,而所谓的虚拟DOM实际上就是一个JS对象,它长这样
代码语言:javascript复制{
nodeName: "div",
attributes: {'id': 'foo'},
children: ['hello']
}
我们给它一个类装着
代码语言:javascript复制function Vnode(nodeName, attributes, children) {
this.nodeName = nodeName;
this.attributes = attributes;
this.children = children;
}
其实h的源码很简单,就是把后面的child做成数组,给children,返回一个虚拟DOM对象。
代码语言:javascript复制function h(nodeName, attributes, ...args) {
let children = args.length ? [].concat(...args) : null;
return new Vnode( nodeName, attributes, children );
}
接着在render()函数里将其渲染成一个真实的DOM
代码语言:javascript复制Preact = {
render: function(vNode, container) {
// 当其类型为string的时候,返回一个文本节点
if(typeof vNode === 'string') {
return document.createTextNode(vNode);
}
let{nodeName, attributes: attrs, children} = vNode;
let node;
if(typeof nodeName === 'string') {
node = document.createElement(nodeName);
if(attrs) {
// 用for in 来遍历对象的时候,使用hasOwnProperty可以很好的避免来自原型对象扩展所带来的困扰
for(let key in attrs) {
if(attrs.hasOwnProperty(key)) {
node.setAttribute(key, attrs[key]);
}
}
}
(children || []).forEach(function(child){
//使用递归渲染
node.appendChild(Preact.render(child))
})
}
container.appendChild(node)
}
}
接着,调用render这个方法就可以渲染出想要的结果
代码语言:javascript复制Preact.render(h('div',{id: 'daisy'},'hello world'), document.getElementById('container'))
接着我们就可以在浏览器里看到hello world啦
总结一下: 其实Preact的渲染的还是很简单的,整个过程就是
1、Babel解析JSX
2、h函数将解析后JSX节点转成虚拟DOM
3、render函数把它转成真实的节点
当然这个例子还很简单,对于render的element只考虑到了string跟浏览器的已有元素(div)的情况,但是没有考虑到渲染component的情况,同时也没有diff算法加快渲染。
下一节,来看渲染自定义的component是如何渲染的。