React Mini版实现(1)
准备
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的渲染