react的火热程度已经达到了94.5k个start,本系列文章主要用简单的代码来实现一个react,来了解JSX、虚拟DOM、diff算法以及state和setState的设计。javascript
提到react,固然少不了vue,vue的api设计十分简单 上手也很是容易,但黑魔法不少,使用起来有点虚, 而react没有过多的api,它的深度体如今设计思想上,使用react开发则让人比较踏实、能拿捏的住,这也是我喜欢react的缘由之一。css
写react
怎么少的了JSX
,JSX
是什么,让我来看个例子
如今有下面这段代码:html
const el = <h3 className="title">Hello Javascript</h3>
这样的js代码若是不通过处理会报错,jsx是语法糖,它让这段代码合法化,经过babel转化后是这样的:vue
const el = React.createElement( 'h3', { className: 'title' }, 'Hello Javascript' )
这种例子官网首页也有demojava
开始编码以前,先介绍两个东西:parcel
和babel-plugin-transform-jsx
,等会咱们用parcel搭建一个开发工程,babel-plugin-transform-jsx
是babel
的一个插件,它能够将jsx
语法转成React.createElement(...)
。node
下面咱们开始react
parcel这里就不介绍了,一句话概况就是为你生成一个零配置的开发环境。git
yarn global add parcel-bundler
或 npm install -g parcel-bundler
simple-react
simple-react
中执行 yarn init -y
或 npm init -y
生成package.json
index.html
src
文件夹 再在src
下建立index.js
而后再index.html
中引入index.js
若是你先麻烦,能够直接下载源码修改。github
以上步骤完可能不完整,最好参考parcel里的内容。以上工做完成后,咱们须要安装babel-plugin-transform-jsx
:算法
npm insatll babel-plugin-transform-jsx --save-dev 或者 yarn add babel-plugin-transform-jsx --dev
而后添加.babelrc
文件,并在该文件中加入下面这段代码:
{ "presets": ["env"], "plugins": [["transform-jsx", { "function": "React.createElement" }]] }
上面代码的意思是 使用transform-jsx
插件,并配置为经过React.createElement
方法来解析JSX
,固然你也能够不用React.createElement
和自定义方法,好比preact
使用的h
方法。
如今咱们在index.js
里开始编码。
首先写入代码:
const el = <h3 className="title">Hello Javascript</h3>; console.log(el);
咱们在什么都不写的状况下,打印看看el
是什么。
打印报错:React没有定义。 这是由于在.babelrc
文件中,咱们使用的这段代码起了做用:
["transform-jsx", { "function": "React.createElement" }]
上面说过,它会经过React.createElement
方法来转译JSX,那么咱们就给出这个方法:
咱们把刚才那段代码改变一下:
const React = { createElement: function(...args) { return args[0]; } }; const el = <h3 className="title">Hello Javascript</h3>; console.log(el);
上面代码添加了一个React
对象,并在其中添加一个createElement
方法,如今再执行一下看看打印出什么:
由打印结果能够看出,jsx
在使用React.createElement
方法转译时,createElement
方法应该是这样的:
createElement({ elementName, attributes, children });
如今咱们改写一下createElement
方法,让key的名称简单一点:
const React = { createElement: function({ elementName, attributes, children }) { return { tag: elementName, attrs: attributes, children }; } };
如今能够看到打印结果是:
咱们再打印个复杂点的DOM结构:
const el = ( <div style="color:red;"> Hello <span className="title">JavaScript</span> </div> ); console.log(el);
和咱们想要的结构同样。
其实上面打印出来的就是虚拟DOM
,如今咱们要作的就是如何把虚拟DOM
转成真正的DOM
对象并显示在浏览器上。
要想将虚拟dom转成真实dom并渲染到页面上,就须要调用ReactDOM.render
,好比:
ReactDOM.render(<h1>Hello World</h1>, document.getElementById('root'));
这段代码转换后的样子:
ReactDOM.render( React.createElement('h1', null, 'Hello World'), document.getElementById('root') );
这时,react会将<h1>Hello World</h1>
挂载到id为root的dom下,从而在页面上显示出来。
如今咱们实现render
方法:
function render(vnode, container) { const dom = createDom(vnode); //将vnode转成真实DOM container.appendChild(dom); }
上面代码中先调用createDom
将虚拟dom转成真实DOM
,而后挂载到container
下。
咱们来实现createDom
方法:
function createDom(vnode) { if (vnode === undefined || vnode === null || typeof vnode === 'boolean') { vnode = ''; } if (typeof vnode === 'string' || typeof vnode === 'number') { return document.createTextNode(String(vnode)); } const dom = document.createElement(vnode.tag); //设置属性 if (vnode.attrs) { for (let key in vnode.attrs) { const value = vnode.attrs[key]; setAttribute(dom, key, value); } } //递归render子节点 vnode.children.forEach(child => render(child, dom)); return dom; }
因为属性的种类比较多,咱们抽出一个setAttribute方法来设置属性:
function setAttribute(dom, key, value) { //className if (key === 'className') { dom.setAttribute('class', value); //事件 } else if (/on\w+/.test(key)) { key = key.toLowerCase(); dom[key] = value || ''; //style } else if (key === 'style') { if (typeof value === 'string') { dom.style.cssText = value || ''; } else if (typeof value === 'object') { // {width:'',height:20} for (let name in value) { //若是是数字能够忽略px dom.style[name] = typeof value[name] === 'number' ? value[name] + 'px' : value[name]; } } //其余 } else { dom.setAttribute(key, value); } }
如今render
方法已经完整的实现了,咱们将建立ReactDOM对象,将render方法挂上去:
const ReactDOM = { render: function(vnode, container) { container.innerHTML = ''; render(vnode, container); } };
这里在调用render
以前加了一句container.innerHTML = ''
,就不解释了,相信你们都明白。
那么万事具有,咱们来测试一下,直接上一个比较复杂的dom结构并加上属性:
const element = ( <div className="Hello" onClick={() => alert(1)} style={{ color: 'red', fontSize: 30 }} > Hello <span style={{ color: 'blue' }}>javascript!</span> </div> ); ReactDOM.render(element, document.getElementById('root'));
打开页面,是咱们想要的结果:
再看看控制台的dom:
很完美,这是咱们想要的东西
该demo代码在这里哟~~
本文叙述了JSX和虚拟DOM,以及将虚拟DOM转成真实DOM的过程,后面的文章会继续叙述react中的组件、生命周期、diff算法和异步setState,敬请期待~