Preact 源码解析系列一 :简单DOM渲染

用过Preact的人都知道,在每个JS文件之前,都需要写 /** @jsx h */,那这句话是什么作用呢?

先从JSX说起。

解析与编译

JSX实际上就是一个语法糖,让我们能以下简单明了的形式来定义UI长什么样,但是它需要编译器帮助编译,这里使用Babel。

Before:

/** @jsx h */
let foo = <div id="foo">Hello!</div>;

/** @jsx h */ 这句话就是为了告诉编辑器,每个解析后的jsx节点都需要调用h这个函数解析。

经过Babel编译之后就变成了,

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

h()

接下来看看 h 做了一件什么事,它其实相当于react.createElement(),用于创建虚拟DOM,而所谓的虚拟DOM实际上就是一个JS对象,它长这样

{
    nodeName: "div",
    attributes: {'id': 'foo'},
    children: ['hello']
}

我们给它一个类装着

function Vnode(nodeName, attributes, children) {
  this.nodeName =  nodeName;
  this.attributes = attributes;
  this.children = children;
}

其实h的源码很简单,就是把后面的child做成数组,给children,返回一个虚拟DOM对象。

function h(nodeName, attributes, ...args) {  
      let children = args.length ? [].concat(...args) : null;
      return new Vnode( nodeName, attributes, children );
}

接着在render()函数里将其渲染成一个真实的DOM

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这个方法就可以渲染出想要的结果

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是如何渲染的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端知识分享

第68天:原型prototype方法

构造函数有一个prototype属性,指向实例对象的原型对象。通过同一个构造函数实例化的多个对象具有相同的原型对象。经常使用原型对象来实现继承

1042
来自专栏三丰SanFeng

字节对齐

什么是对齐,以及为什么要对齐: 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变...

2055
来自专栏前端说吧

vue-学习笔记(更新中...)

6945
来自专栏web前端

JavaScript基础学习--05自定义属性、索引值

一、自定义属性      1、读写操作 <input abc="123" type="button" value="按钮" /> ==============...

1917
来自专栏云飞学编程

美女老师带你做爬虫:BeautifuSoup库详解及实战!

模块:urllib、urllib2、BeautifuSoup4模块(解析器lxml、html)

931
来自专栏快乐八哥

前端知识小结

1. var a=null==undefined?1:"abc"; var b=typeof(a); var c=typeof(b); var d=typeof...

1699
来自专栏偏前端工程师的驿站

JS魔法堂:追忆那些原始的选择器

一、前言                                                                            ...

2137
来自专栏木子昭的博客

Javascript常用API备忘录

Bom常见API 获取浏览器信息 var ua = navigator.userAgent if (ua.indexOf('Chrome')){ co...

2783
来自专栏Keegan小钢

Android技术积累:开发规范

上个月发布了Android项目重构的三篇系列文章,其中,界面篇中提到了在项目中保持规范性的重要性,也有简单举了几个例子。这篇文章则将其延伸,提供更完整的开发规范...

1012
来自专栏前端儿

使用BEM命名规范来组织CSS代码

在本规范中,以双下划线 __ 来作为块和元素的间隔,以单下划线 _ 来作为块和修饰器 或 元素和修饰器 的间隔,以中划线 - 来作为 块|元素|修饰器 名称中多...

1325

扫码关注云+社区