从Preact了解一个类React的框架是怎么实现的(一): 元素创建
首先欢迎大家关注我的掘金账号和Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。
其实我在阅读React源码的时候,真的非常痛苦。React的代码及其复杂、庞大,阅读起来挑战非常大,但是这却又挡不住我们的React的原理的好奇。前段时间有人就安利过Preact,千行代码就基本实现了React的绝大部分功能,相比于React动辄几万行的代码,Preact显得别样的简洁,这也就为了我们学习React开辟了另一条路。本系列文章将重点分析类似于React的这类框架是如何实现的,欢迎大家关注和讨论。如有不准确的地方,欢迎大家指正。
我们用Preact编写代码就雷同于React,比如举个例子: import { Component,h } from 'preact' export default class TodoList extends Component { state = { todos: [],text: '' }; setText = e => { this.setState({ text: e.target.value }); }; addTodo = () => { let { todos,text } = this.state; todos = todos.concat({ text }); this.setState({ todos,text: '' }); }; render({ },{ todos,text }) { return ( <form onSubmit={this.addTodo} action="javascript:"> <input value={text} onInput={this.setText} /> <button type="submit">Add</button> <ul> { todos.map( todo => ( <li>{todo.text}</li> )) } </ul> </form> ); } } 上面就是用Preact编写TodoList的例子,掌握React的你是不是感觉再熟悉不过了,上面的例子和React不太相同的地方是 本人还是非常推崇React这一套机制的,React这套机制提我们完成了数据和视图的绑定,使得开发人员只需要关注数据和数据流的改变,从而极大的降低的开发的关注度,使得我们能够集中精力于数据本身。而且React引入了虚拟DOM(virtual-dom)的机制,从而提升渲染性能。在开始接触React时,觉得虚拟DOM机制十分的高大上,但经过一段时间的学习,开始对虚拟DOM有了进一步的认识。虚拟DOM从本质上将就是将复杂的DOM转化成轻量级的JavaScript对象,不同的渲染中会生成同的虚拟DOM对象,然后通过高效优化过的Diff算法,比较前后的虚拟DOM对象,以最小的变化去更新真实DOM。
正如上面的图,其实类React的框架的代码都基本可以分为两部分,组件到虚拟DOM的转化、以及虚拟DOM到真实DOM的映射。当然细节性的东西还有非常多,比如生命周期、事件机制(代理)、批量刷新等等。其实Preact精简了React中的很多部分,比如React中采用的是事件代理机制,Preact就没这么做。这篇文章将着重于叙述Preact的JSX与组件相关的部分代码。 import ReactDOM from 'react-dom' const App = (props)=>(<div>Hello World</div>) ReactDOM.render(<APP />,document.body); 请问可以执行吗?事实上是不能只能的,浏览器会告诉你:
如果你不了解JSX你就会感觉奇怪,因为没有地方显式地调用React,但是事实上上面的代码确实用到了React模块,奥秘就在于JSX。JSX其实相当于JavaScript + HTML(也被称为hyperscript,即hyper + script,hyper是HyperText超文本的简写,而script是JavaScript的简写)。JSX并不属于新的语法,其目的也只是为了在JavaScript脚本中更方便的构建UI视图,相比于其他的模板语言更加的易于上手,提升开发效率。上面的实例如果经过Babel转化其实会得到下面结果: var App = function App(props) { return React.createElement( 'div',null,'Hello World' ); }; 我们可以看到,之前的JSX语法都被转换成函数 /** @jsx h */ let foo = <div id="foo">Hello!</div>; 我们通过为JSX添加注释 /** @jsx h */ var foo = h( "div",{ id: "foo" },"Hello!" ); 当然在每个JSX上都设置Pragma是没有必要的,我们可以在工程全局进行配置,比如我们可以在Babel6中的 { "plugins": [ ["transform-react-jsx",{ "pragma":"h" }] ] } 这样工程中所有用到JSX的地方都是被Babel转化成使用 import {VNode} from './vnode'; const stack = []; const EMPTY_CHILDREN = []; 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; return p; } 函数
render(){ return( <ul> { [1,2,3].map((val)=><li>{val}</li>) } </ul> ) }
if (simple && lastSimple) { children[children.length - 1] += child; } 其实做的就是一个字符串拼接,lastSimple是用来记录上次的节点是否是简单类型。之所以这么做,是因为某些编译器会将下面代码 let foo = <div id="foo">Hello World!</div>; 转化为: var foo = h( "div","Hello","World!" ); 这是时候
函数结束循环遍历之后,创建了一个 function VNode() {} 说了这么多,我们看几个转化之后的例子: //jsx let foo = <div id="foo">Hello World!</div>; //js var Element = h( "div","Hello World!" ); //转化为的元素节点 { nodeName: "div",children: [ "Hello World!" ],attributes: { id: "foo" },key: undefined } /* jsx class App extends Component{ //.... } class Child extends Component{ //.... } */ let Element = <App><Child>Hello World!</Child></App> //js var Element = h( App,h( Child,"Hello World!" ) ); //转化为的元素节点 { nodeName: ? App(argument),children: [ { nodeName: ? Child(argument),children: ["Hello World!"],attributes: undefined,key: undefined } ],key: undefined } 上面JSX元素转化成的JavaScript对象就是DOM在内存中的表现。在Preact中不同的数据会生成不同的虚拟DOM节点,通过比较前后的虚拟DOM节点,Preact会找出一种最简单的方式去更新真实DOM,以使其匹配当前的虚拟DOM节点,当然这会在后面的系列文章讲到,我们会将源码和概念分割成一块块内容,方便大家理解,这篇文章着重讲述了Preact的元素创建与JSX,之后的文章会继续围绕Preact类似于diff、组件设计等概念展开,欢迎大家关注我的账号获得最新的文章动态。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |