从零开始实现一个React(一):JSX和虚拟DOM
前言React是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读React:从零开始实现一个React,从API层面实现React的大部分功能,在这个过程中去探索为什么有虚拟DOM、diff、为什么setState这样设计等问题。 提起React,总是免不了和Vue做一番对比 Vue的API设计非常简洁,但是其实现方式却让人感觉是“魔法”,开发者虽然能马上上手,但是为什么能实现功能却很难说清楚。 相比之下React的设计哲学非常简单,虽然经常有需要自己处理各种细节问题,但是却让人感觉它非常“真实”,能清楚地感觉到自己仍然是在写js。 关于jsx在开始之前,我们有必要搞清楚一些概念。 我们来看一下这样一段代码: const title = <h1 className="title">Hello,world!</h1>; 这段代码并不是合法的js代码,它是一种被称为jsx的语法扩展,通过它我们就可以很方便的在js代码中书写html片段。 本质上,jsx是语法糖,上面这段代码会被babel转换成如下代码 const title = React.createElement( 'h1',{ className: 'title' },'Hello,world!' ); 你可以在babel官网提供的在线转译测试jsx转换后的代码,这里有一个稍微复杂一点的例子 准备工作为了集中精力编写逻辑,在代码打包工具上选择了最近火热的零配置打包工具parcel,需要先安装parcel: npm install -g parcel-bundler 接下来新建 当然,有一个更简单的方法,你可以直接下载这个仓库的代码: https://github.com/hujiulong/... 注意一下babel的配置 { "presets": ["env"],"plugins": [ ["transform-react-jsx",{ "pragma": "React.createElement" }] ] } 这个 准备工作完成后,我们可以用命令 React.createElement和虚拟DOM前文提到,jsx片段会被转译成用 从jsx转译结果来看,createElement方法的参数是这样: createElement( tag,attrs,child1,child2,child3 ); 第一个参数是DOM节点的标签名,它的值可能是 我们对createElement的实现非常简单,只需要返回一个对象来保存它的信息就行了。 function createElement( tag,...children ) { return { tag,children } } 函数的参数 现在我们来试试调用它 // 将上文定义的createElement方法放到对象React中 const React = { createElement } const element = ( <div> hello<span>world!</span> </div> ); console.log( element ); 打开调试工具,我们可以看到输出的对象和我们预想的一致
我们的createElement方法返回的对象记录了这个DOM节点所有的信息,换言之,通过它我们就可以生成真正的DOM,这个记录信息的对象我们称之为虚拟DOM。 ReactDOM.render接下来是ReactDOM.render方法,我们再来看这段代码 ReactDOM.render( <h1>Hello,world!</h1>,document.getElementById('root') ); 经过转换,这段代码变成了这样 ReactDOM.render( React.createElement( 'h1',null,world!' ),document.getElementById('root') ); 所以 总而言之,render方法的作用就是将虚拟DOM渲染成真实的DOM,下面是它的实现: function render( vnode,container ) { // 当vnode为字符串时,渲染结果是一段文本 if ( typeof vnode === 'string' ) { const textNode = document.createTextNode( vnode ); return container.appendChild( textNode ); } const dom = document.createElement( vnode.tag ); if ( vnode.attrs ) { Object.keys( vnode.attrs ).forEach( key => { if ( key === 'className' ) key = 'class'; // 当属性名为className时,改回class dom.setAttribute( key,vnode.attrs[ key ] ) } ); } vnode.children.forEach( child => render( child,dom ) ); // 递归渲染子节点 return container.appendChild( dom ); // 将渲染结果挂载到真正的DOM上 } 这里注意React为了避免类名 这里其实还有个小问题:当多次调用 const ReactDOM = { render: ( vnode,container ) => { container.innerHTML = ''; return render( vnode,container ); } } 渲染和更新到这里我们已经实现了React最为基础的功能,可以用它来做一些事了。 我们先在index.html中添加一个根节点 <div id="root"></div> 我们先来试试官方文档中的Hello,World ReactDOM.render( <h1>Hello,document.getElementById('root') ); 可以看到结果: 试试渲染一段动态的代码,这个例子也来自官方文档 function tick() { const element = ( <div> <h1>Hello,world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element,document.getElementById( 'root' ) ); } setInterval( tick,1000 ); 可以看到结果: 后话这篇文章中,我们实现了React非常基础的功能,也了解了jsx和虚拟DOM,下一篇文章我们将实现非常重要的组件功能。 最后留下一个小问题 例如: import React from 'react'; // 下面的代码没有用到React对象,为什么也要将其import进来 import ReactDOM from 'react-dom'; ReactDOM.render( <App />,document.getElementById( 'editor' ) ); 不知道答案的同学再仔细看看这篇文章哦 从零开始实现React系列React是前端最受欢迎的框架之一,解读其源码的文章非常多,但是我想从另一个角度去解读React:从零开始实现一个React,从API层面实现React的大部分功能,在这个过程中去探索为什么有虚拟DOM、diff、为什么setState这样设计等问题。 整个系列大概会有六篇左右,我每周会更新一到两篇,我会第一时间在github上更新,有问题需要探讨也请在github上回复我~ 博客地址: https://github.com/hujiulong/blog (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |