在
vue、react中,key都做为了一个重要的关键字存在,官方解释是为了性能优化(这里不讨论优化部分)但是
key有哪些类型,又为什么是那些类型,还需要深入代码进行研究 talk is cheap, show me the code(只讨论为什么不能用定义之外的类型)
-
vue2.x 中的 key
key 的类型为显示的定义为 string number symbol undefined
但是在很多的代码中可以看到有人使用了一个
object作为一个key存在,貌似理论上也没错?毕竟key就是用来进行diff的,而diff的过程,使用object貌似也没什么问题
vue/src/core/vdom/patch.ts#56
function createKeyToOldIdx(children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
在
vue2.x的源码中,可以看到有不少的地方都是使用了key作为了一个对象的key键而存在,而看下图,当 一个object作为key存在放入 对象时,会被显示地转化为[object object]导致所有其他的key全都会被覆盖,这样vue引以为傲的diff基本就是处于无用的状态了
-
react 中的 key
只允许 string 和 number 存在
packages/react-reconciler/src/ReactPortal.js#15
export function createPortal(
children: ReactNodeList,
containerInfo: any,
// TODO: figure out the API for cross-renderer implementation. implementation: any,
key: ?string = null,
): ReactPortal {
if (__DEV__) {
checkKeyStringCoercion(key);
}
return {
// This tag allow us to uniquely identify this as a React Portal $$typeof: REACT_PORTAL_TYPE,
key: key == null ? null : '' + key,
children,
containerInfo,
implementation,
};
}
至于
react的实现就更简单了,直接把所有的keystringfy了一下,这下子 就连symbol也不行了
-
vue3.x 中的 key
只允许 string 和 number 存在
很眼熟,和
react保持一致了,也就是说,对比 2.x 来说,甚至还少了symbol这个类型别急,我们先来看
diff的过程
vue/dist/vue.runtime.global.js#5855
const keyToNewIndexMap = new Map();
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i])
: normalizeVNode(c2[i]));
if (nextChild.key != null) {
if ( keyToNewIndexMap.has(nextChild.key)) {
warn(`Duplicate keys found during update:`, JSON.stringify(nextChild.key), `Make sure keys are unique.`);
}
keyToNewIndexMap.set(nextChild.key, i);
}
}
从这一段代码来说,其实
key无论是什么类型,vue3.x都能hold住了,毕竟Map能使用基本所有类型作为key(如果忽视掉那个**JSON.stringify**(nextChild.key)的话)
但是 ,所以要说一个但是
vue3.x在Transition****组件中,使用了const key =String(vnode.key);这类语法,这里的key被用来
- 对消失的节点进行一个缓存
- 验证前后消失和现实的
node是否是同一个nodevue/dist/vue.runtime.global.js#3834
function resolveTransitionHooks(vnode, props, state, instance) {
const { appear, mode, persisted = false, onBeforeEnter, onEnter, onAfterEnter, onEnterCancelled, onBeforeLeave, onLeave, onAfterLeave, onLeaveCancelled, onBeforeAppear, onAppear, onAfterAppear, onAppearCancelled } = props;
const key = String(vnode.key);
const leavingVNodesCache = getLeavingNodesForType(state, vnode);
const callHook = (hook, args) => {
hook &&
callWithAsyncErrorHandling(hook, instance, 9 /* TRANSITION_HOOK */ , args);
};
const hooks = {
...
},
leave(el, remove) {
const key = String(vnode.key);
...
},
...
};
return hooks;
}