上一篇文章说到了JSX的渲染机制流程,以及虚拟dom怎么形成的,这篇就讲一下如何把虚拟dom变成真实的dom
把构建的virtualDOM渲染为真实DOM
真实DOM:浏览器页面中,最后渲染出来,让用户看见的DOM元素!!
基于ReactDOM中的render方法处理的!!
v16
ReactDOM.render(
<>...</>,
document.getElementById('root')
);
v18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>...</>
);
补充说明:第一次渲染页面是直接从virtualDOM->真实DOM;但是后期视图更新的时候,需要经过一个DOM-DIFF的对比,计算出补丁包PATCH(两次视图差异的部分),把PATCH补丁包进行渲染!!
接下来就是创建真实DOM,遍历DOM节点的属性,我们这次只考虑type和props
基于传统的for/in循环,会存在一些弊端「性能较差(既可以迭代私有的,也可以迭代公有的);只能迭代“可枚举、非Symbol类型的”属性...」
一般来讲,内置的属性都是不可枚举的「枚举:可以被列举,例如被for/in、Object.keys等列举出来的」;自定义的属性都是可枚举的!!
但是我们可以设定成员的枚举性!! Object.defineProperty()
例:
let arr = [10,20];
console.log(arr);
可以看到,10,20以及length都被打印出来了
那在加个其他属性呢?
Array.prototype.BB = 200;
let arr = [10,20];
arr[Symbol('AA')] = 100;
console.log(arr);
同样可以打印出来
那此时用for in循环一下试试
Array.prototype.BB = 200;
let arr = [10,20];
arr[Symbol('AA')] = 100;
console.log(arr);
for (let key in arr) {
console.log(key)
}
可以看到,可枚举的无论私有公有的属性都遍历出来了,但是非Symbol类型的属性没有拿到,这就是他的弊端。
这时就要变一下获取方法了。
解决思路:获取对象所有的私有属性「私有的、不论是否可枚举、不论类型」
-
Object.getOwnPropertyNames(arr) -> 获取对象非Symbol类型的私有属性「无关是否可枚举」
-
Object.getOwnPropertySymbols(arr) -> 获取Symbol类型的私有属性
获取所有的私有属性:
let keys = Object.getOwnPropertyNames(arr).concat(Object.getOwnPropertySymbols(arr));
或者这样太麻烦,要写一大堆,展示第二种方法
可以基于ES6中的Reflect.ownKeys代替上述操作「弊端:不兼容IE」
let keys = Reflect.ownKeys(arr);
那以后,就可以用这种方式代替for in循环了
let keys = Reflect.ownKeys(arr);
keys.forEach(key=>{
console.log(key,arr[key]);
})
这时就可以封装我们的each获取虚拟Dom属性的方法了
const each = function each(obj, callback) {
if (obj === null || typeof obj !== "object") throw new TypeError('obj is not a object');
if (typeof callback !== "function") throw new TypeError('callback is not a function');
let keys = Reflect.ownKeys(obj);
keys.forEach(key => {
let value = obj[key];
// 每一次迭代,都把回调函数执行
callback(value, key);
});
};
/* render:把虚拟DOM变为真实DOM */
export function render(virtualDOM, container) {
let { type, props } = virtualDOM;
if (typeof type === "string") {
// 存储的是标签名:动态创建这样一个标签
let ele = document.createElement(type);
// 把新增的标签,增加到指定容器中
container.appendChild(ele);
}
};
下面就是设置DOM的属性了,这里提一下设置DOM的属性需要注意的点
我们一般设置属性都是直接 加"."
但是此时结构上并没有
但是往里面加内联样式是可以的
虽然这样结构中没有,但是在堆内存中是存在的
另外一种方式是setAttribute
可以看到结构上是有了,但是堆内存中没有
由此可以总结
下面我们就可以设置DOM的属性了
/* render:把虚拟DOM变为真实DOM */
export function render(virtualDOM, container) {
let { type, props } = virtualDOM;
if (typeof type === "string") {
// 存储的是标签名:动态创建这样一个标签
let ele = document.createElement(type);
// 为标签设置相关的属性 & 子节点
each(props, (value, key) => {
// className的处理:value存储的是样式类名
if (key === 'className') {
ele.className = value;
return;
}
// style的处理:value存储的是样式对象
if (key === 'style') {
each(value, (val, attr) => {
ele.style[attr] = val;
});
return;
}
// 子节点的处理:value存储的children属性值
if (key === 'children') {
let children = value;
if (!Array.isArray(children)) children = [children];
children.forEach(child => {
// 子节点是文本节点:直接插入即可
if (/^(string|number)$/.test(typeof child)) {
ele.appendChild(document.createTextNode(child));
return;
}
// 子节点又是一个virtualDOM:递归处理
render(child, ele);
});
return;
}
ele.setAttribute(key, value);
});
// 把新增的标签,增加到指定容器中
container.appendChild(ele);
}
};