准备
JSX
React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
表面上像 HTML,本质上还是通过 babel 转换为 js 执行。jsx 就是一段 js,只是写成了 html 的样子,而我们读取他的时候,jsx 会自动转换成 vnode 对象给我们,这里都由 react-script 的内置的 babel 帮助我们完成。
1return <div>Hello Word</div>;23//实际上是:45return React.createElement("div", null, "Hello");
JSX 本质上就是转换为 React.createElement 在 React 内部构建虚拟 Dom,最终渲染出页面。
虚拟 Dom
先看一个最简单的 react 组件渲染
1import React from 'react'2import ReactDOM from 'react-dom'3function App(props){4 return <div>你好</div>5 </div>6}7ReactDOM.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
1{2 "name": "react_mini",3 "scripts": {4 "start": "react-scripts start"5 },6 "version": "0.1.0",7 "private": true,8 "dependencies": {9 "react-scripts": "3.4.1"10 }11}
1npm i
新建 public/index.js, src/index.js, src/react.js, src/react.dom.js
最终目录结构如下
1.2├── package.json3├── public4│ └── index.html5└── src6 ├── index.js7 ├── react-dom.js8 └── react.js
index.html
react-scripts 会起一个 server 加载这个 html
1<!DOCTYPE html>2<html lang="en">3 <head> </head>4 <body>5 <div id="root"></div>6 </body>7</html>
index.js
react-scripts 会以这个文件为入口加载 js 这个文件写一些我们的测试代码
1import ReactDOM from "./react-dom";2import React from "./react.js";34// 类组件示例5class MyClassCmp extends React.Component {6 constructor(props) {7 super(props);8 }9 render() {10 return <div className="class_2">这是{this.props.name}</div>;11 }12}1314//函数组件示例15function MyFuncCmp(props) {16 return <div className="class_1">这是{props.name}</div>;17}1819const hello = () => {20 alert("Hello Mini React");21};2223let jsx = (24 <div>25 <h1>Mini React</h1>26 <div className="className1" onClick={hello}>27 <div>This is my Mini React</div>28 <MyFuncCmp name="函数组件" />29 <MyClassCmp name="类组件" />30 </div>31 </div>32);3334ReactDOM.render(jsx, document.getElementById("root"));
react.js
以下就是 react 的 mini 版实现
1/**2 * 创建虚拟dom函数3 *4 * @param {*} type // 节点类型5 * @param {*} props // 属性参数6 * @param {*} children // 子组件7 * @return {*}8 */9function createElement(type, props, ...children) {10 console.log("type", type);11 props.children = children;12 let vtype;13 if (typeof type === "string") {14 // 判断类型为string则是原生html类型15 vtype = 1;16 }17 if (typeof type === "function") {18 // 判断是否是函数组件或类组件19 vtype = type.isReactComponent ? 2 : 3; // 判断是函数组件还是类组件20 }21 return {22 // 返回虚拟dom节点23 vtype, // 虚拟dom类型24 type, // 节点类型25 props, // 属性参数26 };27}2829//类组件定义30class Component {31 static isReactComponent = true;32 constructor(props) {33 this.props = props;34 this.state = {};35 }36 setState = () => {};37}3839export default {40 createElement,41 Component,42};
react-dom.js
以下就是react-dom的迷你版实现
1//渲染函数2function render(vnode, container) {3 mount(vnode, container);4}56//主挂载函数7function mount(vnode, container) {8 const { vtype } = vnode;9 if (!vtype) {10 //处理文本节点11 mountTextNode(vnode, container);12 }13 if (vtype === 1) {14 //处理原生标签15 mountHtml(vnode, container);16 }17 if (vtype === 3) {18 //处理函数组件19 mountFunc(vnode, container);20 }21 if (vtype === 2) {22 //处理class组件23 mountClass(vnode, container);24 }25}2627//文本节点挂载函数28function mountTextNode(vnode, container) {29 const node = document.createTextNode(vnode);30 container.appendChild(node);31}3233//原生节点挂载函数34function mountHtml(vnode, container) {35 const { type, props } = vnode;36 const node = document.createElement(type); //创建一个真实dom37 const { children, ...rest } = props;38 children.map((item) => {39 //子元素递归40 if (Array.isArray(item)) {41 item.map((c) => {42 mount(c, node);43 });44 } else {45 mount(item, node);46 }47 });48 Object.keys(rest).map((item) => {49 if (item === "className") {50 node.setAttribute("class", rest[item]);51 }52 if (item.slice(0, 2) === "on") {53 node.addEventListener("click", rest[item]);54 }55 });56 container.appendChild(node);57}5859//函数组件挂载函数60function mountFunc(vnode, container) {61 const { type, props } = vnode;62 const node = new type(props);63 mount(node, container);64}6566//类组件挂载函数67function mountClass(vnode, container) {68 const { type, props } = vnode;69 const node = new type(props);70 mount(node.render(), container);71}7273export default {74 render,75};
验证效果
以上就实现了mini react的渲染