目录

手写极简React

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"]
}
*/

在(http://js.jirengu.com/zesuk/1/edit?js,console,output)看看控制台的输出。输出的这个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
);

可在 http://js.jirengu.com/zesuk/2/edit?js,console,output 运行测试效果。

以上代码是一个最粗浅的React雏形。自己后续可尝试实现Class组件、setState、函数组件、DOM diff、Hooks等。

实现简易React源码: https://github.com/jirengu/OneReact