(Vue) key值的作用

今天思考一个问题,在子组件中,key值的作用是什么?
如果一个组件,<A key="1" />改边key的值,<A key="2" />,发生什么?
实践出真理,测试一下:
首先,创建一个子组件:

//components/keyCom.vue
<template>
  <div>
      <p>{{ptext}}</p>
  </div>
</template>

一个非常简单的组件,在各个生命周期上,绑定事件:

export default {
    data(){
        return {
            ptext:"测试文本"
        }
    },
    beforeCreate(){
        console.log('enter beforeCreate')
    },
    created(){
        console.log('enter created')
    },
    beforeMount(){
        console.log('enter beforeMount')
    },
    mounted(){
        console.log('enter mounted')
    },
    beforeDestroy(){
        console.log('enter beforeDestroy')
    },
    destroyed(){
        console.log('enter destroyed')
    }
}

接下来,通过父组件修改子组件的key值:

<template>
<div>
  <Key-Com :key="key" />
  <a class='change-btn' @click='changeKey()'>切换key</a>
</div>
</template>

<script>
import KeyCom from '@/components/keyCom'
export default {
    data(){
        return {
            key:1
        }
    },
    components:{
        KeyCom
    },
    methods:{
        changeKey:function(){
            this.key=2;
        }
    }
}
</script>

看看运行起来是什么情况:

切换key值之前

点击按钮之后,发现:

修改了key值之后

组件经历了一个全新的生命周期,这是为何?为什么同样一个组件,仅仅改变了它上面的key值,就会重新挂载一个新组件?
之前了解到,key值的最大作用,是在渲染列表的时候,diff算法使用到,那么我们就来看看diff的过程是如何?
分析vue的源码,可以知道,diff算法是从patch函数开始:

patch:function(oldVnode,vnode){
    if(sameVnode(oldVnode,vnode)){
       patchVnode(oldVnode,vnode)
    } else {
       const oEl = oldVnode.el;
       let parentEle = api.parentNode(oEl);
       createEle(vnode);
       if(parent!==null){
          api.insertBefore(parentEle,vnode.el,api.nextSibling(oEl));
          api.removeChild(parentEle,oldVnode.el);
          oldVnode = null
       }
    }
    return vnode;
},

通过patch函数,可以看到,首先需要对比两个节点是否是相同节点,(相同的组件,难道不是相同节点吗?)
进入sameVnode函数看看:

        sameVnode(a,b){
            return (
                a.key === b.key && // key值
                a.tag === b.tag && // 标签名
                a.isComment === b.isComment && //是否为注释节点
                //是否都定义了data,data包含一些具体信息,例如onclick
                isDef(a.data) === isDef(b,data) &&
                sameInputType(a,b) //当标签是input,type必须相同
            )
        }

恍然大悟,原来在diff的时候,不仅是对比元素的标签名,还会去对比元素的key值,key值一旦改变,就算子节点的内容一模一样,也是会进入到patch函数的else中,那么这个时候,执行的操作就是新建新组件=>删除旧组件=>添加新组件。
因此,可以看到生命周期是新组件的生命周期先执行,再进行旧组件的销毁,接着挂载新组件。

emmmm...
那么再思考深一层的问题,如果是列表渲染的时候,key值设为id,和index会有什么区别呢??
同样的做一个测验:创建一个子组件,子组件里包含一个孙子组件

<template>
  <div>
    {{text}}
    <input v-model="x">
    <button @click="onDelete">delete</button>
  </div>
</template>

<script>
export default {
  name: "Child",
  props: ["text"],
  data() {
    return {
      x: "在这输入"
    };
  },
  methods: {
    onDelete() {
      this.$emit("delete");
    }
  }
};
</script>

接着,在原先的<key-com/>组件里:

<template>
  <div>
      <Child class="child" v-for="(item,i) in array" :key='i' :text="item" @delete="remove(i)" />
      <Child class="child" v-for="(item,i) in array2" :key='item.id' :text="item.value" @delete="remove2(i)" />
  </div>
</template>

创建2个Child组件,它们的区别就是一个使用index作为key,一个使用id作为key:

data(){
    return {
            ptext:"测试文本",
            array: ['111','222','333'],
            array2: [{id:1,value:'文本1'},{id:2,value:'文本2'},{id:3,value:'文本3'}]
    }
}

运行之后就可以看到它们的区别:

image.png

先看上面的三行,这个是使用index作为key值的组件,当修改其中222这行的input值,然后点击删除:
改变第二行的input值

删除第二项之后

删除之后发现,这与我们的预知不符呀,因为 data 里的数组从 [1,2,3] 变成了 [1,3]。
这个可以看到vue中数组遍历的规则:首先对比1和1,发现1没变,然后对比2和3,发现2变成了3,接着对比3和undefined,把3删掉。
所以步骤是:2变成3=>删除3。
那么在删除的时候,因为input的值是孙子组件,里面的值不受2变成3的影响,所以就地复用
再看下面这个列表,使用id作为key值。当我们修改了第二项的input值,然后删除第二项的时候,会把第二项完全删掉,符合我们的预期:
修改第二项的input值

删除第二项之后

原本的数组是:

array2: [
  {id:1,value:'文本1'},
  {id:2,value:'文本2'},
  {id:3,value:'文本3'}
]

点击删除之后数组是:

array2: [
  {id:1,value:'文本1'},
  {id:3,value:'文本3'}
]

先对比id从[1,2,3]变成了[1,3],即第二项被删除了。
因此:key值为何不能用index作为值?
如果你用index作为key值的时候,在删除第二项时,index就从1,2,3变成1,2;而不是1,3。

结论

VUE是通过比对组件自身新旧vdom进行更新的。key的作用是辅助判断新旧vdom节点在逻辑上是不是同一个对象。
因此可以确定,渲染列表时,key值需要一个唯一确定的id来赋值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 什么是diff算法 react 作为一款最主流的前端框架之一,在设计的时候除了简化操作之外,最注重的地方就是节省性...
    鹤仔z阅读 1,241评论 0 7
  • 1 .index值不是一定不变的,如果不加key值的话,删除前面的项。后面的index可能变也可能不变,比如加个定...
    skoll阅读 18,404评论 1 8
  • 文章首发于个人博客 这是我 Deep In React 系列的第二篇文章,如果还没有读过的强烈建议你先读第一篇:详...
    勿忘巛心安阅读 1,047评论 1 2
  • width: 65%;border: 1px solid #ddd;outline: 1300px solid #...
    邵胜奥阅读 4,812评论 0 1
  • 目前,前端领域中 React 势头正盛,使用者众多却少有能够深入剖析内部实现机制和原理。本系列文章希望通过剖析 R...
    流动码文阅读 971评论 0 11