我们在使用React的时候,源码里写的都是JSX。JSX代码在运行之前会被Babel的@babel/preset-react 预设里的插件转换成JavaScript后再运行。
let div = <div className="header">hello jirengu</div>;
转换后的JavaScript
let div = React.createElement("div", {className: "header"}, "hello jirengu");
动手试试看!
上面的代码没法运行,因为目前还不存在React.createElement这个东西。我们补充代码,提前增加一个React对象,里面包含createElement方法。这个方法接收输入参数,返回一个对象。
const React = { createElement(tag, attrs, ...children) { return { tag, attrs, children } } }; //let div = React.createElement("div", {className: "header"}, "hello jirengu"); let div = <div className="header">hello jirengu</div>; console.log(div); /* { tag: "div", attrs:{className: "header"}, children: ["hello"] } */
在(http://js.jirengu.com/zesuk/1/edit?js,console,output)看看控制台的输出。输出的这个div已经变成一个对象,包含原始JSX的全部信息。这个对象就叫做虚拟DOM(即:假的,目前还不存在的,但后面要被渲染放到页面变成真实DOM的对象)。
function render(vdom, container) { let node; if(typeof vdom === 'string') { node = document.createTextNode(vdom); } if(typeof vdom === 'object') { node = document.createElement(vdom.tag); vdom.children.forEach(childVdom => render(childVdom, node)); } setAttribute(node, vdom.attrs); container.appendChild(node); } function setAttribute(node, attrs) { for(let key in attrs) { if(key.startsWith('on')) { //<p onClick={xxx}></p> node[key.toLocaleLowerCase()] = attrs[key]; } else if(key === 'style') { //<p style={xxx}></p> Object.assign(node.style, attrs[key]); } else { node[key] = attrs[key]; //<p className={xxx}></p> } } } const ReactDom = { render(vdom, container) { container.innerHTML = ''; render(vdom, container); } };
上述代码中,render函数的作用是把虚拟DOM对象渲染后放入到container元素内。为了便于调用,我们新建一个ReactDom对象,包含一个render方法,作用是清空容器,渲染虚拟DOM到容器。
把以上代码做一个整理
const React = { createElement(tag, attrs, ...children) { return {tag, attrs, children}; } }; const ReactDom = { render(vdom, container) { container.innerHTML = ""; render(vdom, container); } }; function render(vdom, container) { let node; if (typeof vdom === "string") { node = document.createTextNode(vdom); } if (typeof vdom === "object") { node = document.createElement(vdom.tag); setAttribute(node, vdom.attrs); vdom.children.forEach((childVdom) => render(childVdom, node)); } container.appendChild(node); } function setAttribute(node, attrs) { for (let key in attrs) { if (key.startsWith("on")) { node[key.toLocaleLowerCase()] = attrs[key]; } else if (key === "style") { Object.assign(node.style, attrs[key]); } else { node[key] = attrs[key]; } } } //测试代码 let str = "jirengu"; let styleObj = { color: "red", fontSize: "30px" }; ReactDom.render( <div className="wrap"> Hello {str} <button className="btn" onClick={() => console.log("click me")}> Click me! </button> <p style={styleObj}>I have style</p> </div>, document.body );
可在 http://js.jirengu.com/zesuk/2/edit?js,console,output 运行测试效果。
以上代码是一个最粗浅的React雏形。自己后续可尝试实现Class组件、setState、函数组件、DOM diff、Hooks等。
实现简易React源码: https://github.com/jirengu/OneReact