# mini-react
注意:因为 react 17 采用实时编译[jsx-vdom],所以本示例仅适合 react 16 及以下版本
# 前言
react 用于生成 vdom;
react-dom 将生成的 vdom,渲染对应的界面(如:浏览器、app、桌面端);
知道两者区别,便于我们实现接下来的操作;
# react
在开始前我们要知道什么是JSX
;
JSX 本质上就是个对象,是通过 react.createElement 方法生成的 vdom;
const Foo = <div className="yunfei">Foo</div>;
React.createElement("div", { className: "yunfei" }, "Foo");
# babel
babel 在 react 中的角色是,调用 React.createElement 方法,将JSX
解析完成后的数据,传递给该方法,生成 vdom
# react 中的方法
通过 babel 可以,react.js 中需要提供 createElement 方法;
在创建 class 组件时,还需要提供一个继承类 ·Component·;
import React, { Component } from "react";
class App extends Component {}
综上所述 react 提供了 createElement 方法 与 Component 类;
// 标识vdom 类型
const reactElement = Symbol("react.element");
/**
- type 类型
- props 属性
- {...any} children 子节点
*/
function createElement(type, props, ...children) {
return { $$type: reactElement, type, props, children };
}
// 类组件
class Component {
constructor(props) {
this.props = props;
}
static isReactComponent = {}; // 区分组件 或者 元素
setState(newState) {} // setState 方法
}
const React = {
createElement,
};
export default React;
接下来我们来看下 App 转换成 vnode 后,打印出来的结果
// app.js
import React from "./mini-react/react";
const App = <div>hello world</div>;
console.log(App);
打印结果
# 复杂的 JSX
针对 文本类型 和 数组 的特殊处理,便于之后的 mounted 做好预设
<div>
hello world
{[<p key="1">1</p>, <p key="2">2</p>, <p key="3">3</p>, ["a", "b", "c"]]}
</div>
react.js
// 标识vdom
const reactElement = Symbol("react.element");
// 标记string、number
const reactText = Symbol("react.text");
function createElement(type, props, ...child) {
delete props.__self;
delete props.__source;
// 这里的 key 在 diff时使用
let key = props.key;
delete props.key;
let children = child.flat(Infinity);
// 处理 string 与 number(react中是在 react-dom处理的)
children = children
.map((e) => {
if (typeof e == "object") {
return e;
} else if (typeof e == "string" || typeof e == "number") {
// 封装
return { $$typeof: reactText, type: "textNode", inner: e };
} else {
return null;
}
})
.filter((e) => e);
return { $$type: reactElement, key, type, props, children };
}
打印下
接下来我们看看 react-dom 做了什么
# React-dom
import ReactDOM from "react-dom";
import App from "./app";
ReactDOM.render(App, document.querySelector("#root"));
如上所示 react.dom 提供了 render 方法;接收参数 vnode 和 根节点;
# 主体结构
参数
- tree vdom
- container 挂载节点
- cb 回调函数
function render(tree, container, cb) {}
const ReactDOM = {
render,
};
export default ReactDOM;
# 创建 dom
根据 vnode 生成相应 节点
节点类型
- 元素
- 文本
- 类组件
因为 vnode 是个树形结构,所以采用递归
方式创建节点
# 创建元素与文本
function render(tree, container, cb) {
const node = createNode(tree); // 创建节点
container.appendChild(node); // 挂载节点
}
const ReactDOM = {
render,
};
export default ReactDOM;
- createNode
此函数用来创建 元素 [标签|文本] || [
|text]
通过 判断 $$type 来进行生成对应节点
import { reactText, reactElement } from "./react";
function createNode(vnode) {
let node;
// 元素
if (vnode.$$type === reactElement) {
node = document.createElement(vnode.type);
// 创建子节点
createChildren(node, vnode.children);
} else if (vnode.$$type === reactText) {
node = document.createTextNode(vnode.inner);
}
return node;
}
- createChildren
循环子节点 并 调用 render 函数
// 创建子节点 递归回调
function createChildren(parent, children) {
children.forEach((ele) => {
render(ele, parent);
});
}
# 添加属性 props
import React from "./mini-react/react";
import ReactDOM from "./mini-react/react-dom";
const App = (
<div>
hello world
<div
style={{
width: "200px",
height: "200px",
border: "2px solid #000",
}}
></div>
<button
onClick={() => {
alert(1);
}}
>
点击
</button>
</div>
);
console.log(App);
ReactDOM.render(App, document.querySelector("#root"));
属性有哪些
- style
- 事件(react 自己的
合成事件
是·基于观察者模式·实现的)
- createProps
创建属性,通过循环遍历 props 来创建
function createProps(node, props) {
for (let p in props) {
// 样式
if (p === "style") {
for (let st in props["style"]) {
node["style"][st] = props["style"][st];
}
} else if (EVENTS.includes(p)) {
// 绑定事件
node[p.toLocaleLowerCase()] = props[p].bind(undefined); //改变指针
}
}
}
- createNode
更改 createNode 方法,添加 创建 props 函数
// 创建节点 元素 字符串 组件
function createNode(vnode) {
let node;
// 元素
if (vnode.$$type === reactElement) {
node = document.createElement(vnode.type);
// 创建属性 add
createProps(node, vnode.props);
// 创建子节点
createChildren(node, vnode.children);
} else if (vnode.$$type === reactText) {
node = document.createTextNode(vnode.inner);
}
return node;
}
# 挂载 class 组件
首先打印下 class 组件的 vnode;
- index.js
import React from "./mini-react/react";
import ReactDOM from "./mini-react/react-dom";
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "wang",
};
}
render() {
const { name } = this.state;
return <div>Foo:{name}</div>;
}
}
const App = (
<div>
hello world
<Foo />
<div
style={{ width: "200px", height: "200px", border: "2px solid #000" }}
></div>
<button
onClick={() => {
alert(1);
}}
>
点击
</button>
</div>
);
console.log(App);
ReactDOM.render(App, document.querySelector("#root"));
打印出的 class Foo vdom 结构 如下
# 思路
- 判断是否为 class 组件
- 依据 class 的 vdom,生成 真实 dom
- 执行组件声明周期
- 判断 class 组件
修改 createNode 函数,依据 class 组件继承Component
类的isReactComponent
属性进行判断
// 创建节点 元素 字符串 组件
function createNode(vnode) {
let node;
// 元素
if (vnode.$$type === reactElement) {
if (typeof vnode.type === "string") {
node = document.createElement(vnode.type);
// 创建属性
createProps(node, vnode.props);
// 创建子节点
createChildren(node, vnode.children);
} else {
if (vnode.type.isReactComponent) {
node = createCmp(vnode);
}
}
} else if (vnode.$$type === reactText) {
node = document.createTextNode(vnode.inner);
}
return node;
}
- 依据 class 的 vdom,生成 真实 dom
新增 createCmp 函数
// 创建组件
function createCmp(vCmp) {
// 实例化 class
let Cmp = new vCmp.type(vCmp.props); //传参 props
// 获取 vdom
const vnode = Cmp.render();
// 生成 节点
let node = createNode(vnode);
return node;
}
- 执行组件声明周期
新增 stateFromProps 与 componentDidMount 方发
// class 组件 props 映射 到 state
function stateFromProps(cmp, props, state) {
// 判断当前是否存在此方法
return cmp.type.getDerivedStateFromProps
? cmp.type.getDerivedStateFromProps(props, state)
: {};
}
// class 组件 ComponentDidMount
function didMount(cmp) {
if (cmp.componentDidMount) {
cmp.componentDidMount();
}
}
修改 createCmp 函数
// 创建组件
function createCmp(vCmp) {
// 实例化 class
let Cmp = new vCmp.type(vCmp.props); //传参 props
// 生命周期
let nextState = stateFromProps(vCmp, vCmp.props, Cmp.state);
// 合并状态
if (nextState) {
Object.assign(Cmp.state, nextState);
}
// 获取 vdom
const vnode = Cmp.render();
// 生成 节点
let node = createNode(vnode);
// 组件 mounted (react解决方案 微任务 事件队列)使用 setTimeout 替换
setTimeout(() => {
didMount(Cmp);
});
return node;
}
结果
# 实现批处理 setState
setState 的同步/异步在于其使用方式
- 在 React 时间,及相关的 React 方法中,是异步
- 在 setTimeout 等异步方法,或者 DOM 原生事件中是同步
批处理原理
批处理在,组件提供的
方法
、合成事件
中发生发生时,组件内部会记录一个状态值
批处理
(batchUpdate),默认值为true
执行方法(生命周期、事件)
在调用
setState
方法时,判断当前批处理
是否开启;
- 开启状态,则将状态值,添加至
状态队列
中 - 关闭状态,直接更新组件
方法执行完成后,
批处理
状态值设置为false
合并
状态队列
中的状态更新组件
# 改造 createCmp
// 创建组件
function createCmp(vCmp) {
// 实例化 class
let Cmp = new vCmp.type(vCmp.props); //传参 props
// 生命周期
let nextState = stateFromProps(vCmp, vCmp.props, Cmp.state);
// 合并状态
if (nextState) {
Object.assign(Cmp.state, nextState);
}
// 获取 vdom
const vnode = Cmp.render();
// 生成 节点
let node = createNode(vnode);
/** 声明 更新组件函数 */
Cmp.updater = (nextProps, nextState) => {
// shouldComponentUpdate
let prevProps = Cmp.props;
let prevState = Cmp.state;
Cmp.props = nextProps;
Cmp.state = nextState;
// 生成 新的 vdom
let newNode = Cmp.render();
console.log(newNode);
// diff 操作
diff(vnode, newNode);
};
// 组件 mounted (react解决方案 微任务 事件队列)使用 setTimeout 替换
setTimeout(() => {
// didMount(Cmp)
batchUpdate(Cmp, didMount, [Cmp]);
});
return node;
}
# 创建 batchUpdate
批处理函数
// 批处理
function batchUpdate(Cmp, fn, args, $this) {
Cmp.isBatchUpdate = true; // 批处理开启
Cmp.nextStates = []; // 任务队列
fn.apply($this, args); // 执行
Cmp.isBatchUpdate = false; // 批处理关闭
// 合并状态值
let nextState = Object.assign({}, Cmp.state);
Cmp.nextStates.forEach((state) => {
Object.assign(nextState, state); //合并任务队列中的状态
});
// 有变化时更新
if (nextState.length > 0) {
// 更新组件
Cmp.updater(Cmp.props, nextState); // 此方法在 创建组件node时 声明
}
}
# 改造 component
设置 setState 方法,实现批处理
class Component {
constructor(props) {
this.props = props;
}
static isReactComponent = {}; // 区分组件或者元素
setState(newState) {
// 进入更新流程
if (this.isBatchUpdate) {
//如果当前的批处理状态是打开的,不直接更新组件
this.nextStates.push(newState);
} else {
// 当前批处理流程未打开,直接更新组件
this.updater(this.props, Object.assign({}, this.state, newState));
}
}
}
# diff
对比 oldTree 与 newTree;所需对比的类型如下:
- tree diff
- element diff
- list diff
tree diff 遵循策略:
同层对比
递归向下
element diff 对比策略如下:
- 对比 type 是否一致
- 对比 文本节点内容 是否一致
- 组件 则更新子组件
- 如果是元素 对比 props 并更新 子元素
list diff
- 将 数组,转换为以 key 作为属性名的对象
- 通过循环 newChildren 找到,新增项
- 通过循环 oldChildren 找到,删除项
- 在其他中,继续递归节点对比,同时,通过索引值的策略找到 位置差异项
# 实现 diff
function diff(oldTree, newTree) {
diffNode(oldTree, newTree);
}
diff 节点
function diffNode(oldNode, newNode) {
if (oldNode.type !== newNode.type) {
// 判断类型 - 不同替换
} else if (oldNode.$$type === reactText) {
// 判断文本 - 不同替换
if (oldNode.inner !== newNode.inner) {
// 更新文本内容
}
} else if (oldNode.$$type === reactElement) {
// 组件 ?
if (oldNode.type.isReactComponent) {
// 更新子组件
updateCmp(oldNode, newNode);
} else {
// 对比 props
diffProps(oldNode.props, newNode.props);
// 递归子级
diffChildren(oldNode.children, newNode.children);
}
} else {
console.error("diff异常!");
}
}
diff 属性
function diffProps(oldProps = {}, newProps = {}) {
for (let k in nextProps) {
if (typeof nextProps[k] === "object") {
diffProps(oldProps[k], nextProps[k]);
} else if (k in oldProps) {
if (oldProps[k] !== nextProps[k]) {
console.log(k, "属性值有变化");
}
} else {
console.log(k, "新增属性");
}
}
for (let k in oldProps) {
if (!(k in newProps)) {
console.log(k, "删除属性");
}
}
}
diff 子组件
function diffChildren(oldChildren, newChildren) {}
更新组件
更新组件需要拿到当前·组件实例·所以修改 createCmp 方法
function createCmp(vCmp){
let Cmp = new vCmp.type(vCmp.props);
...
vCmp.Cmp = Cmp; //记录
...
}
function updateCmp(oldNode, newNode) {
// 更新组件
oldCmp.props = oldCmp.props;
return oldCmp.Cmp.updater(newCmp.props, oldCmp.Cmp.state);
}
# diffChildren
该方法较为复杂,因为 children 可能发生 新增、删除、移动等情况
并且 diffChildren(oldChildren, newChildren) 的两个参数值是数组结构
为了便于 diff 将其 转换为 对象结构
提供方法 getKeys
这里的 属性 key 是在 react.createElement 中 Porps 提供的;
function getKeys(child) {
let keys = {};
child.forEach((item, index) => {
let { key } = item;
key = key !== undefined ? key : "RC" + index;
key = key;
keys[key] = item;
keys[key].index = index; // 记录 index 便于 对 元素顺序的比对
});
}
值得注意的是,在对象 key 为数字时 { 2:2, 1:1, 4:4 },遍历该对像时,打印的结果是 1:1 , 2:2, 4:4; 所以针对 key 数字的对象,打印结果会按照 key 进行排序; 想解决这个问题 要注意 保证不是 数字( 如: 'w'+1);
如果 key 是纯数字,则会导致 diff 是 节点 对比顺序发生问题
修改 diffChildren 方法
function diffChildren(oldChildren, newChildren) {
let oldChild = getKeys(oldChildren);
let newChild = getKeys(newChildren);
for (let k in newChild) {
if (oldChild[k]) {
// 新 旧 节点都存在 进行深层次比对
diffNode(oldChild[k], newChild[k]);
} else {
// 新增 节点
}
}
for (let k in oldChild) {
if (!newChild[k]) {
// 老节点被删除
}
}
}
# 检测 children 顺序改变?
正常情况下, 后一位的索引值,应该不小于前一位的;
如果,元素的位置没有发生改变,那我们拿元素更新的前索引值,去做对比,也应该符合后一位的索引值不小于前一位的索引值
perv: a(0), b(1), c(2), d(3)
next: b, a, e, d, f, c
声明一个变量,记录上一位的索引值.
lastIndex = 0;
1. 找到 b ,b 更新前的索引值为 1,符合规则,b 位置没有变化,更新 lastIndex = 1
2. 找到 a ,a 更新前的索引值为 0,不符合规则,a 的位置变化,
3. 找到 e ,这是新增节点,跳过 e
4. 找到 d ,d 更新前的索引值为 3,符合规则,d 位置没有变化,更新 lastIndex = 3
5. 找到 f ,这是新增节点,跳过 f
6. 找到 c ,c 更新前的索引值为 2,不符合规则,说明位置有变化
依照 此流程 可以将变化的元素搜集出来;如 移动(a,c)、新增(e、f)
修改 diffChildren 的 if(oldChild[k])
的判断
function diffChildren(oldChildren, newChildren) {
let oldChild = getKeys(oldChildren);
let newChild = getKeys(newChildren);
let lastIndex = 0; // 标识 上一位 索引位置
for (let k in newChild) {
if (oldChild[k]) {
// 新 旧 节点都存在 进行深层次比对
diffNode(oldChild[k], newChild[k]);
if (lastIndex > oldChild[k].index) {
// 位置发生变化
} else {
// 位置没有变化
lastIndex = oldChild[k].index;
}
} else {
// 新增 节点
}
}
for (let k in oldChild) {
if (!newChild[k]) {
// 老节点被删除
}
}
}
测试
index.js
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "wang",
};
}
componentDidMount() {
console.log("挂载完成");
this.setState({ name: "yunfei" });
}
render() {
const { name } = this.state;
return (
<div className={name}>
Foo:{name}
<button
onClick={() => {
this.setState({ name: name + Date.now() });
}}
>
add
</button>
</div>
);
}
}
注意 onClick 返回的是函数 而函数每次返回值都是不同的(增加性能消耗);所以在使用时尽量将其封装到类或者方法中去
改进
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "wang",
};
}
componentDidMount() {
console.log("挂载完成");
this.setState({ name: "yunfei" });
}
setName = () => {
this.setState({ name: name + Date.now() });
};
render() {
const { name } = this.state;
return (
<div className={name}>
Foo:{name}
<button onClick={this.setName}>add</button>
</div>
);
}
}
写到这里,diff 的更新就结束了。接下来就 更新节点
的过程
# patch
- 类型不一致 replace
- parent.replaceChild(node,oldNode) ; 注意 parent 为父节点 node 被插入节点 oldNode 为插入节点
- 更新文本内容 text
- node.textContent = '新内容'
- 属性变化 props
- node
- 节点位置变化 move
- 新增项 insert
- parent.insertBefore(node,nextSibling)
- 删除节点 remove
- node.remove()
# 创建差异收集器 patchs
parent.replaceChild(node,oldNode);
依据方法 replaceChild 可知 需要取得 父节点,所以改造 createNode 方法 将当前 vNode 创建属性(dom)用于挂载真实节点
// 创建节点 元素 字符串 组件
function createNode(vnode) {
let node;
...
vnode.dom = node; //挂载真实节点
return node
}
定义 patches 用于 收集 对应的操作
function diff(oldTree, newTree, createNode) {
diffNode(oldTree, newTree, createNode);
}
// diff节点
function diffNode(oldNode, newNode, createNode) {
const patches = [];
let node = oldNode.dom;
const parent = node.parentNode;
if (oldNode.type !== newNode.type) {
// 判断类型 - 不同替换
patches.push({
type: "replace",
parent,
oldNode: node,
node: createNode(newNode),
});
} else if (oldNode.$$type === reactText) {
// 判断文本 - 不同替换
if (oldNode.inner !== newNode.inner) {
// 更新文本内容
patches.push({
type: "text",
node,
inner: newNode.inner,
});
}
} else if (oldNode.$$type === reactElement) {
// 组件 ?
if (oldNode.type.isReactComponent) {
// 更新子组件
updateCmp(oldNode, newNode);
} else {
// 对比 props
const propsPatches = diffProps(oldNode.props, newNode.props);
if (Object.keys(propsPatches).length > 0) {
patches.push({
type: "props",
node,
props: propsPatches,
});
}
// 递归子级
diffChildren(oldNode.children, newNode.children, createNode);
}
} else {
console.error("diff异常!");
}
// 最后
if (patches.length > 0) {
patch(patches); // 开始更新dom
}
}
diffProps 的修改
// diff属性
function diffProps(oldProps = {}, newProps = {}) {
let propsPatches = {};
for (let k in newProps) {
if (typeof newProps[k] === "object") {
let subPatches = diffProps(oldProps[k], newProps[k]); //接收 递归
if (Object.keys(subPatches).length > 0) {
propsPatches[k] = subPatches;
}
} else if (k in oldProps) {
if (oldProps[k] !== newProps[k]) {
console.log(k, "属性值有变化");
propsPatches[k] = newProps[k];
}
} else {
console.log(k, "新增属性");
propsPatches[k] = newProps[k];
}
}
for (let k in oldProps) {
if (!(k in newProps)) {
console.log(k, "删除属性");
propsPatches[k] = "react-remove"; // 删除标识
}
}
return propsPatches;
}
patch.js 的实现
function patch(patches) {
patches.forEach((p) => {
switch (p.type) {
case "replace":
p.parent.replaceChild(p.node, p.oldNode);
break;
case "text":
p.node.textContent = p.inner;
break;
case "props":
patchProps(p.node, p.props);
break;
case "move":
case "insert":
p.parent.insertBefore(p.node, p.next);
break;
case "remove":
p.node.remove();
break;
}
});
}
function patchProps(node, props) {
for (let s in props) {
if (props[s] === "react-remove") {
// 需要判断如果是 DOM 属性,使用removeAttribute,否则 delete node[s]
delete node[s];
} else if (s === "style") {
for (let styl in props["style"]) {
node["style"][styl] = props["style"][styl];
}
} else if (s.slice(0, 2) === "on") {
node[s.toLocaleLowerCase()] = props[s];
} else {
node[s] = props[s];
}
}
}
export default patch;
接下来要 处理 diffChildren
如何获取 next
let prevChild = [a, b, c, d];
let nextChild = [b, a(move), e(insert), d, c];
let prev = index - 1;
let next = prev.nextElementSibling;
// [a,b,c,d]
// a: 获取前一位 b,然后获取b在dom中的下一位:c,将a插入到c之前
// [b,a,c,d]
// e: 获取前一位 a,然后获取a在dom中的下一位:c,将e插入到c之前
// [b,a,e,c,d]
// d: 位置不变
// [b,a,e,c,d]
// c: 获取前一位 d,然后获取d在dom中的下一位:undefined,将c插入到undefined之前
// [b,a,e,d,c]
通过该思路 我们就可以实现 节点的位移
、新增
修改 diffChildren
// diff子组件
// 触发情况 新增 删除 移动
// 对比优化 将数组 装换成 对象来进行对比 {k:{},v:{}}
function diffChildren(oldChildren, newChildren, createNode, parent) {
// console.log(oldChildren, newChildren) // 两个数组 不易于对比
let oldChild = getKeys(oldChildren);
let newChild = getKeys(newChildren);
let lastIndex = 0; // 标识 上一位 索引位置
let patches = [];
let nextChildren = newChildren;
for (let k in newChild) {
if (oldChild[k]) {
// 新 旧 节点都存在 进行深层次比对
nextChildren[newChild[k].index] = diffNode(
oldChild[k],
newChild[k],
createNode
);
if (lastIndex > oldChild[k].index) {
// 位置发生变化
// 获取上一节点
let prev =
newChild[k].index > 0 ? newChildren[newChild[k].index - 1].dom : null;
// 获取下一个节点
let next = prev ? prev.nextElementSibling : parent.children[0];
patches.push({
type: "move",
parent,
node: oldChild[k].dom,
next,
});
} else {
// 位置没有变化
lastIndex = oldChild[k].index;
}
} else {
// 新增 节点
//console.log(newChild[k],"新增项");
let prev =
newChild[k].index > 0 ? newChildren[newChild[k].index - 1].dom : null;
let next = prev ? prev.nextElementSibling : parent.children[0];
patches.push({
type: "insert",
parent,
node: createNode(newChild[k]),
next,
});
nextChildren[newChild[k].index] = newChild[k];
}
}
for (let k in oldChild) {
if (!newChild[k]) {
// 老节点被删除
console.log(oldChild[k], "被删除了");
patches.push({
type: "remove",
node: oldChild[k].dom,
});
}
}
if (patches.length > 0) {
patch(patches);
}
return nextChildren;
}
最要保证 vnode的实时性,要在diff之后产生的newVNode 赋值给 oldVNode;
react-dom.js
// 创建组件
function createCmp(vCmp) {
let Cmp = new vCmp.type(vCmp.props);
// 生命周期
let nextState = stateFromProps(vCmp, vCmp.props, Cmp.state);
if (nextState) {
Object.assign(Cmp.state || {}, nextState);
}
let vnode = Cmp.render();
let node = createNode(vnode);
//更新组件
vCmp.Cmp = Cmp;
Cmp.updater = (nextProps, nextState) => {
//Object.assign(nextState,stateFromProps(vCmp,nextProps,nextState));
// SCU
let prevProps = Cmp.props;
let prevState = Cmp.state;
Cmp.props = nextProps;
Cmp.state = nextState;
//调用 render 生成新的 虚拟DOM;
let newVNode = Cmp.render();
// getSnapshotBeforeUpdate
vnode = diff(vnode, newVNode, createNode);
return vCmp;
};
setTimeout(() => {
batchUpdate(Cmp, didMount, [Cmp]);
})
return node;
}
diff.js
function diff(oldTree, newTree, createNode) {
return diffNode(oldTree, newTree,createNode) //返回 newVNode
}
// diff节点
function diffNode(oldNode, newNode, createNode) {
const patches = [];
let node = oldNode.dom;
let parent = node.parentNode;
let nextNode = oldNode; //返回nextNode
if (oldNode.type !== newNode.type) {
// 判断类型 - 不同替换
patches.push({
type: 'replace',
parent,
oldNode: node,
node: createNode(newNode)
})
nextNode = newNode;
} else if (oldNode.$$type === reactText) {
// 判断文本 - 不同替换
if (oldNode.inner !== newNode.inner) {
// 更新文本内容
patches.push({
type: 'text',
node,
inner: newNode.inner
})
nextNode.inner = newNode.inner;
}
} else if (oldNode.$$type === reactElement) {
// 组件 ?
if (oldNode.type.isReactComponent) {
// 更新子组件
nextNode = updateCmp(oldNode, newNode);
} else {
// 对比 props
let propsPatches = diffProps(oldNode.props, newNode.props);
if (Object.keys(propsPatches).length > 0) {
patches.push({
type: 'props',
node,
props: propsPatches
})
nextNode.props = newNode.props;
}
// 递归子级
nextNode.children = diffChildren(oldNode.children, newNode.children,createNode,node)
}
} else {
console.error('diff异常!')
}
// 最后
if (patches.length > 0) {
patch(patches); // 开始更新dom
}
return nextNode;
}
← React 高阶技巧 dva-umi →