一、验证 Diffing 算法
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>验证 Diffing 算法</title>
</head>
<body>
<div id="test"></div>
</body>
<script src="../js/react17.0/react.development.js" type="text/javascript"></script>
<script src="../js/react17.0/react-dom.development.js" type="text/javascript"></script>
<script src="../js/react17.0/babel.min.js" type="text/javascript"></script>
<script type="text/babel">
class Time extends React.Component {
state = { date: new Date() };
componentDidMount() {
setInterval(() => {
this.setState({
date: new Date(),
});
}, 1000);
}
// 这里更新的最小粒度是文本节点:{this.state.date.toTimeString()}
// 在两个输入框中输入内容,内容都不会因为更新而改变
render() {
return (
<div>
<input type="text" />
<span>
现在是:{this.state.date.toTimeString()}
<input type="text" />
</span>
</div>
);
}
}
ReactDOM.render(<Time />, document.getElementById("test"));
</script>
</html>
二、key 的作用
1、基本介绍
-
key 是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用
-
当状态中的数据发生变化时,React 会根据新数据生成新的虚拟 DOM,随后 React 进行新虚拟 DOM 与旧虚拟 DOM 的 diff 比较
-
旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key
-
若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
-
若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
-
-
旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
- 根据数据创建新的真实 DOM,随后渲染到到页面
-
2、用 index 作为 key 可能会引发的问题
-
若对数据进行逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实 DOM 更新,界面效果没问题,但效率低
-
如果结构中还包含输入类的 DOM:会产生错误的真实 DOM 更新,界面有问题
- 注:如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表展示,使用 index 作为 key 是没有问题的
3、开发中如何选择 key
-
最好使用每条数据的唯一标识作为 key,比如 id、手机号、身份证号、学号等
-
如果确定只是简单的展示数据,用 index 也可以
4、演示
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>key 的作用</title>
</head>
<body>
<div id="test"></div>
</body>
<script src="../js/react17.0/react.development.js" type="text/javascript"></script>
<script src="../js/react17.0/react-dom.development.js" type="text/javascript"></script>
<script src="../js/react17.0/babel.min.js" type="text/javascript"></script>
<script type="text/babel">
// ----------------------------------------------------------------------------------------------------
class Person extends React.Component {
state = {
persons: [
{ id: 1, name: "小张", age: 18 },
{ id: 2, name: "小李", age: 19 },
],
};
add = () => {
const { persons } = this.state;
const p = { id: persons.length + 1, name: "小王", age: 20 };
this.setState({ persons: [p, ...persons] });
};
render() {
return (
<div>
<button onClick={this.add}>添加一个小王</button>
<h3>使用 index(索引值)作为 key</h3>
<ul>
{this.state.persons.map((personObj, index) => {
return (
<li key={index}>
{personObj.name} --- {personObj.age}
<input type="text" />
</li>
);
})}
</ul>
<hr />
<h3>使用 id(数据的唯一标识)作为 key</h3>
<ul>
{this.state.persons.map((personObj) => {
return (
<li key={personObj.id}>
{personObj.name} --- {personObj.age}
<input type="text" />
</li>
);
})}
</ul>
</div>
);
}
}
ReactDOM.render(<Person />, document.getElementById("test"));
</script>
</html>
-
逆序添加,不包含输入类的 DOM
使用 index 索引值作为 key
初始数据
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
初始的虚拟 DOM
- 小张 --- 18
- 小李 --- 19
- 小王 --- 20 (对比旧虚拟 DOM 中有没有 key 为 0 的虚拟 DOM,有,对比内容有没有变化,有,渲染)
- 小张 --- 18 (对比旧虚拟 DOM 中有没有 key 为 1 的虚拟 DOM,有,对比内容有没有变化,有,渲染)
- 小李 --- 19 (对比旧虚拟 DOM 中有没有 key 为 2 的虚拟 DOM,没有,渲染)
- 小张 --- 18
- 小李 --- 19
- 小王 --- 20 (对比旧虚拟 DOM 中有没有 key 为 3 的虚拟 DOM,没有,渲染)
- 小张 --- 18 (对比旧虚拟 DOM 中有没有 key 为 1 的虚拟 DOM,有,对比内容有没有变化,没有,复用)
- 小李 --- 19 (对比旧虚拟 DOM 中有没有 key 为 2 的虚拟 DOM,有,对比内容有没有变化,没有,复用)
-
逆序添加,包含输入类的 DOM
使用 index 索引值作为 key
初始数据
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
初始的虚拟 DOM
- 小张 --- 18
- 小李 --- 19
- 小张 --- 18 (输入 10)
- 小李 --- 19
- 小王 --- 20 (输入 10)(对比旧虚拟 DOM 中有没有 key 为 0 的虚拟 DOM,有,对比内容有没有变化,有,渲染,对比其子虚拟 DOM 有没有变化,没有,复用)
- 小张 --- 18 (对比旧虚拟 DOM 中有没有 key 为 1 的虚拟 DOM,有,对比内容有没有变化,有,渲染,对比其子虚拟 DOM 有没有变化,没有,复用)
- 小李 --- 19 (对比旧虚拟 DOM 中有没有 key 为 2 的虚拟 DOM,没有,渲染)
- 小张 --- 18
- 小李 --- 19
- 小张 --- 18 (输入 10)
- 小李 --- 19
- 小王 --- 20 (对比旧虚拟 DOM 中有没有 key 为 3 的虚拟 DOM,没有,渲染)
- 小张 --- 18 (输入 10)(对比旧虚拟 DOM 中有没有 key 为 1 的虚拟 DOM,有,对比内容有没有变化,没有,复用,对比其子虚拟 DOM 有没有变化,没有,复用)
- 小李 --- 19 (对比旧虚拟 DOM 中有没有 key 为 2 的虚拟 DOM,有,对比内容有没有变化,没有,复用,对比其子虚拟 DOM 有没有变化,没有,复用)
更新后的数据
{ id: 3, name: "小王", age: 20 }
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
更新数据后的虚拟 DOM
使用 id 唯一标识作为 key
初始数据
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
初始的虚拟 DOM
更新后的数据
{ id: 3, name: "小王", age: 20 }
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
更新数据后的虚拟 DOM
输入内容
更新后的数据
{ id: 3, name: "小王", age: 20 }
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
更新数据后的虚拟 DOM
使用 id 唯一标识作为 key
初始数据
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
初始的虚拟 DOM
输入内容
更新后的数据
{ id: 3, name: "小王", age: 20 }
{ id: 1, name: "小张", age: 18 }
{ id: 2, name: "小李", age: 19 }
更新数据后的虚拟 DOM
- 注:子虚拟 DOM 的变化,指的是 input 元素本身,而不是 input 的 value 属性值