性能优化,本质就是减少页面重新渲染的次数。 文章将从两点展开:
- 类组件:PureComponent
- 函数组件:memo
一、PureComponent
1. 原理
纯组件,核心是内部重写了shouldComponentUpdate方法。当组件更新时,如果组件的 props 和 state 都没发生改变, render方法就不会触发,省去虚拟DOM的生成和比对过程,达到提升性能的目的。
React.PureComponent中的shouldComponentUpdate()仅作对象的浅层比较。如果对象中包含复杂的数据结构,则可能无法检查到深层的差别,产生错误的对比结果。所以只在props 和 state 比较简单时使用这个,或者在深层数据结构发生变化时调用forceUpdate()来确保组件被正确地更新。也可以使用immutable对象加速嵌套数据的比较。此外,React中 的
shouldComponentUpdate()将跳过所有子组件树的 props 更新。因此,确保所有子组件也是纯的组件。
2. 思路
2-1. 实现PureComponent
PureComponent的核心是重写shouldComponentUpdate方法。shouldComponentUpdate函数一直返回true,这就告诉React,无论何种情况都要重新渲染该组件。
因此我们在PureComponent类中写上shouldComponentUpdate方法,入参为新属性、新状态。内部借助shallowEqual方法分别去和老属性、老状态进行对比,如果新老属性或新老状态不相等,说明需要更新就返回true,如果相等,说明不需要更新返回false。
2-2. 实现shallowEqual
shouldComponentUpdate方法默认是允许更新返回true,因此shallowEqual方法内部我们需要判断不更新的情况并返回false,入参为需要做对比的两个对象。
- 判断两个对象的类型:如果两个类型的其中一个不是
'object'或者其中一个的值为null,就认为是不相等的返回false - 判断两个对象的键集合:如果键的长度不相等,就认为是不相等返回
false - 判断两个对象的键名:遍历第一个对象,拿到
key去第二个对象中找到相应的值对比,如果第二个对象里没有这个键,或者说同名的键对应的两个值不相等,就任务是不相等返回false
注意:因为是浅对比,所以简单判断如果两个对象的引用地址相等,那就认为是相等的返回true。
二、memo
1. 原理
React.memo()是一个高阶函数,它与 React.PureComponent类似,用来控制函数组件的重新渲染。
React.memo()可接收两个参数,第一个是需要优化的组件,第二个是需要控制渲染的方法,默认为compare。
React.memo()会返回一个纯化的组件MemoFuncComponent,这个组件将会在JSX标记中渲染出来。当组件的参数props和状态state发生改变时,React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染。
2. 思路
2-1. 实现React.memo
打印React.memo的调用:
我们只需模仿这个结构作为
React.memo()的返回值即可。compare是新老属性或状态对比的方法,type就是原本的函数组件。
2-2. 实现mountMemoComponent
组件创建createDOM时,判断如果节点类型为react.memo,那么就调用mountMemoComponent来实现挂载。
从虚拟DOM上解构出type和props,此时的type就是我们第一步返回的结果。因此:
type属性就是原始的函数组件,想要拿到DOM进行挂载,只需调用type传入props即可。为方便后续更新时进行新老属性的对比,此处需要在虚拟DOM上添加prevProps属性来记录老的属性。
2-3. 实现updateMemoComponent
组件更新updateElement时,判断如果节点类型为react.memo,那么就调用updateMemoComponent来实现更新。
组件要更新就执行type,更新时要把新老的属性对象传递给compare进行比较,如果相同就不更新,如果不相同就更新。
三、代码实现
1. src/index.js
import React from "./react";
import ReactDOM from "./react-dom";
/**
* 纯组件:
* 核心是内部重写了shoudlComponentUpdate方法
* 每次更新先判断属性是否变化
* 变化就更新,没变化就不更新
*/
class ClassCounter extends React.PureComponent {
render() {
console.log("ClassCouter.render");
return <div>ClassCounter: {this.props.count}</div>;
}
}
function FunctionCounter(props) {
console.log("FunctionCounter.render");
return <div>FunctionCounter: {props.count}</div>;
}
const MemoFunctionCounter = React.memo(FunctionCounter);
console.log("MemoFunctionCounter", MemoFunctionCounter);
class App extends React.Component {
state = {
number: 0,
};
amountRef = React.createRef();
hanldeClick = () => {
let nextNum = this.state.number + parseInt(this.amountRef.current.value);
this.setState({
number: nextNum,
});
};
render() {
return (
<div>
<ClassCounter count={this.state.number} />
{/* <FunctionCounter count={this.state.number} /> */}
<MemoFunctionCounter count={this.state.number} />
{/* 当input创建完真实DOM后,会把值给ref的current属性 */}
<input ref={this.amountRef} defaultValue={0} />
<button onClick={this.hanldeClick}>+</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
2 src/react.js
> > > class PureComponent extends Component {
> > > shouldComponentUpdate(nextProps, nextState) {
> > > // 如果属性不相等 或状态不相等,说明需要更新,就返回true
> > > return (
> > > !shallowEqual(this.props, nextProps) ||
> > > !shallowEqual(this.state, nextState)
> > > );
> > > }
> > > }
3 src/utils.js
> > > export function shallowEqual(obj1, obj2) {
> > > // 两个对象引用地址一样,就认为相等
> > > if (obj1 === obj2) {
> > > return true;
> > > }
> > > // 任何一个不是对象或值为null 就不相等
> > > if (
> > > typeof obj1 !== "object" ||
> > > obj1 === null ||
> > > typeof obj2 !== "object" ||
> > > obj2 === null
> > > ) {
> > > return false;
> > > }
> > > let keys1 = Object.keys(obj1);
> > > let keys2 = Object.keys(obj2);
> > > // 如果属性数量不相等 就不相等
> > > if (keys1.length !== keys2.length) {
> > > return false;
> > > }
> > >
> > > for (let key of keys1) {
> > > if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
> > > return false;
> > > }
> > > }
> > >
> > > return true;
> > > }
4. src/react-dom.js
import {
REACT_TEXT,
REACT_FORWARD_REF,
MOVE,
PLACEMENT,
REACT_PROVIDER,
REACT_CONTEXT,
> > > REACT_MEMO,
} from "./constants";
import { addEvent } from "./event";
/**
*把虚拟DOM变成真实DOM插入容器
* @param {*} vdom 虚拟DOM/React元素
* @param {*} container 真实DOM容器
*/
function render(vdom, container) {
mount(vdom, container);
}
/** 页面挂载真实DOM */
function mount(vdom, parentDOM) {
//把虚拟DOM变成真实DOM
let newDOM = createDOM(vdom);
//把真实DOM追加到容器上
parentDOM.appendChild(newDOM);
if (newDOM.componentDidMount) newDOM.componentDidMount();
}
/**
* 把虚拟DOM变成真实DOM
* @param {*} vdom 虚拟DOM
* @return 真实DOM
*/
function createDOM(vdom) {
if (!vdom) return null; // null/und也是合法的dom
let { type, props, ref } = vdom;
let dom; //真实DOM
> > > if (type && type.$$typeof === REACT_MEMO) {
> > > return mountMemoComponent(vdom);
> > > } else if (type && type.$$typeof === REACT_PROVIDER) {
return mountProviderComponent(vdom);
} else if (type && type.$$typeof === REACT_CONTEXT) {
return mountContextComponent(vdom);
} else if (type && type.$$typeof === REACT_FORWARD_REF) {
return mountForwardComponent(vdom);
} else if (type === REACT_TEXT) {
// 如果元素为文本,创建文本节点
dom = document.createTextNode(props.content);
} else if (typeof type === "function") {
if (type.isReactComponent) {
// 说明这是一个类组件
return mountClassComponent(vdom);
} else {
// 函数组件
return mountFunctionComponent(vdom);
}
} else if (typeof type === "string") {
//创建DOM节点 span div p
dom = document.createElement(type);
}
// 处理属性
if (props) {
//更新DOM的属性 后面我们会实现组件和页面的更新。
updateProps(dom, {}, props);
let children = props.children;
//如果说children是一个React元素,也就是说也是个虚拟DOM
if (typeof children === "object" && children.type) {
//把这个儿子这个虚拟DOM挂载到父节点DOM上
mount(children, dom);
props.children.mountIndex = 0;
} else if (Array.isArray(children)) {
reconcileChildren(children, dom);
}
}
vdom.dom = dom; // 给虚拟dom添加dom属性指向这个虚拟DOM对应的真实DOM
if (ref) ref.current = dom;
return dom;
}
> > > /** 挂载memo组件 */
> > > function mountMemoComponent(vdom) {
> > > let { type, props } = vdom; // type是memo组件的返回值,type属性是原始的函数组件
> > > let renderVdom = type.type(props);
> > > vdom.prevProps = props; // 记录老的属性对象,以后更新时方便对比
> > > vdom.oldRenderVdom = renderVdom;
> > > return createDOM(renderVdom);
> > > }
/** 挂载Provider组件 */
function mountProviderComponent(vdom) {
let { type, props } = vdom; // type = { $$typeof: REACT_PROVIDER, _context: context }
let context = type._context; // { $$typeof: REACT_CONTEXT, _currentValue: undefined }
// 1. 赋值为Provider使用时的value值
context._currentValue = props.value;
// 2. 渲染的是Provider包裹的子组件
let renderVdom = props.children;
// 为后面更新做准备
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
/** 挂载Context组件-Consumer */
function mountContextComponent(vdom) {
let { type, props } = vdom;
let context = type._context; // type = { $$typeof: REACT_CONTEXT, _context: context }
let renderVdom = props.children(context._currentValue);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
/** 挂载类组件 */
function mountClassComponent(vdom) {
let { type: ClassComponent, props, ref } = vdom;
// 把类组件的属性传递给类组件的构造函数,
// 创建类组件的实例,返回组件实例对象,以便在组件卸载时可以直接执行实例的方法
let classInstance = new ClassComponent(props);
if (classInstance.contextType) {
// 把value值赋给实例的context属性
classInstance.context = ClassComponent.contextType._currentValue;
}
// 在虚拟DOM上挂载classInstance,指向类的实例
vdom.classInstance = classInstance;
// 如果有ref,就把实例赋值给current属性
if (ref) ref.current = classInstance;
if (classInstance.componentWillMount) {
classInstance.componentWillMount();
}
//可能是原生组件的虚拟DOM,也可能是类组件的的虚拟DOM,也可能是函数组件的虚拟DOM
let renderVdom = classInstance.render();
//在第一次挂载类组件的时候让类实例上添加一个oldRenderVdom=renderVdom
// 类组件的虚拟dom的oldRenderVdom属性,指向renderVdom
vdom.oldRenderVdom = classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
/** 挂载函数组件 */
function mountFunctionComponent(vdom) {
let { type: functionComponent, props } = vdom;
//获取组件将要渲染的虚拟DOM
let renderVdom = functionComponent(props);
// 函数组件的oldRenderVdom属性,指向渲染的虚拟DOM--renderVdom
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
/** 挂载经过转发的ref的函数组件 */
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
return createDOM(renderVdom);
}
/** 如果子元素为数组,遍历挂载到容器 */
function reconcileChildren(children, parentDOM) {
// 给每个虚拟DOM挂载mountIndex属性记录其索引
children.forEach((childVdom, index) => {
childVdom.mountIndex = index;
mount(childVdom, parentDOM);
});
}
/**
* 把新的属性更新到真实DOM上
* @param {*} dom 真实DOM
* @param {*} oldProps 旧的属性对象
* @param {*} newProps 新的属性对象
*/
function updateProps(dom, oldProps, newProps) {
for (let key in newProps) {
if (key === "children") {
// 子节点另外处理
continue;
} else if (key === "style") {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
// 绑定事件 ==> dom.onclick = 事件函数
// dom[key.toLowerCase()] = newProps[key];
// 之后不再把事件函数绑定在对应的DOM上,而是事件委托到文档对象
addEvent(dom, key.toLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
//如果说一个属性老的属性对象里有,新的属性没有,就需要删除
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
/**
* DOM-DIFF:递归比较老的虚拟DOM和新的虚拟DOM,找出两者的差异,把这些差异最小化的同步到真实DOM上
* @param {*} parentDOM 父真实DOM
* @param {*} oldVdom 老的虚拟DOM
* @param {*} newVdom 新的虚拟DOM
* @param {*} nextDOM 新的虚拟DOM
*
*/
export function compareToVdom(parentDOM, oldVdom, newVdom, nextDOM) {
/**
// 之前写得:
// 拿到老的真实DOM,创建新的真实DOM,新的替换掉老的
// 性能很差,应完善,去做深度的dom-diff
// 获取oldRenderVdom对应的真实DOM
let oldDOM = findDOM(oldVdom);
// 根据新的虚拟DOM得到新的真实DOM
let newDOM = createDOM(newVdom);
// 把老的真实DOM替换为新的真实DOM
parentDOM.replaceChild(newDOM, oldDOM);
*/
// 1.老-无 新-无:啥也不干
if (!oldVdom && !newVdom) return;
// 2.老-有 新-无:直接删除老节点
if (oldVdom && !newVdom) {
unMountVdom(oldVdom);
}
// 3.老-无 新-有:插入节点
if (!oldVdom && newVdom) {
mountVdom(parentDOM, newVdom, nextDOM);
}
// 4-1.老-有 新-有:判断类型不一样,删除老的,添加新的
if (oldVdom && newVdom && oldVdom.type !== newVdom.type) {
unMountVdom(oldVdom);
mountVdom(parentDOM, newVdom, nextDOM);
}
// 4-2.老-有 新-有:判断类型一样,进行DOM-DIFF,并且节点可复用
if (oldVdom && newVdom && oldVdom.type === newVdom.type) {
updateElement(oldVdom, newVdom);
}
}
/**
* 新老DOM类型一样的更新----DOM-DIFF精髓之处
* 如果新老DOM的类型一样,那么节点就可以复用
*/
function updateElement(oldVdom, newVdom) {
> > > // memo组件
> > > if (oldVdom.type.$$typeof === REACT_MEMO) {
> > > updateMemoComponent(oldVdom, newVdom);
> > > // Consumer组件
> > > } else if (oldVdom.type.$$typeof === REACT_PROVIDER) {
updateProviderComponent(oldVdom, newVdom);
// Provider组件
} else if (oldVdom.type.$$typeof === REACT_CONTEXT) {
updateContextComponent(oldVdom, newVdom);
// 新老节点都是文本节点:复用老的节点,替换内容
} else if (oldVdom.type === REACT_TEXT) {
// 老的真实DOM给新的DOM的dom属性,把内容改掉
let currentDOM = (newVdom.dom = findDOM(oldVdom));
currentDOM.textContent = newVdom.props.content;
// 原生节点
} else if (typeof oldVdom.type === "string") {
let currentDOM = (newVdom.dom = findDOM(oldVdom));
// 更新属性
updateProps(currentDOM, oldVdom.props, newVdom.props);
// 递归比较儿子
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
// 类组件或函数组件
} else if (typeof oldVdom.type === "function") {
// 类组件
if (oldVdom.type.isReactComponent) {
// 先同步实例
newVdom.classInstance = oldVdom.classInstance;
updateClassComponent(oldVdom, newVdom);
// 函数组件
} else {
updateFunctionComponent(oldVdom, newVdom);
}
}
}
> > > /** 更新memo组件 */
> > > function updateMemoComponent(oldVdom, newVdom) {
> > > let { type, prevProps } = oldVdom; // 类型 老属性
> > > // 新老属性不相等,更新
> > > if (!type.compare(prevProps, newVdom.props)) {
> > > // 获取老的真实DOM
> > > let oldDOM = findDOM(oldVdom);
> > > // 获取真实父节点
> > > let parentDOM = oldDOM.parentNode;
> > > let { type, props } = newVdom;
> > > let renderVdom = type.type(props);
> > > compareToVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
> > > newVdom.prevProps = props;
> > > newVdom.oldRenderVdom = renderVdom;
> > > // 新老属性相等,跳过更新,直接赋值
> > > } else {
> > > newVdom.prevProps = prevProps;
> > > newVdom.oldRenderVdom = oldVdom.oldRenderVdom;
> > > }
> > > }
/** 更新Proveder组件 */
function updateProviderComponent(oldVdom, newVdom) {
// 获取老的真实DOM
let oldDOM = findDOM(oldVdom);
// 获取真实父节点
let parentDOM = oldDOM.parentNode;
let { type, props } = newVdom;
let context = type._context;
// 新的属性赋值给_currentValue
context._currentValue = props.value;
let renderVdom = props.children;
compareToVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
/** 更新context组件 */
function updateContextComponent(oldVdom, newVdom) {
// 获取老的真实DOM
let oldDOM = findDOM(oldVdom);
// 获取真实父节点
let parentDOM = oldDOM.parentNode;
let { type, props } = newVdom;
let context = type._context;
// 从 取值
let renderVdom = props.children(context._currentValue);
compareToVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
/**
* 更新类组件
* @param {*} oldVdom
* @param {*} newVdom
*/
function updateClassComponent(oldVdom, newVdom) {
// 复用老的类组件实例
let classInstance = (newVdom.classInstance = oldVdom.classInstance);
if (classInstance.componentWillReceiveProps) {
classInstance.componentWillReceiveProps(newVdom.props);
}
classInstance.updater.emitUpdate(newVdom.props);
}
/**
* 更新函数组件
* @param {*} oldVdom
* @param {*} newVdom
*/
function updateFunctionComponent(oldVdom, newVdom) {
// 获取老的真实DOM的父节点
let parentDOM = findDOM(oldVdom).parentNode;
let { type, props } = newVdom;
let newRenderVdom = type(props);
// 函数组件更新每次都要重新执行函数,拿到新的虚拟DOM
compareToVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.newRenderVdom = newRenderVdom;
}
/**
* 递归比较子节点
* @param {*} parentDOM
* @param {*} oldVChildren
* @param {*} newVChildren
*/
function updateChildren(parentDOM, oldVChildren, newVChildren) {
// 为方便后续进行DOM-DIFF,以数组形式保存
oldVChildren = (
Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren]
).filter((item) => item);
newVChildren = (
Array.isArray(newVChildren) ? newVChildren : [newVChildren]
).filter((item) => item);
// DOM-DIFF 1.构建老map {虚拟DOM的key: 虚拟DOM}
let keyedOldMap = {};
oldVChildren.forEach((oldVChild, index) => {
let oldKey = oldVChild.key ? oldVChild.key : index;
keyedOldMap[oldKey] = oldVChild;
});
// 补丁包:存放要进行的操作
let patch = [];
// 上一个放置好的、不需要移动的索引
let lastPlaceIndex = 0;
// DOM-DIFF 2.遍历新数组查找老虚拟DOM
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index;
let newKey = newVChild.key ? newVChild.key : index;
// 查找老的虚拟DOM中是否存在这个key的节点
let oldVChild = keyedOldMap[newKey];
// 如果找到,复用老节点
if (oldVChild) {
// 先更新
updateElement(oldVChild, newVChild);
// 判断是否移动 把oldVChild移动到mountIndex当前索引处
if (oldVChild.mountIndex < lastPlaceIndex) {
patch.push({
type: MOVE,
oldVChild,
newVChild,
mountIndex: index,
});
}
// 删除已经被复用的节点
delete keyedOldMap[newKey];
lastPlaceIndex = Math.max(oldVChild.mountIndex, lastPlaceIndex);
} else {
// 如果没找到,插入新节点
patch.push({
type: PLACEMENT,
newVChild,
mountIndex: index,
});
}
});
// DOM-DIFF 3.获取需要移动的元素
let moveChildren = patch
.filter((action) => action.type === MOVE)
.map((action) => action.oldVChild);
// 遍历map留下的元素(其实就是没有被复用的)
Object.values(keyedOldMap)
.concat(moveChildren)
.forEach((oldVChild) => {
// 获取老DOM
let currentDOM = findDOM(oldVChild);
parentDOM.removeChild(currentDOM);
});
// DOM-DIFF 4.插入或移动节点
patch.forEach((action) => {
let { type, oldVChild, newVChild, mountIndex } = action;
// 真实DOM节点集合
let childNodes = parentDOM.childNodes;
if (type === PLACEMENT) {
// 根据新的虚拟DOM创建新真实DOM
let newDOM = createDOM(newVChild);
// 获取老DOM中对应的索引处的真实DOM
let childNode = childNodes[mountIndex];
if (childNode) {
parentDOM.insertBefore(newDOM, childNode);
} else {
parentDOM.appendChild(newDOM);
}
} else if (type === MOVE) {
let oldDOM = findDOM(oldVChild);
let childNode = childNodes[mountIndex];
if (childNode) {
parentDOM.insertBefore(oldDOM, childNode);
} else {
parentDOM.appendChild(oldDOM);
}
}
});
// // 最大长度
// let maxLength = Math.max(oldVChildren.length, newVChildren.length);
// // 每一个都进行深度对比
// for (let i = 0; i < maxLength; i++) {
// // 在老的虚拟DOM查找,有老节点并且老节点真的对应一个真实DOM节点,并且这个索引要比我大(目的是找到本身的下一个节点)
// let nextVdom = oldVChildren.find(
// (item, index) => index > i && item && findDOM(item)
// );
// compareToVdom(
// parentDOM,
// oldVChildren[i],
// newVChildren[i],
// nextVdom && findDOM(nextVdom)
// );
// }
}
/**
* 插入新的真实DOM
* @param {}} parentDOM
* @param {*} vdom
* @param {*} nextDOM
*/
function mountVdom(parentDOM, newVdom, nextDOM) {
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
}
/**
* 删除老的真实DOM
* @param {*} vdom 老的虚拟DOM
*/
function unMountVdom(vdom) {
let { props, ref } = vdom;
// 获取老的真实DOM
let currentDOM = findDOM(vdom);
// 如果这个子节点是类组件,还要执行它的卸载的生命周期函数
if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
vdom.classInstance.componentWillUnmount();
}
// 如果有ref,删除ref对应的真实DOM
if (ref) ref.current = null;
// 取消监听函数
Object.keys(props).forEach((propName) => {
if (propName.slice(0, 2) === "on") {
// 事件在真实dom就这样做
// const eventName = propName.slice(2).toLowerCase()
// currentDOM.removeEventListener(eventName, props[propName])
//但是我们先处理了合成事件,事件注册再store上
delete currentDOM.store;
}
});
// 如果有子节点,递归删除所有子节点
if (props.children) {
let children = Array.isArray(props.children)
? props.children
: [props.children];
children.forEach(unMountVdom);
}
// 从父节点中把自己删除
if (currentDOM) currentDOM.parentNode.removeChild(currentDOM);
}
/** 虚拟DOM返回的真实DOM */
export function findDOM(vdom) {
if (!vdom) return null;
// 如果有dom属性,说明这个vdom是原生组件的虚拟DOM,会有dom属性指向真实dom
if (vdom.dom) {
return vdom.dom;
} else {
return findDOM(vdom.oldRenderVdom);
}
}
const ReactDOM = {
render,
};
export default ReactDOM;