转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
javascript
涉及面试题:
1. 什么是"key"属性,在元素数组中使用它们有什么好处?
2. 索引作为键的影响是什么?
3. 安全地使用索引作为键的条件是什么?
4. 如何更新状态以及不更新状态?
编号:[react_14]
1 用 5 个问题了解"Diff 算法"
紧接上一篇文章:
- 当数据 state 发生变化:
javascript
this.state = {
content: "hello, qdywxs."
};
随即"数据 + JSX 模板"结合,生成新的"虚拟 DOM" :
["div", {id: "abc"}, ["span", {}, "hello, qdywxs."]]
比较 "原始虚拟 DOM"和"新的虚拟 DOM"的区别,找到区别是
<span>
中的内容;直接操作 DOM,改变
<span>
中的内容。
❓1️⃣在第 7 步中,我们需要比较"原始虚拟 DOM"和"新的虚拟 DOM"的区别,怎么比较呢? 答:Diff 算法。
我们把这两个"JS 对象"找区别(比对)的方式,称作"Diff 算法"。
Diff 是 difference 的简写,其含义很直观------差异。
❓2️⃣"虚拟 DOM"什么时候会被"比较"?
答:当"数据"发生改变的时候,"虚拟 DOM"才会去做新的"比较"!
❓3️⃣什么时候算"数据"发生改变呢?
答:要么改变了 state,要么改变了 props(🔗详见《React 进阶------② state、props 与 render 函数的关系》)。就代码而言,即调用了 setState
时,数据就发生了变化。
❓4️⃣我们之前的文章就说过, setState
是设计成"异步的",可为什么要这样设计呢?
答:之所以设计成"异步",是为了提高 React 底层的性能。
假设我们连续调用 3 次 setState
,且时间间隔非常小 。如果是"同步"的话,3 次数据变化,就需要做 3 次"虚拟 DOM"的比对,并更新 3 次页面。这样就比较浪费性能!
React 的做法是,把这连续 3 次的 setState
调用,合并成 1 次 。即,只做 1 次"虚拟 DOM"的比对,然后只去更新 1 次 DOM。这样就能省去了额外的 2 次"虚拟 DOM"比对所带来的性能耗费。
❓5️⃣"Diff 算法"是怎样对两个"虚拟 DOM"进行比较的呢?
答:采用"同层比较 "的方式------ 从最顶层开始,如果它们一致,React 就去比较第二层(以此类推 )。
-
假如,顶层"比较"时,它们两者不一致,React 就不会再往下"比较"了,它会将"原始 DOM"进行全部替换。
-
假如,顶层是一致的,就比较第二层,若第二层不一致,原理同上。
这样的"比较"和"替换"方式,乍一看好像挺浪费性能的(也确实可能消耗了些性能)------一层不一样就将下边全部替换,很多 DOM 都没被复用。
但由于其"同层比较"的算法很简单,直接带来的好处就是:比较的速度很快。比较的速度快了,在"比较"这件事情上,性能一下就被提升了!
2 key 值的重要性
在 React 中,当我们做循环渲染的时候,我们需要给渲染出的每一项增加一个 key
值!这个 key
值是每一项唯一的"标识符"。
❓为什么一定需要一个 key 值呢,它与"虚拟 DOM"和"Diff 算法"又有什么关系呢?
答:"key 值"可以有效地提高 "虚拟 DOM"比较 的性能。
1️⃣假设我有一个数组,数组里边包含 5 个数据。当页面第一次循环渲染的时候,这 5 个数据会被映射成 5 个"虚拟 DOM"节点,并生成一个小的"原始虚拟 DOM"树:
2️⃣当数据发生变化时,会生成一个"新的虚拟 DOM 树":
3️⃣然后我们会作两个"虚拟 DOM"的比较(理想的是如下图一样作比较):
4️⃣❌但如果每个"虚拟 DOM"都没有彼此的 key 值,即没有自己的"名字"。那此刻"比较"时,节点与节点间的关系就很难被确立!
此时,就需要做两层循环的比较(用下边的每一个分别去和上边的所有节点作比较),这种"比较"会极其消耗性能!
5️⃣🏆相反,如果当我们做循环渲染的时候,我们给渲染出的每一项增加一个 key
值!那此时的"比较"就很轻松了:
如,第一次循环的时候,我给 5 个节点分别取了 a
b
c
d
e
5 个名字;第二次循环时,同样的节点我取了同样的名字(这点是提升"性能"的关键)。
在作比较时,按节点的 key 值迅速做比较,最后发现只多出了一个 z
,然后只需将 z
增加到 DOM 树上就可以了,性能上是很优秀的。
3 为什么不建议用 index 做 key 值
紧接上文:
如,第一次循环的时候,我给 5 个节点分别取了
a
b
c
d
e
5 个名字;第二次循环时,同样的节点我取了同样的名字(这点是提升"性能"的关键) ,多出的节点,我取名为z
。
由于提升"性能"的关键点是"同样的节点我取了同样的名字 "。如果用 index
作为 key
值,我们不能保证同样的节点拥有同样的"名字"。
打开 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 = {
inputValue: "",
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
console.log("render")
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(
// ❌1️⃣我们在这里用了 index 作为了 key 值;
<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;
2️⃣打开页面,在 input 框输入一些内容:
此时:
lua
Oli --> key: 0
qdywxs --> key: 1
Oli and qdywxs --> key: 2
3️⃣点击列表项内容 Oli
:
此时:
lua
qdywxs --> key: 0
Oli and qdywxs --> key: 1
❗️这就直接造成一个问题:同一节点,前后的 key 值不相等,两个节点就无法建立起关系,此时 key 值就失去了它存在的意义!
❓应该用什么作为 key 值呢?
答:使用一个稳定的内容作为 key 值,才是最佳实践!
本例我们用每一项的 item 作为 key 值:
jsx
getTodoItem() {
return this.state.list.map((item, index) => {
return(
// ✅4️⃣用 item 作为 key 值;
<TodoItem
key={item}
content={item}
index={index}
itemDelete={this.handleItemDelete}
/>
)
})
}
打开页面查看效果(不管怎么操作,key 值都是稳定 的):
当然,"同层比较"、"key 值比较"都是"Diff 算法"中的一部分,"Diff 算法"还有很多其他的知识点,但当下,我们可以先掌握好这两个,以后有精力可以去自行拓展。
祝好,qdywxs ♥ you!