集帅(美)们,别再写 :key = “index“ 啦!

本文介绍了在Vue中使用`v-for`时,应避免使用`key=index`作为唯一标识符,因为这会导致在数据变动时虚拟DOM的不稳定,影响性能。作者解释了虚拟DOM的工作原理、diff算法以及为什么随机数作key也不可行。

浅聊一下

灵魂拷问:你有没有在v-for里使用过:key = "index",如果有,我希望你马上改正过来并且给我点个赞,如果没有,来都来了,顺手给我点个赞...

开始

在向掘友们解释为什么不能使用 :key = "index" 之前,我想我还得向你们铺垫一点东西

虚拟DOM

什么是虚拟DOM呢?虚拟DOM是一个对象,没想到吧...我们来看看Vue是如何将template模板里面的东西交给浏览器来渲染的

image.png

首先通过 compiler 将 template模板变成一个虚拟DOM,再将虚拟DOM转换成HTML,最后再交给浏览器V8引擎渲染,那么虚拟DOM是什么样的呢?

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
    <div id="app">
       <ul id="item">
           <li v-for="item in list" class="item">{{item}}</li>
       </ul>
    </div>
    <script>
        const { createApp, ref } = Vue
        createApp({
            setup() {
               const list = ref(['vue','js','html'])
               return {
                    list
                }
            }
        }).mount('#app')
    </script>
</body>

</html>

在这里,template模板实际上是

 <ul>
      <li v-for="item in list">{{item}}</li>
 </ul>

通过v-for循环,渲染出来了3个li

<ul>
    <li>vue<li>
    <li>js<li>
    <li>html<li>
</ul>

我们的compiler会将这个模板转化成虚拟DOM

let oldDom = {
    tagName = 'ul',
    props:{
        //存放id 和 class 等
        id:'item'
    },
    children[
        {
            tagName = 'li',
            props:{
                class:'item'
            },
            children:['vue']
        },
                {
            tagName = 'li',
            props:{
                class:'item'
            },
            children:['js']
        },
                {
            tagName = 'li',
            props:{
                class:'item'
            },
            children:['html']
        },
    ]
}

diff算法

给前面的例子来点刺激的,加上一个按钮和反转函数,点击按钮,list反转

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
    <div id="app">
       <ul>
           <li v-for="item in list">{{item}}</li>
       </ul>
       <button @click="change">change</button>
    </div>
    <script>
        const { createApp, ref } = Vue
        createApp({
            setup() {
               const list = ref(['唱','跳','rap','篮球'])
               const change = ()=>{
                list.value.reverse()
               }
               const add = ()=>{
                list.value.unshift('6')
               }
               return {
                    list,
                    change,
                }
            }
        }).mount('#app')
    </script>
</body>

</html>

点击change按钮,此时我们的DOM更改vue又是如何来更新DOM的呢?

image.png

 众所周知,回流和重绘会消耗极大的性能,而当DOM发生变更的时候会触发回流重绘,那么vue3就有一个diff算法,用来优化性能

image.png

当DOM更改,compiler会生成一个新的虚拟DOM,然后通过diff算法来生成一个补丁包,用来记录旧DOM和新DOM的差异,然后再拿到html里面进行修改,最后再交给浏览器V8进行渲染

简单介绍一下diff算法的比较规则

  1. 同层比较,是不是相同的结点,不相同直接废弃老DOM
  2. 是相同结点,比较结点上的属性,产生一个补丁包
  3. 继续比较下一层的子节点,采用双端对列的方式,尽量复用,产生一个补丁包
  4. 同上

image.png

别再写 :key = "index"

要说别写 :key = "index" ,我们得先明白key是用来干什么的...如果没有key,那么在diff算法对新旧虚拟DOM进行比较的时候就没法比较了,你看这里有两个一样的vue,当反转顺序以后diff算法不知道哪个vue该对应哪个vue了

image.png

如果我们用index来充当key的话来看,当我们在头部再插入一个结点的时候,后面的index其实是改变了的,导致diff算法在比较的时候认为他们与原虚拟DM都不相同,那么diff算法就等于没有用...

image.png

可以用随机数吗?


js

复制代码

<li v-for="item in list" :key="Math.random()">

想出这种办法的,也是一个狠人...当然是不行的,因为在template模板更新时,会产生一个新的虚拟DOM,而这个虚拟DOM里面的key也是随机值,和原虚拟DOM里的key99.99999%是不一样的...

结尾

希望你以后再也不会写 :key = "index" 了

作者:滚去睡觉
链接:https://juejin.cn/post/7337513012394115111
 

