10個你可能不知道的Vue開發技巧

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

1. .sync修飾符實現props雙向數據綁定

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

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

  • 普通實現方式:
// 父組件
<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`修飾符實現方式
// 父組件
<template>
  <div>
    <button @click="handleClick">click me</button>
    // 添加sync修飾符,相當于<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. 監聽生命周期Hook

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

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

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

2.2. 監聽組件內部的生命周期函數

在組件內部,如果想要監聽組件的生命周期鉤子,可以使用$on,$once

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

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

3. 深度作用選擇器

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

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

這樣轉換后的結果:

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

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

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

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

編譯后:

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

4. 組件初始化時觸發Watcher

默認情況下,Watcher在組件初始化的時候是不會運行的,所以如果在watch中監聽的數據默認是不會進行初始化的。類似于這樣:

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時可以對傳入的屬性進行校驗,可以校驗為字符串、數字、數組、對象、函數。但我們也可以進行自定義校驗。
示例:驗證傳入的字符串狀態必須為successerror

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

6. 動態指令參數

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

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

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

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

7. 組件路由復用

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

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

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

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

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

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

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

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

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

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

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

下面以開發一個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. 把所有父級組件的事件監聽傳遞到子組件 - $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()
              }
          */
          // 調要祖父組件的事件
          this.$listeners.on-test1()
          // 調要父組件的事件
          this.$listeners.on-test2()
      }
  }
</script>

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

10. 基礎組件自動注冊

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

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

// utils/globals.js
/*
    這個方法負責基礎組件的全局注冊;
    這些組件可以在項目的任何地方使用而無需引入;
    所有的通用組件文件要定義在/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)
        // 轉換組件命名為駝峰命名
        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 的特性之一是單向數據流,父組件傳遞給子組件的數據,無法做到完全同步,子組件如果想更新父組件的數據需要派發事件給父組件。但vue也提供了一種方式給我們,自定義v-model,讓我們可以實現雙向數據綁定的效果。

  • 方式一:
// 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為接收到父組件的數據
            value: Boolean
        },
        computed: {
            isVisible: {
                get() {
                    return this.value
                },
                set(val) {
                    // 更新父組件數據
                    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為接收到父組件的數據
            value: Boolean
        },
        computed: {
            isVisible: {
                get() {
                    return this.value
                },
                set(val) {
                    // 默認派發input事件
                    this.$emit('input', val)
                }
            }
        },
    }
</script>

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

參考文獻 1

參考文獻 2

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。