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 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

CSS动画简介

现在,我很少写介绍CSS的文章,因为感觉网站开发的关键还是在服务器端。 但是,CSS动画除外,它实在太有用了。 ? 本文介绍CSS动画的两大组成部分:trans...

3198
来自专栏何俊林

ijkplayer框架深入剖析

不少读者很多是以ijkplayer为播放内核。因为编译简单,接口使用方便。核心部分是移植了ffplay.c中的代码。今天分享这篇文章是金山云团队,调研分析的ij...

9246
来自专栏九彩拼盘的叨叨叨

jQuery 最佳实践(译)

原文 http://shichuan.github.io/javascript-patterns/#jquery-patterns

583
来自专栏DeveWork

Web 前端利器Emmet 的CSS 用法总结

承接上文《Web 前端利器Emmet 的HTML用法总结》,这篇主要是Emmet 的 html的好基友——CSS 的用法。原文来自tutsplus,由w3cpl...

1835
来自专栏TungHsu

这或许是对小白最友好的python入门了吧——8,初识for语句

有时候我们想要使用列表中所有元素,但是如果手打又不现实,这时候我们可以用for语句来遍历整个列表,我们先举个例子,还是昨天的列表 ? 现在我们用for语句来遍历...

2716
来自专栏阮一峰的网络日志

CSS 变量教程

今年三月,微软宣布 Edge 浏览器将支持 CSS 变量。 这个重要的 CSS 新功能,所有主要浏览器已经都支持了。本文全面介绍如何使用它,你会发现原生 CSS...

33711
来自专栏跟着阿笨一起玩NET

C# WinForm登录窗口代码

Main窗体为应用程式主窗体,Login为登录窗体。均为SDI窗体。      两种实现方式如下:

1311
来自专栏天天

css推荐写法

用"-"隔开比使用驼峰是更加清晰。产品线-产品-模块-子模块,命名的时候也可以使用这种方式

832
来自专栏微信公众号:Java团长

Java内部类详解

   说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉。原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总...

921
来自专栏菩提树下的杨过

Flash/Flex学习笔记(19):颜色合成与分解的基本原理

传统的RGB颜色体系中,每一个分量值的范围都是0到255,如果转换为2进制的话最多需要8位(比如:十进制的255变成二进制则为11111111),三个分量加起来...

1928

扫码关注云+社区