Open yaofly2012 opened 4 years ago
React组件render
方法返回的是个React元素构成的树。当组件的props
或者state
发生变更时,render
方法会返回一个新的React元素构成的树。React采用一个启发式(简化的)diffing算法计算出两颗树的差异。详细的得看看React协调。
React用key
标记React元素。
key
的描述A “key” is a special string attribute you need to include when creating lists of elements. 列表元素必须要指定
key
属性。
没有指定key
属性会提示:
Warning: Each child in a list should have a unique "key" prop.
Keys Must Only Be Unique Among Siblings 兄弟(siblings)之间唯一(不必全局唯一)。
不唯一会提示:
Warning: Encountered two children with the same key,
xxx
. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
不唯一造成的影响:
重复key
的组件可能会重复渲染或者被忽略(测试v@16.8.6版本是被渲染的,只是控制台有个warning)。但是行为不保证,未来可能会变更。
总之行为不可预测(unpredictable),不要存在重复key
。
key
值充当React元素标识符,要能唯一识别数据,并且每次re-render
时子元素的key
要保持不变,否则影响diffing性能。
key
属性的取值可以是任意类型吗?
直接看ts声明:
type Key = string | number;
/**
* @internal You shouldn't need to use this type since you never see these attributes
* inside your component or have to validate them.
*/
interface Attributes {
key?: Key | null | undefined;
}
引用类型会被转成字符串。
优先valueOf
,没有再toString
:
function NumberList(props) {
const { numbers } = props;
const listItems = numbers.map(number =>
<li key={CustomerKey(number)}>{number}</li> // 相当于 CustomerKey(number) + ''
);
return (
<ul>{listItems}</ul>
)
}
function CustomerKey(number) {
return {
toString: () => {
console.log(`toString`)
return number;
},
valueOf: () => {
console.log(`valueOf`)
return number * 2;
}
}
}
复用子组件实例,避免重复销毁-创建组件实例。即在re-render
过程中如果key
不变,则复用之前的组件实例(组件实例状态不变)。
Demo:
// 旧v-dom
<ul>
<li key="first">first</li>
<li key="second">second</li>
</ul>
// 新v-dom
<ul>
<li key="zero">zero</li>
<li key="first">first</li>
<li key="second">second</li>
</ul>
React只创建元素<li key="zero">zero</li>
插入到列表首部,剩下两个元素复用。
当使用index
作为key
时,但实际新创建的是index=2
的元素,前面两个元素复用(不过prop
发生变化)。
key
引发的问题?经常会看到用数组索引作为key
。
key
React元素的key
变更等同类型变更,React会视为不同的React元素树,会重新创建组件实例。
import { useEffect, useState } from "react";
export default function App() {
const [key, setKey] = useState();
return (
<div>
<button onClick={() => setKey(Date.now())}>Update Key</button>
<Foo key={key} />
</div>
);
}
function Foo() {
useEffect(() => {
console.log("Foo.mounted");
return () => {
console.log("Foo.willUnmount");
};
}, []);
return <h2>Foo</h2>;
}
点击【Update Key】按钮都会造成组件Foo
重新创建。
index
作为key
行不行?When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort.
We don’t recommend using indexes for keys if the order of items may change.
If you choose not to assign an explicit key to list items then React will default to using indexes as keys. 但是此时React还会给出Warning。
总之:虽然React内部默认采用index
作为列表元素的key
,但是官方不建议使用索引index
作为key
。
index
不能作为key
?索引index
表示的是子元素位置,索引index
作为key
本质是表示子元素的位置能够识别子元素。特定的条件下索引可能能代表元素,但是如果列表数据发生排序、增加或者删除操作会造成,可能会造成错误的渲染效果,以及对性能造成影响。
总之索引index
不是合适的key
。
key
属性本质上任何组件都可以声明key
属性,只不过非列表的元素key
属性不是必须的。不管是否是列表元素key
属性的用法是一致的。
如果子元素发生位置变化(类似列表发生排序),也可以利用key
属性复用子组件实例。
Demo:
import { useEffect, useState } from "react"
export default () => {
const [swapped, setSwapped] = useState();
return (
<div>
<button onClick={() => setSwapped(swapped => !swapped)}>Swap</button>
{
swapped
? (
<>
<Right key="right"/>
<Left key="left"/>
</>
)
: (
<>
<Left key="left" />
<Right key="right" />
</>
)
}
</div>
)
}
Use unique and constant keys when rendering dynamic children, or expect strange things to happen. 动态子元素应该增加
key
?
怎样理解key is not really about performance, it's more about identity
组件的props或者state发生变化时react会自动同步UI页面。
优化方式1: 批处理【待研究】
https://zhuanlan.zhihu.com/p/51483167 http://echizen.github.io/tech/2019/04-06-react-fiber https://zhuanlan.zhihu.com/p/26027085