10個你可能不知道的Vue開發(fā)技巧

使用Vue開發(fā)已經(jīng)幾年的時間了,今天將 10 個日常工作中實踐以及從其他國外文章看到的小技巧分享出來,希望能夠讓大家可以更愉快的擼碼!

1. .sync修飾符實現(xiàn)props雙向數(shù)據(jù)綁定

Vue的數(shù)據(jù)流向是單向數(shù)據(jù)流,即:父組件通過屬性綁定將數(shù)據(jù)傳給子組件,子組件通過props接收,但子組件中無法對props的數(shù)據(jù)修改來更新父組件的數(shù)據(jù),只能通過$emit派發(fā)事件的方式,父組件接收到到事件后執(zhí)行修改。Vue2.30 以后新增了一個sync屬性,可以實現(xiàn)子組件派發(fā)事件時執(zhí)行修改父組件數(shù)據(jù),無需再父組件接收事件進(jìn)行更改。

示例:在父組件中控制子組件的顯示隱藏

  • 普通實現(xiàn)方式:
// 父組件
<template>
  <div>
    <button @click="handleClick">click me</button>
    <child
      :visible="visible"
      @on-success="handleSuccess"
      @on-cancel="handleCancel"
    ></child>
  </div>
</template>
<script>
  export default {
      data() {
          return {
              visible: false
          }
      }
      methods: {
          handleClick() {
              this.visible = true
          },
          handleSuccess() {
              this.visible = false
          },
          handleCancel() {
              this.visible = false
          }
      }
  }
</script>

// 子組件
<template>
  <div class="box" v-show="visible">
    <input type="text" />
    <div>
      <button @click="cancel">取消</button>
      <button @click="submit">確定</button>
    </div>
  </div>
</template>
<script>
  export default {
    props: {
      visible: {
        type: Boolean,
        default: false,
      },
    },
    methods: {
      submit() {
        this.$emit("on-success");
      },
      cancel() {
        this.$emit("on-cancel");
      },
    },
  };
