Skip to content

Commit a566dac

Browse files
committed
feat[ptr]: 以数组作为下标的影响: 数组变化后,相同位置的元素 key 相同,Vue 会认为是同一个元素,直接复用 DOM
但实际上数据已经变了,导致 DOM 和数据错位 特别是表单元素(input、select、textarea),它们的值存储在 DOM 节点上,不是响应式的,复用 DOM 后值就乱了,导致每次删除的都只可能是最后一个
1 parent 6943239 commit a566dac

4 files changed

Lines changed: 160 additions & 2 deletions

File tree

React/sandboxs/src/App.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import React from 'react';
77
// import { FileUpload } from './components';
88
import Timer from './test/Timer';
99

10+
import ListKey from './test/ListKey';
11+
1012
const el = (
1113
// 写在 组件中间{ }中的 会自动传入到 props.children
1214
// <CoverButton>
@@ -27,7 +29,8 @@ const el = (
2729
// }}
2830
// />
2931

30-
<Timer />
32+
// <Timer />
33+
<ListKey />
3134
)
3235

3336

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { useState } from "react";
2+
3+
export default function ListKey() {
4+
// 错误示范:使用 index 作为 key
5+
const [badList, setBadList] = useState([
6+
{ name: "项目 A" },
7+
{ name: "项目 B" },
8+
{ name: "项目 C" },
9+
]);
10+
const [badCounter, setBadCounter] = useState(4);
11+
12+
// 正确做法:使用唯一 id 作为 key
13+
const [goodList, setGoodList] = useState([
14+
{ id: 1, name: "项目 A" },
15+
{ id: 2, name: "项目 B" },
16+
{ id: 3, name: "项目 C" },
17+
]);
18+
const [goodCounter, setGoodCounter] = useState(4);
19+
20+
// 错误示范的方法
21+
const removeBadItem = (index) => {
22+
setBadList((prev) => prev.filter((_, i) => i !== index));
23+
};
24+
25+
const addBadItem = () => {
26+
setBadList((prev) => [
27+
...prev,
28+
{ name: `项目 ${String.fromCharCode(64 + badCounter)}` },
29+
]);
30+
setBadCounter((prev) => prev + 1);
31+
};
32+
33+
// 正确做法的方法
34+
const removeGoodItem = (id) => {
35+
setGoodList((prev) => prev.filter((item) => item.id !== id));
36+
};
37+
38+
const addGoodItem = () => {
39+
setGoodList((prev) => [
40+
...prev,
41+
{
42+
id: goodCounter,
43+
name: `项目 ${String.fromCharCode(64 + goodCounter)}`,
44+
},
45+
]);
46+
setGoodCounter((prev) => prev + 1);
47+
};
48+
49+
return (
50+
<div>
51+
<h2>错误示范:使用 index 作为 key</h2>
52+
<div>
53+
<ul>
54+
{badList.map((item, index) => (
55+
<li key={index}>
56+
<span>{item.name}</span>
57+
<input type="text" placeholder={`输入${item.name}的值`} />
58+
<button onClick={() => removeBadItem(index)}>删除</button>
59+
</li>
60+
))}
61+
</ul>
62+
<button onClick={addBadItem}>添加项</button>
63+
</div>
64+
65+
<h2>正确做法:使用唯一 id 作为 key</h2>
66+
<div>
67+
<ul>
68+
{goodList.map((item) => (
69+
<li key={item.id}>
70+
<span>{item.name}</span>
71+
<input type="text" placeholder={`输入${item.name}的值`} />
72+
<button onClick={() => removeGoodItem(item.id)}>删除</button>
73+
</li>
74+
))}
75+
</ul>
76+
<button onClick={addGoodItem}>添加项</button>
77+
</div>
78+
</div>
79+
);
80+
}

Vue2/sandboxs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"test:FetchData": "vue serve ./test/lifecycle/FetchData.vue",
1818
"test:Loading": "vue serve ./src/components/Loading/test.vue",
1919
"test:vLoading": "vue serve ./src/directives/loading/test.vue",
20-
"test:TreeListMenu": "vue serve ./src/components/TreeListMenu/test.vue"
20+
"test:TreeListMenu": "vue serve ./src/components/TreeListMenu/test.vue",
21+
"test:ListKey": "vue serve ./test/ListKey.vue"
2122
},
2223
"dependencies": {
2324
"axios": "^1.13.4",

Vue2/sandboxs/test/ListKey.vue

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<template>
2+
<div>
3+
<h2>错误示范:使用 index 作为 key</h2>
4+
<div>
5+
<ul>
6+
<li v-for="(item, index) in badList" :key="index">
7+
<span>{{ item.name }}</span>
8+
<input type="text" :placeholder="`输入${item.name}的值`" />
9+
<button @click="removeBadItem(index)">删除</button>
10+
</li>
11+
</ul>
12+
<button @click="addBadItem">添加项</button>
13+
</div>
14+
15+
<h2>正确做法:使用唯一 id 作为 key</h2>
16+
<div>
17+
<ul>
18+
<li v-for="item in goodList" :key="item.id">
19+
<span>{{ item.name }}</span>
20+
<input type="text" :placeholder="`输入${item.name}的值`" />
21+
<button @click="removeGoodItem(item.id)">删除</button>
22+
</li>
23+
</ul>
24+
<button @click="addGoodItem">添加项</button>
25+
</div>
26+
</div>
27+
</template>
28+
29+
<script>
30+
export default {
31+
data() {
32+
return {
33+
// 错误示范:使用 index 作为 key
34+
badList: [{ name: "项目 A" }, { name: "项目 B" }, { name: "项目 C" }],
35+
badCounter: 4,
36+
37+
// 正确做法:使用唯一 id 作为 key
38+
goodList: [
39+
{ id: 1, name: "项目 A" },
40+
{ id: 2, name: "项目 B" },
41+
{ id: 3, name: "项目 C" },
42+
],
43+
goodCounter: 4,
44+
};
45+
},
46+
methods: {
47+
// 错误示范的方法
48+
removeBadItem(index) {
49+
this.badList.splice(index, 1);
50+
},
51+
addBadItem() {
52+
this.badList.push({
53+
name: `项目 ${String.fromCharCode(64 + this.badCounter)}`,
54+
});
55+
this.badCounter++;
56+
},
57+
58+
// 正确做法的方法
59+
removeGoodItem(id) {
60+
const index = this.goodList.findIndex((item) => item.id === id);
61+
if (index > -1) {
62+
this.goodList.splice(index, 1);
63+
}
64+
},
65+
addGoodItem() {
66+
this.goodList.push({
67+
id: this.goodCounter, // 直接往后加就好
68+
name: `项目 ${String.fromCharCode(64 + this.goodCounter)}`,
69+
});
70+
this.goodCounter++;
71+
},
72+
},
73+
};
74+
</script>

0 commit comments

Comments
 (0)