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} />; + return <Item key={name} name={name} />; })} <div> <button onClick={() => { updatePersons(prev => ["lily", ...prev]); }} > add person </button> </div> </div> ); }
这里假设每条数据其 name 值是不一样的,所以将它作为列表元素的唯一标识。
修正 `key` 之后的正常表现
问题的解决回到文章开头的问题,就可以理解其表现了。
const data1 = [10, 20]; const data2 = [50, 20, 10];
默认情况下,React 使用 index 作为 key。
对于第一条数据,其值由 10 变化到 50,动画正常,当再次设置时,其由 50 变回到 10。因为组件并没有重新初始化,所以其初始值确实是 50,所以看到了由 50 到 10 这个缩减的动画。
而对于第二条数据,因为前后值没变化,执行动画的 setTimeout 都不会执行。
修正的方法可以为元素指定一个随机的 key,这样每次组件都会重新渲染,不会复用之前的状态。
function App() { const [data, setData] = useState(data1); return ( <div> <button onClick={() => { setData(prev => (prev === data1 ? data2 : data1)); }} > switch data </button> {data.map((score, index) => { return ( - <div> + <div key={Math.random()}> <Bar score={score} /> </div> ); })} </div> ); }
修正后的百分比柱状条效果
将 key 设置成随机值是不推荐的做法,因为这样 React 就没法在渲染过程中对组件进行重用的优化。但像这里的特殊情况,你需要知道的是其中的原理,然后清楚自己在这样做时的影响。
总结虚拟 DOM 将操作浏览器 DOM 的成本一部分转嫁到了 JavaScript 中,即进行差异计算的成本。提高了渲染的效率,但某些情况下也会是一个坑。
需要注意的是,React 的差异算法高效性是在两个假设前提下进行的,
如果父元素不同,其子节点产生不同的树。
开发者可通过为元素指定 key 来标识元素的唯一性,提高 React 差异检测时的效率。