</script>
  • .sync`修飾符實現(xiàn)方式
// 父組件
<template>
  <div>
    <button @click="handleClick">click me</button>
    // 添加sync修飾符,相當(dāng)于<child
      :visible="visible"
      @update:visible="visible=$event"
    ></child>
    <child :visible.sync="visible"></child>
  </div>
</template>
<script>
  export default {
      data() {
          return {
              visible: false
          }
      }
      methods: {
          handleClick() {
              this.visible = true
          }
      }
  }
</script>

// 子組件
<template>
  <div class="box" v-show="visible">
    <input type="text" />
    <div>
      <button @click="cancel">取消</button>
      <button @click="submit">確定</button>
    </div>
  </div>
</template>
<script>
  export default {
    props: {
      visible: {
        type: Boolean,
        default: false,
      },
    },
    methods: {
      submit() {
        this.$emit("update:visible", false);
      },
      cancel() {
        this.$emit("update:visible", false);
      },
    },
  };
</script>

2. 監(jiān)聽生命周期Hook

2.1. 組件外部(父組件)監(jiān)聽(子)其他組件的生命周期函數(shù)

在有些業(yè)務(wù)場景下,在父組件中我們需要監(jiān)聽子組件,或者第三方組件的生命周期函數(shù),然后來進(jìn)行一些業(yè)務(wù)邏輯處理,但是組件內(nèi)部有沒有提供change事件時,此時我們可以使用hook來監(jiān)聽所有的生命周期函數(shù)。方式: @hook:鉤子函數(shù)

<template>
  <!--通過@hook:updated監(jiān)聽組件的updated生命鉤子函數(shù)-->
  <!--組件的所有生命周期鉤子都可以通過@hook:鉤子函數(shù)名 來監(jiān)聽觸發(fā)-->
  <custom-select @hook:updated="handleSelectUpdated" />
</template>
<script>
  import CustomSelect from "../components/custom-select";
  export default {
    components: {
      CustomSelect,
    },
    methods: {
      handleSelectUpdated() {
        console.log("custom-select組件的updated鉤子函數(shù)被觸發(fā)");
      },
    },
  };
</script>

2.2. 監(jiān)聽組件內(nèi)部的生命周期函數(shù)

在組件內(nèi)部,如果想要監(jiān)聽組件的生命周期鉤子,可以使用$on,$once

示例:使用 echart 時,監(jiān)聽窗口改變事件,組件銷毀時取消監(jiān)聽,通常是在mounted生命周期中設(shè)置監(jiān)聽,beforeDestroy鉤子中銷毀監(jiān)聽。這樣就是要寫在不同的地方,可以使用this.$once('hook:beforeDestroy'),()=> {}這種方式監(jiān)聽beforeDestroy鉤子,在這個鉤子處罰時銷毀。
一次性監(jiān)聽使用$once,一直監(jiān)聽使用$on。

export default {
  mounted() {
    this.chart = echarts.init(this.$el);
    // 監(jiān)聽窗口發(fā)生變化,resize組件
    window.addEventListener("resize", this.handleResizeChart);
    // 通過hook監(jiān)聽組件銷毀鉤子函數(shù),并取消監(jiān)聽事件
    this.$once("hook:beforeDestroy", () => {
      window.removeEventListener("resize", this.handleResizeChart);
    });
  },
  methods: {
    handleResizeChart() {
      // do something
    },
  },
};

3. 深度作用選擇器

我們在寫Vue組件的時候為了避免當(dāng)前組件的樣式對子組件產(chǎn)生影響,通常我們會在當(dāng)前組件的style標(biāo)簽上加上scoped,這樣在這個組件中寫的樣式只會作用于當(dāng)前組件,不會對子組件產(chǎn)生影響。

<style scoped>
.example {
    color: red;
}
</style>

這樣轉(zhuǎn)換后的結(jié)果:

<style>
.example[data-v-f3f3eg9] {
    color: red;
}
</style>

但是有時候,我們引入的第三方組件,我們希望在當(dāng)前組件中修改第三方組件的樣式,對子組件也產(chǎn)生作用,同時跟第三方組件無關(guān)的樣式繼續(xù)scoped。
那么我們可以使用以下兩種方式:

  • 混用本地和全局樣式
    即:可以在一個組件中同時使用有 scoped 和非 scoped 樣式。
<style>
/* 全局樣式 */
</style>

<style scoped>
/* 本地樣式 */
</style>
  • 使用操作符:>>>、/deep/::v-deep
    即:如果你希望scoped 樣式中的一個選擇器能夠作用得“更深”,例如影響子組件,就可以使用操作符。
<style scoped>
.a >>> .b { /* ... */ }
/*或*/
.a /deep/ .b { /* ... */ }
/*或*/
.a ::v-deep .b { /* ... */ }
/* .b選擇器的樣式不僅可以作用當(dāng)前組件,也可以作用于子組件 */
</style>

編譯后:

.a[data-v-f3f3eg9] .b {
  /* ... */
}

4. 組件初始化時觸發(fā)Watcher

默認(rèn)情況下,Watcher在組件初始化的時候是不會運行的,所以如果在watch中監(jiān)聽的數(shù)據(jù)默認(rèn)是不會進(jìn)行初始化的。類似于這樣:

watch: {
  title: (newTitle, oldTitle) => {
    // 組件初始化時不會打印
    console.log("Title changed from " + oldTitle + " to " + newTitle);
  };
}

但是,如果我們期望在初始化的時候運行watch,則可以通過添加immediate屬性。

watch: {
    title: {
        immediate: true,
        handler(newTitle, oldTitle) {
            // 組件初始化時會被打印
            console.log("Title changed from " + oldTitle + " to " + newTitle)
        }
    }
}

5. 自定義驗證Props

我們都知道在子組件接收props時可以對傳入的屬性進(jìn)行校驗,可以校驗為字符串、數(shù)字、數(shù)組、對象、函數(shù)。但我們也可以進(jìn)行自定義校驗。
示例:驗證傳入的字符串狀態(tài)必須為successerror。

props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'success',
        'error',
      ].indexOf(value) !== -1
    }
  }
}

6. 動態(tài)指令參數(shù)

Vue在綁定事件的時候支持將指令參數(shù)動態(tài)傳遞給組件,假設(shè)有一個按鈕組件,并且在某些情況下想監(jiān)聽單擊事件,而在其他情況下想監(jiān)聽雙擊事件。此時就可以使用動態(tài)指令參數(shù)。

<template>
  ...
  <aButton @[someEvent]="handleSomeEvent()" />
  ...
</template>

<script>
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? "click" : "dblclick"
    }
  },

  methods:{
    handleSomeEvent(){
      // handle some event
    }
  }
  ...
</script>

7. 組件路由復(fù)用

在開發(fā)當(dāng)中,有時候我們不同的路由復(fù)用同一個組件,默認(rèn)情況下,我們切換組件,Vue出于性能考慮可能不會重復(fù)渲染。

但是我們可以通過給router-view綁定一個key屬性來進(jìn)行切換的時候路由重復(fù)渲染。

<template>
  <router-view :key="$route.fullPath"></router-view>
</template>

8. 批量屬性繼承——使用$props將父組件的所有的props傳遞到子組件

在開發(fā)中當(dāng)前組件從父組件接收傳遞下來的數(shù)據(jù)使用props接收,如果再將這些props數(shù)據(jù)傳遞到子組件,通常情況下,我們同樣是使用屬性綁定的方式一個一個的屬性去綁定。但是如果props的數(shù)據(jù)很多,那么一個個的綁定方式就很不優(yōu)雅。

此時我們可以使用$props來傳遞。

  • Bad
<template>
    <!-- 將從父組件接收到的props數(shù)據(jù)傳遞到子組件 -->
    <childComponent
        :value1='value1'
        :value2='value2'
        :value3='value3'
        :value4='value4'
        :value5='value5'
    />
</template>
<script>
export default {
    // 從父組件接收到的props數(shù)據(jù)
    props: ['value1','value2','value3','value4','value5'],
    data() {
        return {....}
    .....
    }
}
</scrript>

// childComponent.vue
<script>
export default {
    props: ['value1','value2','value3','value4','value5'],
    data() {
        return {....}
    .....
    },
    mounted() {
        // 子組件可以接收到數(shù)據(jù)
        console.log(this.value1)
        console.log(this.value2)
        console.log(this.value3)
        console.log(this.value4)
        console.log(this.value5)
    }
}
</scrript>

  • Good
<template>
    <!-- 將從父組件接收到的props數(shù)據(jù)傳遞到子組件
        使用v-bind="$props" 批量傳遞
    -->
    <childComponent
        v-bind="$props"
    />
</template>
<script>
export default {
    // 從父組件接收到的props數(shù)據(jù)
    props: ['value1','value2','value3','value4','value5'],
    data() {
        return {....}
    .....
    }
}

// childComponent.vue
<script>
export default {
    props: ['value1','value2','value3','value4','value5'],
    data() {
        return {....}
    .....
    },
    mounted() {
        // 子組件可以接收到數(shù)據(jù)
        console.log(this.value1)
        console.log(this.value2)
        console.log(this.value3)
        console.log(this.value4)
        console.log(this.value5)
    }
}
</scrript>

屬性繼承在開發(fā)表單組件時,是不得不解決的問題,使用$props就可以很好的解決批量屬性傳遞問題。

下面以開發(fā)一個XInput為例:

<template>
  <label>姓名</label>
  <!-- 使用XInput組件 -->
  <XInput
    :value="value"
    :placeholder="placeholder"
    :maxlength="maxlength"
    :minlength="minlength"
    :name="name"
    :form="form"
    :value="value"
    :disabled="disabled"
    :readonly="readonly"
    :autofocus="autofocus"
    @input="handleInputChange"
  />
</template>
  • Bad
// XInput.vue
<template>
  <div>
    <input
      @input="$emit('input', $event.target.value)"
      :value="value"
      :placeholder="placeholder"
      :maxlength="maxlength"
      :minlength="minlength"
      :name="name"
      :form="form"
      :value="value"
      :disabled="disabled"
      :readonly="readonly"
      :autofocus="autofocus"
    />
  </div>
</template>

<script>
  export default {
    props: [
      "label",
      "placeholder",
      "maxlength",
      "minlength",
      "name",
      "form",
      "value",
      "disabled",
      "readonly",
      "autofocus",
    ],
  };
</script>
  • Good
<template>
  <div>
    <input v-bind="$props" />
  </div>
</template>
<script>
  export default {
    props: [
      "label",
      "placeholder",
      "maxlength",
      "minlength",
      "name",
      "form",
      "value",
      "disabled",
      "readonly",
      "autofocus",
    ],
  };
</script>

9. 把所有父級組件的事件監(jiān)聽傳遞到子組件 - $listeners

如果子組件不在父組件的根目錄下,則可以將所有事件偵聽器從父組件傳遞到子組件。即在子組件可以獲取到所有子組件的事件。

// Parnet.vue
<template>
  <div>父組件</div>
  <!-- 組件 -->
  <Child @on-test1="handleTest" 1 />
</template>
// Child.vue
<template>
  <div>子組件</div>
  <!-- 組件 -->
  <!-- 使用v-on='$listeners'將所有父組件非原生事件傳遞到子組件 -->
  <sub-child
    @on-test2="handleTest2"
    @on-test3.native="handleTest3"
    v-on="$listeners"
  />
</template>
// SubChild.vue
<template>
  <div>孫子組件</div>
</template>
<script>
  export default {
      ...
      mounted() {
          console.log(this.$listeners)
          /*
              {
                  on-test1: ? invoker()
                  on-test2: ? invoker()
              }
          */
          // 調(diào)要祖父組件的事件
          this.$listeners.on-test1()
          // 調(diào)要父組件的事件
          this.$listeners.on-test2()
      }
  }
</script>

注意:如果使用native修改的事件則獲取不到。即無法獲取到原生事件。

10. 基礎(chǔ)組件自動注冊

在項目開發(fā)中我們通常對于通用組件都是用到的地方挨個import引入,這種方式雖然沒有問題,但是作為一個有追求的程序狗怎么能做這種重復(fù)性的勞動呢。

你可以嘗試下面這種基礎(chǔ)組件自動全局注冊的方式,通用組件只需要定義在components/base/文件夾下,就可以實現(xiàn)自動全局注冊。需要使用的地方可以直接使用,無需單獨引入。

// utils/globals.js
/*
    這個方法負(fù)責(zé)基礎(chǔ)組件的全局注冊;
    這些組件可以在項目的任何地方使用而無需引入;
    所有的通用組件文件要定義在/components/base/文件夾下;
    組件命名采用:Base<componentName>.vue 的方式
*/
export const registerBaseComponents = vm => {
    // 引入通用組件
    const requireComponent = require.context(
        // 讀取文件的路徑
        './components/base',
        // 是否遍歷文件的子目錄
        false,
        // 匹配文件的正則
        /Base[\w-]+\.vue$\
    )
    requireComponent.keys().forEach(fileName => {
        // 獲取每個組件文件配置
        const componentConfig = requireComponent(fileName)
        // 轉(zhuǎn)換組件命名為駝峰命名
        const componentName = upperFirst(
            camelCase(fileName.replace(/^\.\//,'').replace(/\.\w+$/,''))
        )
        // 全局注冊組件
        vm.component(componentName,componentConfig.default || componentConfig)
    })
}

然后,在入口文件main.js中引入并初始化。

import Vue from 'vue'
import { registerBaseComponents } from '@/utils/globals'
registerBaseComponents(Vue)
.....

11、自定義 v-model

Vue 的特性之一是單向數(shù)據(jù)流,父組件傳遞給子組件的數(shù)據(jù),無法做到完全同步,子組件如果想更新父組件的數(shù)據(jù)需要派發(fā)事件給父組件。但vue也提供了一種方式給我們,自定義v-model,讓我們可以實現(xiàn)雙向數(shù)據(jù)綁定的效果。

  • 方式一:
// Parent.vue
<Button @click="handleChange"></Button>
.....
<Child v-model="visible" />
...
data() {
    return {
        visible: false
    }
},
methods: {
    handleChange() {
        this.visible = true
    }
}

// Child.vue
<template>
    <Drawer
        v-model="isVisible">
    ......
    </Drawer>
</template>
<script>
    export default {
        model: {
            prop: 'value',
            event: 'change'
        },
        props: {
            // value為接收到父組件的數(shù)據(jù)
            value: Boolean
        },
        computed: {
            isVisible: {
                get() {
                    return this.value
                },
                set(val) {
                    // 更新父組件數(shù)據(jù)
                    this.$emit('change', val)
                }
            }
        },
    }
</script>
  • 方式二:
// Parent.vue
<Button @click="handleChange"></Button>
.....
<Child v-model="visible" />
...
data() {
    return {
        visible: false
    }
},
methods: {
    handleChange() {
        this.visible = true
    }
}

// Child.vue
<template>
    <Drawer
        v-model="isVisible">
    ......
    </Drawer>
</template>
<script>
    export default {
        props: {
            // value為接收到父組件的數(shù)據(jù)
            value: Boolean
        },
        computed: {
            isVisible: {
                get() {
                    return this.value
                },
                set(val) {
                    // 默認(rèn)派發(fā)input事件
                    this.$emit('input', val)
                }
            }
        },
    }
</script>

至此,我們的 11 個小技巧就分享完了,如果你覺得有用,請你動動小手點個贊讓我知道[筆芯]

參考文獻(xiàn) 1

參考文獻(xiàn) 2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容