背景
以下是手写系列内容预告,初步计划一周一个主题。
- 手写Promise、Promise.all、Promise.race
- 手写发布订阅模式
- 手写简易Redux
- 手写简易模块加载器
- 手写简易模块打包器
- 手写简易React
- 手写简易React Hooks
JSX与虚拟DOM
我们在使用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"]
}
*/
在【这里】看看控制台的输出。输出的这个div已经变成一个对象,包含原始JSX的全部信息。这个对象就叫做虚拟DOM(即:假的,目前还不存在的,但后面要被渲染放到页面变成真实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
);
可在【这里】运行测试效果。
以上代码是一个最粗浅的React雏形。自己后续可尝试实现Class组件、setState、函数组件、DOM diff、Hooks等。下方是视频教程和源码可供参考(源码可切换分支看一步一步实现的过程)。