vue&react中哪些类型可以作为 key

49 阅读2分钟

vuereact 中, key 都做为了一个重要的关键字存在,官方解释是为了性能优化(这里不讨论优化部分)

但是key有哪些类型,又为什么是那些类型,还需要深入代码进行研究 talk is cheap, show me the code(只讨论为什么不能用定义之外的类型)

  1. 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 基本就是处于无用的状态了

  1. react 中的 key

只允许 stringnumber 存在

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 的实现就更简单了,直接把所有的 key stringfy 了一下,这下子 就连 symbol 也不行了

  1. vue3.x 中的 key

只允许 stringnumber 存在

很眼熟,和 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.xTransition ****组件中,使用了 const key = String(vnode.key); 这类语法,这里的key被用来

  1. 对消失的节点进行一个缓存
  2. 验证前后消失和现实的node是否是同一个 node

vue/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;
  }