React Mini版实现(1)

Favori,

Mini React 1

图:Vivivian

准备

JSX

React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

表面上像 HTML,本质上还是通过 babel 转换为 js 执行。jsx 就是一段 js,只是写成了 html 的样子,而我们读取他的时候,jsx 会自动转换成 vnode 对象给我们,这里都由 react-script 的内置的 babel 帮助我们完成。

return <div>Hello Word</div>;
 
//实际上是:
 
return React.createElement("div", null, "Hello");

JSX 本质上就是转换为 React.createElement 在 React 内部构建虚拟 Dom,最终渲染出页面。

虚拟 Dom

先看一个最简单的 react 组件渲染

import React from 'react'
import ReactDOM from 'react-dom'
function App(props){
     return <div>你好</div>
 </div>
}
ReactDOM.render(<App/>,  document.getElementById('root'))

在上述的 js 文件中,我们使用了 jsx。但是 jsx 的编译是需要 react 的,所以不引用 react 就会报错。 react 的作用,就是把 jsx 转换为虚拟dom对象。

JSX 本质上就是转换为React.createElement在 React 内部构建虚拟 Dom,最终渲染出页面。

  • React 负责逻辑控制,数据 -> VDOM
  • ReactDom 渲染实际 DOM,VDOM -> DOM

React 将 jsx 转换为“虚拟 dom”对象。我们再利用 ReactDom 的虚拟 dom 通过 render 函数,转换成 dom。再通过插入到我们的真是页面中。

实现

下面我们来看下实现

react 的功能化问题,暂时不考虑。例如,启动 react,怎么去识别 JSX,实现热更新服务等等,我们的重点在于 react 自身。我们借用一下一下 react-scripts 插件。

初始化项目

新建 package.json

{
  "name": "react_mini",
  "scripts": {
    "start": "react-scripts start"
  },
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react-scripts": "3.4.1"
  }
}
npm i

新建 public/index.js, src/index.js, src/react.js, src/react.dom.js

最终目录结构如下

.
├── package.json
├── public
│   └── index.html
└── src
    ├── index.js
    ├── react-dom.js
    └── react.js

index.html

react-scripts 会起一个 server 加载这个 html

<!DOCTYPE html>
<html lang="en">
  <head> </head>
  <body>
    <div id="root"></div>
  </body>
</html>

index.js

react-scripts 会以这个文件为入口加载 js 这个文件写一些我们的测试代码

import ReactDOM from "./react-dom";
import React from "./react.js";
 
// 类组件示例
class MyClassCmp extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <div className="class_2">这是{this.props.name}</div>;
  }
}
 
//函数组件示例
function MyFuncCmp(props) {
  return <div className="class_1">这是{props.name}</div>;
}
 
const hello = () => {
  alert("Hello Mini React");
};
 
let jsx = (
  <div>
    <h1>Mini React</h1>
    <div className="className1" onClick={hello}>
      <div>This is my Mini React</div>
      <MyFuncCmp name="函数组件" />
      <MyClassCmp name="类组件" />
    </div>
  </div>
);
 
ReactDOM.render(jsx, document.getElementById("root"));

react.js

以下就是 react 的 mini 版实现

/**
 * 创建虚拟dom函数
 *
 * @param {*} type // 节点类型
 * @param {*} props // 属性参数
 * @param {*} children // 子组件
 * @return {*}
 */
function createElement(type, props, ...children) {
  console.log("type", type);
  props.children = children;
  let vtype;
  if (typeof type === "string") {
    // 判断类型为string则是原生html类型
    vtype = 1;
  }
  if (typeof type === "function") {
    // 判断是否是函数组件或类组件
    vtype = type.isReactComponent ? 2 : 3; // 判断是函数组件还是类组件
  }
  return {
    // 返回虚拟dom节点
    vtype, // 虚拟dom类型
    type, // 节点类型
    props, // 属性参数
  };
}
 
//类组件定义
class Component {
  static isReactComponent = true;
  constructor(props) {
    this.props = props;
    this.state = {};
  }
  setState = () => {};
}
 
export default {
  createElement,
  Component,
};

react-dom.js

以下就是react-dom的迷你版实现

//渲染函数
function render(vnode, container) {
  mount(vnode, container);
}
 
//主挂载函数
function mount(vnode, container) {
  const { vtype } = vnode;
  if (!vtype) {
    //处理文本节点
    mountTextNode(vnode, container);
  }
  if (vtype === 1) {
    //处理原生标签
    mountHtml(vnode, container);
  }
  if (vtype === 3) {
    //处理函数组件
    mountFunc(vnode, container);
  }
  if (vtype === 2) {
    //处理class组件
    mountClass(vnode, container);
  }
}
 
//文本节点挂载函数
function mountTextNode(vnode, container) {
  const node = document.createTextNode(vnode);
  container.appendChild(node);
}
 
//原生节点挂载函数
function mountHtml(vnode, container) {
  const { type, props } = vnode;
  const node = document.createElement(type); //创建一个真实dom
  const { children, ...rest } = props;
  children.map((item) => {
    //子元素递归
    if (Array.isArray(item)) {
      item.map((c) => {
        mount(c, node);
      });
    } else {
      mount(item, node);
    }
  });
  Object.keys(rest).map((item) => {
    if (item === "className") {
      node.setAttribute("class", rest[item]);
    }
    if (item.slice(0, 2) === "on") {
      node.addEventListener("click", rest[item]);
    }
  });
  container.appendChild(node);
}
 
//函数组件挂载函数
function mountFunc(vnode, container) {
  const { type, props } = vnode;
  const node = new type(props);
  mount(node, container);
}
 
//类组件挂载函数
function mountClass(vnode, container) {
  const { type, props } = vnode;
  const node = new type(props);
  mount(node.render(), container);
}
 
export default {
  render,
};

验证效果

以上就实现了mini react的渲染