React 虚拟dom是如何实现的

来源:互联网 发布:java属性签名是什么 编辑:程序博客网 时间:2024/05/16 03:52

JSX是比较简单的:花一分钟时间阅读本篇文章,你将会理解有关模板的有趣替代方案的所有内容。

替代标题:和JSX在一起

指令

你可以声明每个文件或每个函数来告诉你的转换器(如:Babel)每个节点在运行时应该调用的函数名称(参见:Transpilation)

/** @jsx h **/ 

转换

如果你还没有使用转换器,对,你可能就是。使用ES6/ES2015时,在开发、调试、测试以及运行JavsScript会更加高效。而这样的场景下Babel是一个非常流行且极力推荐的转换器,所以,我在这里我将假设你正在使用它。

除了将ES6/ES7+语法转换为当下各个浏览器都支持的JavaScript,Babel在转换JSX时也非常方便,你不需要添加或修改任何内容就可以使用此功能。

通过一个非常简单的例子可以很容易的看出这是如何实现的:

Before:(转换前你写的代码)

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

After:(转换后的可运行代码)

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

看到第二个代码片段后你可能会想通过函数的方式构建UI还不是很差。。。

这就是为什么我刚开始使用JSX的想法:如果JSX从地球上消失,用手写的输出依然很舒服。

JSX只是一个很不错的语法糖
有人甚至用它来实现了一个项目:hyperscript

一起来实现一个JSX渲染器

首先,我们需要定义一个在转换后代码中调用的h()函数。

你也可以定义一个任何你想要的名字,我使用h()是因为这类“构建”函数最开始被称为hyperscript("hypertext"+"javascript")

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

好了,这很简单。

不熟悉ES6/ES2015?
  • ... 在参数列表中被称为rest(剩余)参数。它将“剩余”的参数收集到数组中。
  • concat(...args) 是一个扩展操作符:它拿到一个数组并把数组展开为 concat() 的参数。这里使用 concat() 是折叠子节点的任何嵌套数组。

现在我们有了这些h()函数吐出来的嵌套JSON对象,所以,我们得到这样一颗“树”:

{    nodeName: "div",    attributes: {        "id": "foo"    },    children: ["Hello!"]}

然后,我们仅需要一个接受这种格式参数并吐出实际DOM节点的函数:

function render(vnode){    // 字符串转换为#text节点    if (vnode.split) return document.createTextNode(vnode);    // 根据VDOM节点的节点名称创建一个DOM元素    let n = document.createElement(vnode.nodeName);    let a = vnode.attributes || {};    Object.keys(a).forEach( k => n.setAttribute(k, a[k]) );    // 渲染并追加到子节点列表    (vnode.children || []).forEach( c => n.appendChild(render(c)) );    return n;}

理解它是如何工作的很容易。 
如果需要,你可以把”虚拟DOM”想象成作为生成DOM结构的很简单的配置。

虚拟DOM的好处是它是个非常轻量、小对象引用其他小对象,非常容易优化的应用程序结构。 
这也意味着它不会尝试任何渲染逻辑或慢DOM操作方法。

使用JSX

我们知道JSX被转换为h()函数调用。这些函数调用创建一个简单的“虚拟”DOM树。 
我们可以使用render()函数去创建匹配的“真实”DOM树。

就像这个样子:

// JSX -> VDOMlet vdom = <div id="foo">Hello!</div>;// VDOM -> DOM;let dom = render(vdom);// 添加树到<body>document.body.appendChild(dom);

分片、迭代和逻辑:没有新语法

我们拥有所有JavaScript特性,而不是一般模板语言引入的有限的概念。

“分片”是无逻辑/有限逻辑模板引擎引入的概念,用于在不同的上下文中重用的视图片段。

迭代是每一个模板语言好像重复发明的事物(我和任何人一样有罪)。使用JSX,没有一次性的语法去学习:迭代你在JavaScript程序中的其他任何地方。您选择最适合给定任务的迭代风格:[].forEach()、[].map()、for和while循环等。

像迭代一样,逻辑也是模板语言喜欢重新发明的事物。一方面,无逻辑模板提供了将逻辑嵌入到视图中的最差的方法:诸如{{#if value}}的有限构造将逻辑推入控制器层,鼓励膨胀。这规避了构建一种描述更复杂逻辑的语言,避免可预测性和安全隐患。

另一方面,使用代码生成的引擎(一种简陋到不可原谅的技术),通常自诩具有执行逻辑甚至迭代的任意JavaScript表达式的能力。这是一个很好的理由,不惜一切代价避免这种情况:您的代码被从原始位置(可能是模块、闭包或者标记内)中剥离出来,并评估到“其他地方”。那样对我来说是不可预测或足够安全的。

JSX允许所有JavaScript语言特性,不依赖在开发阶段产生的奇形怪状的代码,而且不需要eval(),更加友好。
// 我们想展示到列表中的字符串数组let items = ['foo', 'bar', 'bza'];// 提供一些文本创建一个列表项function item(text) {    return <li>{text}</li>}// 具有“迭代”和“分片”的“视图”let list = render(    <ul>        { items.map(item) }    </ul>);

render()返回一个DOM节点(上例中的<ul>),所以,我们仅仅需要把它放入到DOM中:

document.body.appendChild(list);

合在一起

这些是上面提到的简单版的虚拟DOM渲染器和它用到的视图的完整源码。

const ITEMS = 'hello there people'.split(' ');// 将数组转换成列表项:let list = items => items.map( p => <li> {p} </li> );// 通过调用(“分片”)来从一个数值中生成列表的视图let vdom =     <div id="foo">        <p>Looka simple JSX DOM renderer!</p>        <ul>{ list(ITEMS) }</ul>    </div>);// render() 将“虚拟DOM”转换成真实的DOM树let dom = render(vdom);// 将新的DOM节点放到某个位置document.body.appendChild(dom);// 记住什么是“虚拟DOM”?它只是一个JSON - 每一个“VNode”是一个有3个属性的对象。let json = JSON.stringify(vdom, null, ' ');// 整个过程(JSX -> VDOM -> DOM)在一步完成document.body.appendChild(    render( <pre id="vdom">{ json }</pre> ));
Codepen示例
原创粉丝点击