参考文档:
React文档:Lists and Keys
React文档:Reconciliation
React 实践心得:key 属性的原理和用法 ——淘宝前端团队
A “key” is a special string attribute you need to include when creating lists of elements.
最早在React文档中看到关于组件的key,当时并没有很在意,直到某天在控制台看到了如下报错信息:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `App`. See https://fb.me/react-warning-keys for more information.
那么这个 key 究竟有多重要呢?
React文档中说应当在数组中给组件一个单独的 key ,React 用这个 key 作为组件的身份来识别具体对哪个组件做增删改各种操作。通常使用这个组件的唯一标识来作为key值,例如组件数据的id等:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
文档中写到,通常来说,不建议使用 index 作为 key 值。
毕竟 index 太易变,如果使用 index 作为 key,某种程度上来说就失去了使用 key 的意义,同时可能还会引发其他的问题。但是,如果你在通过遍历数组生成一组组件时没有为每个组件分配key值,React 会自动使用 index 作为 key 值来分配给每个组件。
注意:
- 关于 key 值的唯一性:
Keys used within arrays should be unique among their siblings. However they don’t need to be globally unique.
也就是说,我们可以在生成两组不同的组件时使用相同的一组 key。
- key 只提供给 React 使用
Keys serve as a hint to React but they don’t get passed to your components.
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
)
虽然 key 在使用时看起来和 props 很像,但你并不能在组件中通过 prop 取到它的值,如上述代码,在 Post 组件中,可以取到 props.id
的值,但 props.key
的值是拿不到的。
看起来没有使用数组 map() 时,你的组件可能也需要 key
认识到 key 的重要性之后,我也曾经天真地以为只需要在 map 中使用它,毕竟React文档好像也只是在强调 map,直到上一个某一天之后不太久的某天……
这次,我遇到了一个略微复杂的需求,大概是要先 map 一个数据数组,加载一个用户列表:
const userList = users.map((user) =>
<UserItem
key={user.id}
id={user.id} />
)
加载用户列表的过程当然每个 item 都需要一个
key 的,我选择用 user.id
作为 UserItem
的 key,然后在与某个 item 发生交互(例如点击)时,把 user.id
传给用户详情组件 <UserInfoCard id={user.id}/>
,拿着 user.id
去redux 里查询这个用户信息数据,如果没有就去后端请求这个数据,将后端返回的数据加入redux,再从 redux 中取得数据加载到组件,每次只展示当前点击的 UserItem 对应的 UserInfoCard,但所有的 UserInfo 组件都自动渲染在某一个节点(假设为Container)下。所有 UserInfoCard 一旦 mount,在父节点 Container 未发生变化时不会 unmount,仅会 update 数据和是否可见的属性。
面对这个逻辑,作为 UserInfoCard 的使用者,我没有关心它具体渲染在哪个节点下,自然忽略了它们共有一个父节点,也就是说,虽然没有使用 map 来生成一组 UserInfo 组件,但确确实实每增加一个UserInfoCard,都等同于在它们的父节点Container下为它们增加了一个同为 UserInfoCard 的好姐妹。
这样实际上和 map 生成的一组好兄弟本质上并没有什么区别,没有 key 值的话,React 难以对它们加以区分,另外本地没有的数据还需要从后端获取,假设这样一种情况:
- 点击一号用户的 UserItem,需向后端请求一号用户的数据
- 在后端的数据返回之前,点击二号用户的 UserItem,二号用户的数据也没有,向后端请求二号用户的数据
从点击一号用户到再点击二号用户的过程,对父节点 Container 来说只是一个
Update 的过程,在点击二号用户之后,收到一号用户的数据之前,对父节点来说,此时它有两个完全一样的React子元素(两个没有数据的 UserInfoCard ),此时收到了一个用户的信息数据,那么把它更新给哪个 UserInfoCard 呢?请求数据这种异步操作无法预料哪一条请求的数据会先到达,这就很容易造成混乱了——可能你点击了二号用户的UserItem,看到的却是一号用户的信息。
为了避免这种混乱,使用 key 属性就非常有必要了。
在使用 UserInfoCard 时将 user.id
作为
key 一并传给 UserInfoCard,这样用户在点击不同的 UserItem 时,key 也发生了变化,结合前面谈到的 key 的作用,不难发现,对 React 来说此时就很容易分辨究竟是哪个 UserInfoCard 需要更新,即使多次点击同一个 UserItem,也不会创建多个相同的 UserInfoCard,而是会更新该 key 对应的 UserInfoCard 实例。
总结:不止在 map 一个数组创建一组组件实例时需要使用 key 为每个数组元素标记,几乎在任何由统一结构或同一组件实例化为一组对象时,都应使用 key 作为它们的身份识别。
参考文档里最后一篇文章提到了在拥有复杂状态(每个状态有具体与之对应的对象)的组件里使用 key 来使组件在适当的时候触发销毁和重建,以此来保持组件内部逻辑的清晰,这也是文档里没有提到的一种对 React key 的应用。
网友评论