这便是元素身上的 key 属性。其值一定是能够唯一标识该元素的,这个唯一是指兄弟节点之间唯一即可,比如列表中同类型的列表元素。如果兄弟节点 key 重复,React 会有警告提醒。
再来看上面的��例,
<ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> <ul> <li key="2014">Connecticut</li> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul>
通过读取元素身上的 key,再次比较时 React 能够智能地得出结论,本次更新只需要插入 <li key="2014">Connecticut</li>,剩余的其他子节点可直接复用。这样处理子节点的 diff 时效率就大大提升了。
所以你通过遍历方式生成一堆子节点时,React 会提示你需要为元素设置 key 属性。
Warning: Each child in a list should have a unique "key" prop.默认情况下,如果没有显式指定 key,React 默认使用其在列表中的索引作为 key。但这个属性最好是来自需要渲染的数据条目的 id,这样能够最大程度地与数据保持一致,如果数据变化了,id 必然变化,则重新渲染。直接使用 for 循环中的 index 索引来做为 key 是不推荐的。因为索引不体现数据的变化,如果列表数据变化了,比如进行了排序,原来位置的数据可能不是原来的那条数据了,但因为索引没变,React 按照每个位置还是同一个元素的 diff 逻辑来处理,该位置的组件复用前一次渲染的状态,势必产生 bug。下面是一个简单展示这一问题的示例:
function Item({ name }) { const [score, setScore] = useState(); return ( <div> name:{name} <input type="text" onChange={e => { setScore(e.target.value); }} /> score is: {score} </div> ); } function App() { const [persons, updatePersons] = useState(["tom", "david"]); return ( <div> <h3>set age for each person</h3> {persons.map((name, index) => { return <Item key={index} name={name} />; })} <div> <button onClick={() => { updatePersons(prev => ["lily", ...prev]); }} > add person </button> </div> </div> ); }
上面的示例遍历一个包含了姓名的数组,为每个人生成一行可输入分数的表单项。同时我们将每个生成项的 key 设置成索引 index。
展示将 `key` 设置成索引导致组件内部状态不对的问题
可以看到,分数设置在列表中子组件中,当添加新的条目后,原来索引位置的组件复用之前的组件状态,因为该位置 key 相同,不会整个重新渲染。所以新增在第一位的 lily,本来还没有为其设置分数,但它使用了原来在那个位置的 tom 的分数,同时,其他元素因为位置变化了,他们所持有的状态都错位了。
修正 key 之后再次操作表现就正常了。