2021-02-08 React原理解析01
一、深入虚拟DOM
React本身只是一个DOM的抽象层,使用组件构建虚拟DOM
什么是Virtual DOM、为什么用以及何处使用
what:用Javascript对象表示DOM信息和结构,当状态变更的时候,重新渲染这个Javascript对象结构。这个Javascript对象称为Virtual DOM
传统的dom渲染过程:
- 将html解析成dom树
- 将CSS解析成css tree
- 合并二者为render tree
- 调用浏览器相关的API绘制渲染
why:之所以用虚拟dom,是因为DOM操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于DOM对象,JS对象处理起来更快,而且更简单。通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行dom操作,从而提升性能
where:React中使用JSX语法描述视图,通过babel-loader转译后它们变为React。createElement(…)形式 ,该函数将生成vdom来描述真是dom。将来如果状态发生变化,vdom将作出相应变化,在通过diff算法(协调的核心)对比新老vdom区别从而作出最终dom操作
二、JSX
1、什么是JSX
- 语法糖
- React使用JSX代替常规的Javascript
- JSX是一个看起来很像XML的Javascript语法扩展
2、为什么需要JSX
- 开发效率:使用JSX编写模板简单快速
- 执行效率:JSX编译为Javascript代码后进行了优化,执行更快
- 类型安全:在编译过程中就能发现错误
3、React16原理:babel-loader会预编译JSX为React.createElement(…)
4、React17原理:React17中的JSX转换不会将JSX转换为React.createElement,而是自动从React的package中引入新的入口函数并调用。另外此次升级不会改变JSX语法,旧的JSX转换也将继续工作
5、与Vue的异同:
- react中虚拟dom+jsx的设计一开始就有,vue则是演讲过程中才出现的
- jsx本来就是js扩展,转移过程简单直接的多;vue把template编译为render函数的过程需要复杂的编译器转换字符串-ast-js函数字符串
三、React核心API
核心精简后:
const React = {
createElement,
Component
}
核心的api:
React.Component:react\packages\react\src\ReactBaseClasses.js
实现自定义组件
ReactDOM.render:react\packages\react-dom\src\client\ReactDOMLegacy.js
渲染真实DOM
render()
ReactDOM.render(element, container, callback)
当首次调用时,容器节点的所有DOM元素都会被替换,后续的调用则会使用React的DOM差分算法(DOM diffing algorithm)进行高效的更新
如果提供了可选的回调函数callback,该回调将在组件被渲染或更新之后被执行
节点类型
注意节点类型:
- 文本节点
- HTML标签节点
- 函数组件
- 类组件
- <>&&Fragment节点
调试文件index.js
// import React from "react";
// import ReactDOM from "react-dom";
import ReactDOM from "./kreact/react-dom";
import Component from "./kreact/Component";
import "./index.css";
function FunctionComponent(props) {
return (
<div className="border">
<p>{props.name}</p>
</div>
);
}
class ClassComponent extends Component {
render() {
return (
<div className="border">
<p>{this.props.name}</p>
</div>
);
}
}
/* function FragmentComponent(props) {
return [1, 2, 3].map(item => <h1>哈哈</h1>);
} */
const jsx = (
<div className="border">
<h1>React原理解析-01</h1>
<a href="https://www.kaikeba.com">kkb</a>
<FunctionComponent name="function" />
<ClassComponent name="class" />
{/* <FragmentComponent /> */}
<>
<h2>哈哈</h2>
<h3>呵呵</h3>
</>
</div>
);
ReactDOM.render(jsx, document.getElementById("root"));
// console.log(React.version);
// 文本节点
// 原生标签
// 函数组件
// 类组件
// 空节点&&Fragment组件
渲染函数react-dom.js
/**
* vnode 虚拟DOM
* node 真实DOM
*/
function render(vnode, container) {
console.log("vnode", vnode);
// vnode->node
const node = createNode(vnode);
// node 插入到container中
container.appendChild(node);
}
function isString(str) {
return typeof str === "string";
}
// 根据vnode,生成node
function createNode(vnode) {
let node;
const { type } = vnode;
// todo 生成node
// 原生标签 div a span
if (isString(type)) {
node = updateHostComponent(vnode);
} else if (typeof type === "function") {
node = type.prototype.isReactComponent
? updateClassComponent(vnode)
: updateFunctionComponent(vnode);
} else if (type === undefined) {
// 文本
node = updateTextComponent(vnode);
} else {
// <>空节点Fragment节点
node = updateFragmentComponent(vnode);
}
return node;
}
// 更新属性
function updateNode(node, nextVal) {
Object.keys(nextVal)
.filter(k => k !== "children")
.forEach(k => (node[k] = nextVal[k]));
}
// 原生标签
function updateHostComponent(vnode) {
const { type, props } = vnode;
const node = document.createElement(type);
// 更新属性
updateNode(node, props);
// 遍历子节点,插入到node上
reconcileChildren(node, props.children);
return node;
}
// 文本
function updateTextComponent(vnode) {
const node = document.createTextNode(vnode);
return node;
}
// 函数组件
function updateFunctionComponent(vnode) {
const { type, props } = vnode;
const vvnode = type(props);
const node = createNode(vvnode);
return node;
}
// 类组件
function updateClassComponent(vnode) {
const { type, props } = vnode;
// 先实例化
const instance = new type(props);
const vvnode = instance.render();
const node = createNode(vvnode);
return node;
}
// <>Fragment节点
function updateFragmentComponent(vnode) {
const { props } = vnode;
// 创建Fragment节点
const node = document.createDocumentFragment();
// 遍历子节点,插入到node上
reconcileChildren(node, props.children);
return node;
}
// 最假的吧,最简单的也是协调
function reconcileChildren(parentNode, children) {
let newChildren = Array.isArray(children) ? children : [children];
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i];
// child是vnode
// child->node, 插入到parentNode
render(child, parentNode);
}
}
// eslint-disable-next-line
export default { render };
组件Component.js
function Component(props) {
this.props = props;
}
Component.prototype.isReactComponent = {};
export default Component;
分析,首先render(vnode, container)接收两个参数vnode和container,在render函数中就做两件事:
第一件事是把vnode->node
第二件事是把node插入到container中
function render(vnode, container) {
console.log("vnode", vnode);
// vnode->node
const node = createNode(vnode);
// node 插入到container中
container.appendChild(node);
}
第一件事,把vnode变成node需要根据vnode.type类型来判断,不同类型使用不同的方法
function isString(str) {
return typeof str === "string";
}
// 根据vnode,生成node
function createNode(vnode) {
let node;
const { type } = vnode;
// todo 生成node
// 原生标签 div a span
if (isString(type)) {
node = updateHostComponent(vnode);
} else if (typeof type === "function") {
node = type.prototype.isReactComponent
? updateClassComponent(vnode)
: updateFunctionComponent(vnode);
} else if (type === undefined) {
// 文本
node = updateTextComponent(vnode);
} else {
// <>空节点Fragment节点
node = updateFragmentComponent(vnode);
}
return node;
}
type的情况有以下几种:
1、原生节点div、span、a等使用updateHostComponent方法
// 原生标签
function updateHostComponent(vnode) {
const { type, props } = vnode;
const node = document.createElement(type);
// 更新属性
updateNode(node, props);
// 遍历子节点,插入到node上
reconcileChildren(node, props.children);
return node;
}
2、文本节点使用updateTextComponent方法
// 文本
function updateTextComponent(vnode) {
const node = document.createTextNode(vnode);
return node;
}
3、函数组件则返回函数的结果vnode,再调用createNode方法
// 函数组件
function updateFunctionComponent(vnode) {
const { type, props } = vnode;
const vvnode = type(props);
const node = createNode(vvnode);
return node;
}
4、类组件则先new一个实例,再调用实例的render方法返回vnode,再调用createNode方法
// 类组件
function updateClassComponent(vnode) {
const { type, props } = vnode;
// 先实例化
const instance = new type(props);
const vvnode = instance.render();
const node = createNode(vvnode);
return node;
}
5、<>节点和Fragment组件使用updateFragmentComponent方法
// <>Fragment节点
function updateFragmentComponent(vnode) {
const { props } = vnode;
// 创建Fragment节点
const node = document.createDocumentFragment();
// 遍历子节点,插入到node上
reconcileChildren(node, props.children);
return node;
}
最后,协调也是最重要的部分diff算法就使用在协调中,这里列出简单的方法
// 最假的吧,最简单的也是协调
function reconcileChildren(parentNode, children) {
let newChildren = Array.isArray(children) ? children : [children];
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i];
// child是vnode
// child->node, 插入到parentNode
render(child, parentNode);
}
}