转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
javascript
涉及面试题:
1. 什么是 Virtual DOM?
2. Virtual DOM 如何工作?
3. Shadow DOM 和 Virtual DOM 之间有什么区别?
4. 什么是 React Fiber,它的主要目标是什么?
编号:[react_13]
1 没有"虚拟 DOM"时的痛点
上篇文章,我们清楚地理解了:当组件的 state
或者 props
发生改变的时候, render
函数就会重新执行,页面也随即被"重新渲染"。
在 React 中,实现这种"重新渲染"的操作时,它的"性能"是非常高的,因为它引入了"虚拟 DOM"的概念!
紧接着上一篇的代码,打开 TodoList.js
文件:
jsx
import React, { Component, Fragment } from "react";
import TodoItem from "./TodoItem";
import "./style.css";
class TodoList extends Component {
constructor(props) {
super(props);
this.state = { // 1️⃣当 state 发生改变时,
inputValue: "",
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() { // 2️⃣render 函数下边的函数体会重新执行;
/*
❓❓❓3️⃣但假设没有 React,我们要自己去实现这个功能,应该怎样思考呢?
请认真阅读下边的各个方法!
*/
return(
<Fragment>
<div>
<label htmlFor="insertArea">请输入要进行的事项:</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleBtnClick}>
提交
</button>
</div>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return(
<TodoItem
key={index}
content={item}
index={index}
itemDelete={this.handleItemDelete}
/>
)
})
}
handleInputChange(e) {
const value = e.target.value
this.setState(() => ({
inputValue: value
}))
}
handleBtnClick() {
this.setState((prevState) => ({
list: [...prevState.list, prevState.inputValue],
inputValue: ""
}))
}
handleItemDelete(index) {
this.setState((prevState) => {
const list = [...prevState.list]
list.splice(index, 1)
return {list}
})
}
}
export default TodoList;
假设没有 React,我想实现"数据"变化,页面跟着变化的功能,我们很惯性的会想到以下两种方法。
❌方法一:
-
定义一个 state 数据;
-
定义一个"JSX 模板";
-
将"数据 + JSX 模板"结合,生成"真实的 DOM"来显示;
-
当数据 state 发生改变;
-
"数据 + JSX 模板"相结合,生成"真实的 DOM",并替换"原始的 DOM"。
❗️缺陷------非常耗"性能": 第一次生成了一个完整的 DOM 片段 --> 数据变化后,又生成了一个完整的 DOM 片段 --> 然后用第二次的 DOM 替换第一次的 DOM
❎方法二(对"方法一"改良一下):
-
定义一个 state 数据;
-
定义一个"JSX 模板";
-
将"数据 + JSX 模板"结合,生成"真实的 DOM"来显示;
-
当数据 state 发生改变;
-
🔨 "数据 + JSX 模板"相结合,生成"真实的 DOM",但并不直接替换"原始的 DOM";
-
将"新的 DOM(DocumentFragment)"和"原始的 DOM"作对比,找差异;
-
找到某个"节点"(如
<input>
元素)发生了变化; -
只用"新的 DOM"中的
<input>
元素,替换掉"原始的 DOM"中的<input>
元素。
❗️优势和缺陷: "方法二"相对于"方法一",虽然步骤增加了,但"性能"上的确有一些提升:"局部替换"比"整体替换"消耗的性能更少。
但同时,其性能又损失了不少:新、旧 DOM 在"作对比,找差异"的过程中,性能又被"消耗"了。
这样"一增一减",性能其实也没多大提升!
2 "虚拟 DOM"带来的新篇章
基于此痛点,React 提出了"虚拟 DOM"方案。
✔️方法三(虚拟 DOM):
- 定义一个 state 数据:
javascript
this.state = {
content: "hello, Oli."
};
- 定义一个"JSX 模板":
jsx
render() {
return(
<div id="abc">
<span>{content}</span>
</div>
)
}
- "数据 + JSX 模板"结合,React 底层会调用
React.createElement()
接口,将 JSX 模板变成一个原始"虚拟 DOM" (定义:"虚拟 DOM"就是一个数组结构的 JS 对象,用它来描述"真实 DOM")。
"虚拟 DOM"的格式为:
1️⃣------第一项表示"最外层元素的标签名";
2️⃣------第二项表示"最外层标签的属性";
3️⃣------第三项表示"children 子节点",若"子节点"又是一个完整的 DOM,那么又需要以 [1️⃣, 2️⃣, 3️⃣]
的形式来写。
※本例中,"JSX 模板"转变为"JS 对象"的底层实现为(注意看 createElement
的用法):
jsx
render() {
/*
🚀-①:我们将"JSX 模板"用 craeteElement 方法来改写,其接收 3 个参数:
第一个参数为"最外层元素的标签名";
第二个参数为"最外层标签的属性";
第三个参数为"children 子节点"。
*/
return React.createElement("div", {id: "abc"}, React.createElement("span", {}, "hello, Oli."))
/*
🚀-②:我们发现,即使没有"JSX 模板",
我们依然能通过 React.createElement() 来实现 JSX 的功能。
但,会很繁杂!
这也正是 JSX 存在的原因,因为它看着真的很简单!
*/
/*
return(
<div id="abc">
<span>{content}</span>
</div>
)
*/
}
本例生成的原始"虚拟 DOM" 为: ["div", {id: "abc"}, ["span", {}, "hello, Oli."]]
- 根据上边的"虚拟 DOM"生成"真实 DOM":
javascript
<div id="abc">
<span>hello, Oli.</span>
</div>
- 当数据 state 发生变化:
javascript
this.state = {
content: "hello, qdywxs."
};
- 随即"数据 + JSX 模板"结合,生成新的"虚拟 DOM":
["div", {id: "abc"}, ["span", {}, "hello, qdywxs."]]
-
比较 "原始虚拟 DOM"和"新的虚拟 DOM"的区别,找到区别是
<span>
中的内容; -
直接操作 DOM,改变
<span>
中的内容。
🏆优势(性能大幅提升):
-
用 JS 生成一个"JS 对象",其性能消耗是非常小的。而"方法二"中,用 JS 去生成一个 DOM 元素,它的代价极高,其底层会调用
Web Application
这个级别的 API,性能损耗很大! -
第 7 步中,两个"虚拟 DOM"之间作对比(即两个"JS 对象"作对比),"Diff 算法"是不怎么消耗性能的。而"方法二"中,把两个"真实 DOM"作对比,其性能消耗极大。
3 "虚拟 DOM"之于"跨端应用"
有了"虚拟 DOM",我们可以用 React(React Native)去写"原生的应用"了。
其原理为: 在上边的第 3 步中,"数据 + JSX 模板"生成了一个"原始虚拟 DOM",这个"虚拟 DOM"是一个"JS 对象"。
"JS 对象 "不仅在"浏览器"里可以被识别,它在"原生应用------Android、iOS 机器上的代码"里也可以被很好地识别 ("原生应用"里是不存在 DOM 这个概念的,故"真实 DOM"在"原生应用"里不被识别,无法使用!)。
在"原生应用"里,有了生成的"虚拟 DOM"后,我们可以让它生成一些原生的组件。
这样的话,在复用"state"、"JSX 模板"等代码的优势下,让"虚拟 DOM"在不同"端"进行转化:
- 网页端:转化为"真实 DOM";
- 原生应用:转化为原生的"组件"。
这样的话,在"原生应用"里,也可以把相应页面展示出来了。
祝好,qdywxs ♥ you!