绑定rules应该在下面代码哪里添加: <div v-if="StepForms.length"> <div v-for="(step, index) in this.StepForms" :key="index"> <a-card :title="`Run Card Step${index + 1}`" style="margin-top: 10px"> <a-row> <a-col :span="12"> <a-form-model-item :prop="'StepForms.' + index + '.fabName'" label="Fab Area:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-select placeholder="请选择" v-model="step.fabName" style="width: 100%" :disabled="step.isSubmitted"> <a-select-option v-for="item in StepFabAreaList" :key="item">{{ item }}</a-select-option> </a-select> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.area'" label="Area:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-select placeholder="请选择" v-model="step.area" style="width: 100%" :disabled="step.isSubmitted"> <a-select-option v-for="item in StepAreaList" :key="item">{{ item }}</a-select-option> </a-select> </a-form-model-item> </a-col> <!-- RCP 字段 --> <a-col :span="12"> <a-form-model-item label="Check With RCP" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }"> <a-checkbox :checked="step.rcp === 'on'" @change="(e) => handleRcpChange(e, step)"> </a-checkbox> </a-form-model-item> </a-col> </a-row> <a-row> <a-col :span="16"> <a-form-model-item prop="'StepForms.' + index + '.stepNo'" label="Reference Step No" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12 }" :required='required'> <div style="display: flex; align-items: center; gap: 8px; white-space: nowrap;"> <a-checkbox :checked="step.noStepNo === 'on'" @change="(e) => handleNoStepNoChange(e, step)">NA </a-checkbox> <a-input v-model="step.stepNo" placeholder="Enter Step No" :disabled="step.isreadonlyStepNo || step.isSubmitted" style="flex: 1; height: 32px;" /> <a-button @click="getStepLotInfo(index)" style="height: 32px;">Go</a-button> <a-button @click="getfuturestepinfo(index)" style="height: 32px;">ViewFutureStep</a-button> </div> </a-form-model-item> </a-col> </a-row> <a-row> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.eqpMode'" label="Equipment:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 6, offset: 2 }" :required='required'> <a-select placeholder="请选择" v-model="step.eqpMode" style="width: 100%" :disabled="step.isSubmitted"> <a-select-option v-for="item in StepEqpModeList" :key="item">{{ item }}</a-select-option> </a-select> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.eqpModeName'" label="" :labelCol="{ span: 6 }" :wrapperCol="{ span: 20, offset: 2 }" :required='required'> <div style="display: flex; align-items: center; gap: 8px; Height: 40px;"> <a-input v-model="step.eqpModeName" :disabled="step.isSubmitted" /> <div style="color: red; white-space: nowrap;"> EqpGroup区分大小写,输入的EqpID最大长度7位!!! </div> </div> </a-form-model-item> </a-col> </a-row> <!-- Recipe 字段(根据条件显示) --> <template v-if="!step.showPhotoFields"> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.recipe'" label="Recipe:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <div style="display: flex; align-items: center; gap: 8px; Height: 40px;"> <a-input v-model="step.recipe" :disabled="step.isSubmitted" /> <div style="color: red; white-space: nowrap;"> 区分大小写!!! </div> </div> </a-form-model-item> </a-col> </template> <!-- Photo 相关字段(当 area 包含 photo 时显示) --> <template v-else> <a-row> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.trackOutRecipe'" label="TrackOut Recipe:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <template #help> <span style="color: red;"> 区分大小写!!!</span> </template> <a-input v-model="step.trackOutRecipe" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.scannerRecipe'" label="Scanner Recipe:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <template #help> <span style="color: red;"> 区分大小写!!!</span> </template> <a-input v-model="step.scannerRecipe" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item label="Recipe Type:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-radio-group :value="step.recipeType" @change="(e) => handleRecipeTypeChange(e, step, index)" :disabled="step.isSubmitted"> <a-radio value="Production">Production</a-radio> <a-radio value="Engineer" @click.stop="confirmEngineer(step, index)">Engineer</a-radio> </a-radio-group> </a-form-model-item> </a-col> <template v-if="step.recipeType === 'Engineer'"> <a-col :span="12"> <a-form-model-item label="Batch Type:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-radio-group :value="step.batchType" @change="(e) => handleBatchTypeChange(e, step, index)" :disabled="step.isSubmitted"> <a-radio value="Production">Production</a-radio> <a-radio value="Matrix" @click.stop="confirmMatrix(step, index)">Matrix</a-radio> </a-radio-group> </a-form-model-item> </a-col> </template> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.reticleId'" label="Reticle ID:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-input v-model="step.reticleId" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.energy'" label="Energy:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-input v-model="step.energy" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <template v-if="step.batchType === 'Matrix'"> <a-col :span="12"> <a-form-model-item label="Energy Step:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }"> <template #help> <span style="color: red;">(Value:-1000.0~1000.0)</span> </template> <a-input v-model="step.energyStep" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> </template> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.focus'" label="Focus:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-input v-model="step.focus" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <template v-if="step.batchType === 'Matrix'"> <a-col :span="12"> <a-form-model-item label="Focus Step:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <template #help> <span style="color: red;">(Value:-2.00~2.00)</span> </template> <a-input v-model="step.focusStep" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> </template> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.ovlData'" label="OVL Data:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-textarea v-model="step.ovlData" :rows="3" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> </a-row> </template> <a-row> <!-- WaferID 显示和弹窗按钮 --> <a-col :span="18"> <a-form-model-item prop="'StepForms.' + index + '.waferId'" label="WaferID" :labelCol="{ span: 3 }" :wrapperCol="{ span: 15 }" :required='required'> <a-input :value="step.waferId" placeholder="请选择 WaferID" @click="() => openWaferModal(index)" read-only style="cursor: pointer" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <!-- QTY 数量显示 --> <a-col :span="6"> <a-form-model-item prop="'StepForms.' + index + '.waferIdQty'" label="QTY" :labelCol="{ span: 3 }" :wrapperCol="{ span: 3 }" :required='required'> <a-input :value="step.waferIdQty.toString()" read-only :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> </a-row> <a-row> <!-- MeasWaferID 显示和弹窗按钮 --> <a-col :span="18"> <a-form-model-item prop="'StepForms.' + index + '.slotId'" label="MeasSlotId" :labelCol="{ span: 3 }" :wrapperCol="{ span: 15 }" :required='required'> <a-input :value="step.slotId" placeholder="请选择量测 WaferID" @click="() => openMeasWaferModal(index)" read-only style="cursor: pointer" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <!-- MeasWaferIDQTY 数量显示 --> <a-col :span="6"> <a-form-model-item prop="'StepForms.' + index + '.slotIdQty'" label="QTY" :labelCol="{ span: 3 }" :wrapperCol="{ span: 3 }" :required='required'> <a-input :value="step.slotIdQty.toString()" read-only :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> </a-row> <a-row> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.contaminationFlag'" label="ContaminationFlag:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 2 }" :required='required'> <a-select placeholder="请选择" v-model="step.contaminationFlag" style="width: 100%" :disabled="step.isSubmitted"> <a-select-option v-for="item in contaminationFlagList" :key="item">{{ item }}</a-select-option> </a-select> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.remark'" label="Remark:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }" :required='required'> <template #help> <span style="color: red;">如果输入中文,Runcard只能Manual Run!!!</span> </template> <a-textarea v-model="step.remark" placeholder="100字内..." :rows="3" :disabled="step.isSubmitted" /> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.ecdPlanName'" label="EDC Plan Name:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }" :required='required'> <a-input v-model="step.ecdPlanName" placeholder="Enter EDC PlanName" :disabled="step.isSubmitted" /> <a-button @click="getEdcPlanSpecInfomation(index)">Go</a-button> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item prop="'StepForms.' + index + '.spec'" label="量测规格SPEC:" :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }" :required='required'> <a-textarea v-model="step.spec" read-only style="overflow: auto; white-space: nowrap;" :row="3" /> </a-form-model-item> </a-col> </a-row> <!-- Qtime 展示区域 --> <a-card style="margin-top: 20px" title="StepEnd Qtime Information">222{{StepForms[index]}} <a-table :dataSource="step.stepQtimeEnds" :columns="StepQtimeStrEndColumns" :pagination="false" :rowKey="record => record.id || Math.random()" bordered> <!-- 注意这里使用 { record, index } 解构 --> <template #handleMode="record, index"> <a-select :value="index.handleMode" @change="(value) => onStepHandleModeChange(index, value)" style="width: 120px"> <a-select-option value="NULL">NULL</a-select-option> <a-select-option value="Ignore">Mapping</a-select-option> </a-select> </template> </a-table> </a-card> <!-- Qtime 展示区域 --> <a-card style="margin-top: 20px" title="StepStr Qtime Information">111{{StepForms[index]}} <a-table :dataSource="StepForms[index].stepQtimes" :columns="StepQtimeStrColumns" :pagination="false" :rowKey="record => record.id || Math.random()" bordered> </a-table> </a-card> <a-row> <a-col :span="24"> <a-form-model-item label="" :labelCol="{ span: 6 }" :wrapperCol="{ span: 12, offset: 10 }"> <a-button @click="updatestepinfo(index)">Update</a-button> <a-button @click="deletestepinfo(index)">Delete</a-button> <a-button @click="submitstepinfo(index)">Submit</a-button> </a-form-model-item> </a-col> </a-row> </a-card> </div> </div>
最新发布
12-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值