首頁>技術>

優雅更新props

更新 prop 在業務中是很常見的需求,但在子元件中不允許直接修改 prop,因為這種做法不符合單向資料流的原則,在開發模式下還會報出警告。因此大多數人會通過 $emit 觸發自定義事件,在父元件中接收該事件的傳值來更新 prop。

child.vue:

export defalut {    props: {        title: String      },    methods: {        changeTitle(){            this.$emit('change-title', 'hello')        }    }}

parent.vue:

<child :title="title" @change-title="changeTitle"></child>
export default {    data(){        return {            title: 'title'        }      },    methods: {        changeTitle(title){            this.title = title        }    }}

這種做法沒有問題,我也常用這種手段來更新 prop。但如果你只是想單純的更新 prop,沒有其他的操作。那麼 sync 修飾符能夠讓這一切都變得特別簡單。

parent.vue:

<child :title.sync="title"></child>

child.vue:

export defalut {    props: {        title: String      },    methods: {        changeTitle(){            this.$emit('update:title', 'hello')        }    }}

只需要在繫結屬性上新增 .sync,在子元件內部就可以觸發 update:屬性名 來更新 prop。可以看到這種手段確實簡潔且優雅,這讓父元件的程式碼中減少一個“沒必要的函式”。

參考文件

provide/inject

這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在其上下游關係成立的時間裡始終生效。

簡單來說,一個元件將自己的屬性通過 provide 暴露出去,其下面的子孫元件 inject 即可接收到暴露的屬性。

App.vue:

export default {    provide() {        return {            app: this        }    } }

child.vue:

export default {    inject: ['app'],    created() {        console.log(this.app) // App.vue例項    }}

在 2.5.0+ 版本可以通過設定預設值使其變成可選項:

export default {    inject: {        app: {            default: () => ({})        }    },    created() {        console.log(this.app)     }}

如果你想為 inject 的屬性變更名稱,可以使用 from 來表示其來源:

export default {    inject: {        myApp: {            // from的值和provide的屬性名保持一致            from: 'app',            default: () => ({})        }    },    created() {        console.log(this.myApp)     }}

需要注意的是 provide 和 inject 主要在開發高階外掛/元件庫時使用。並不推薦用於普通應用程式程式碼中。但是某些時候,或許它能幫助到我們。

參考文件

小型狀態管理器

大型專案中的資料狀態會比較複雜,一般都會使用 vuex 來管理。但在一些小型專案或狀態簡單的專案中,為了管理幾個狀態而引入一個庫,顯得有些笨重。

在 2.6.0+ 版本中,新增的 Vue.observable 可以幫助我們解決這個尷尬的問題,它能讓一個物件變成響應式資料:

// store.jsimport Vue from 'vue'export const state = Vue.observable({   count: 0 })

使用:

<div @click="setCount">{{ count }}</div>
import {state} from '../store.js'export default {    computed: {        count() {            return state.count        }    },    methods: {        setCount() {            state.count++        }    }}

當然你也可以自定義 mutation 來複用更改狀態的方法:

import Vue from 'vue'export const state = Vue.observable({   count: 0 })export const mutations = {  SET_COUNT(payload) {    if (payload > 0) {        state.count = payload    }   }}

使用:

import {state, mutations} from '../store.js'export default {    computed: {        count() {            return state.count        }    },    methods: {        setCount() {            mutations.SET_COUNT(100)        }    }}

參考文件

解除安裝watch觀察

通常定義資料觀察,會使用選項的方式在 watch 中配置:

export default {    data() {        return {            count: 1              }    },    watch: {        count(newVal) {            console.log('count 新值:'+newVal)        }    }}

除此之外,資料觀察還有另一種函式式定義的方式:

export default {    data() {        return {            count: 1              }    },    created() {        this.$watch('count', function(){            console.log('count 新值:'+newVal)        })    }}

它和前者的作用一樣,但這種方式使定義資料觀察更靈活,而且 $watch 會返回一個取消觀察函式,用來停止觸發回撥:

let unwatchFn = this.$watch('count', function(){    console.log('count 新值:'+newVal)})this.count = 2 // log: count 新值:2unwatchFn()this.count = 3 // 什麼都沒有發生...

$watch 第三個引數接收一個配置選項:

this.$watch('count', function(){    console.log('count 新值:'+newVal)}, {    immediate: true // 立即執行watch})

參考文件

巧用template

相信 v-if 在開發中使用得最多的指令,那麼你一定遇到過這樣的場景,多個元素需要切換,而且切換條件都一樣,一般都會使用一個元素包裹起來,在這個元素上做切換。

<div v-if="status==='ok'">    <h1>Title</h1>    <p>Paragraph 1</p>    <p>Paragraph 2</p></div>

如果像上面的 div 只是為了切換條件而存在,還導致元素層級巢狀多一層,那麼它沒有“存在的意義”。

我們都知道在宣告頁面模板時,所有元素需要放在 <template> 元素內。除此之外,它還能在模板內使用,<template> 元素作為不可見的包裹元素,只是在執行時做處理,最終的渲染結果並不包含它。

<template>    <div>        <template v-if="status==='ok'">          <h1>Title</h1>          <p>Paragraph 1</p>          <p>Paragraph 2</p>        </template>    </div></template>

同樣的,我們也可以在 <template> 上使用 v-for 指令,這種方式還能解決 v-for 和 v-if 同時使用報出的警告問題。

<template v-for="item in 10">    <div v-if="item % 2 == 0" :key="item">{{item}}</div></template>

template使用v-if,template使用v-for

過濾器複用

過濾器被用於一些常見的文字格式化,被新增在表示式的尾部,由“管道”符號指示。

<div>{{ text | capitalize }}</div>
export default {    data() {        return {            text: 'hello'        }      },    filters: {        capitalize: function (value) {            if (!value) return ''            value = value.toString()            return value.charAt(0).toUpperCase() + value.slice(1)         }    }}

試想一個場景,不僅模板內用到這個函式,在 method 裡也需要同樣功能的函式。但過濾器無法通過 this 直接引用,難道要在 methods 再定義一個同樣的函式嗎?

要知道,選項配置都會被儲存在例項的 $options 中,所以只需要獲取 this.$options.filters 就可以拿到例項中的過濾器。

export default {    methods: {        getDetail() {            this.$api.getDetail({                id: this.id            }).then(res => {                let capitalize = this.$options.filters.capitalize                this.title = capitalize(res.data.title)            })        }    }}

除了能獲取到例項的過濾器外,還能獲取到全域性的過濾器,因為 this.$options.filters 會順著 __proto__ 向上查詢,全域性過濾器就存在原型中。

自定義指令獲取例項

有的情況下,當需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。像是專案中常用的許可權指令,它能精確到某個模組節點。大概思路為獲取許可權列表,如果當前繫結許可權不在列表中,則刪除該節點元素。

Vue.directive('role', {    inserted: function (el, binding, vnode) {      let role = binding.value      if(role){        const applist = sessionStorage.getItem("applist")        const hasPermission = role.some(item => applist.includes(item))         // 是否擁有許可權        if(!hasPermission){          el.remove() //沒有許可權則刪除模組節點        }      }    }})

自定義指令鉤子函式共接收3個引數,包括 el (繫結指令的真實dom)、binding(指令相關資訊)、vnode (節點的虛擬dom)。

假設現在業務發生變化,applist 儲存在 vuex 裡, 但指令內想要使用例項上的屬性,或者是原型上的 $store。我們是沒有辦法獲取到的,因為鉤子函式內並沒有直接提供例項訪問。vnode 作為當前的虛擬dom,它裡面可是繫結到例項上下文的,這時候訪問 vnode.context 就可以輕鬆解決問題。

Vue.directive('role', {    inserted: function (el, binding, vnode) {      let role = binding.value      if(role){        // vnode.context 為當前例項        const applist = vnode.context.$store.state.applist        const hasPermission = role.some(item => applist.includes(item))         if(!hasPermission){          el.remove()        }      }    }})
優雅註冊外掛

外掛通常用來為 Vue 新增全域性功能。像常用的 vue-router、vuex 在使用時都是通過 Vue.use 來註冊的。Vue.use 內部會自動尋找 install 方法進行呼叫,接受的第一個引數是 Vue 建構函式。

一般在使用元件庫時,為了減小包體積,都是採用按需載入的方式。如果在入口檔案內逐個引入元件會讓 main.js 越來越龐大,基於模組化開發的思想,最好是單獨封裝到一個配置檔案中。配合上 Vue.use,在入口檔案使用能讓人一目了然。

vant.config.js:

import {  Toast,  Button} from 'vant'const components = {  Toast,  Button}const componentsHandler = {  install(Vue){    Object.keys(components).forEach(key => Vue.use(components[key]))  }}export default componentsHandler

main.js:

import Vue from 'vue'import vantCompoents from '@/config/vant.config'Vue.config.productionTip = falseVue.use(vantCompoents)new Vue({  render: h => h(App)}).$mount('#app')

參考文件

自動化引入模組

在開發中大型專案時,會將一個大功能拆分成一個個小功能,除了能便於模組的複用,也讓模組條理清晰,後期專案更好維護。

像 api 檔案一般按功能劃分模組,在組合時可以使用 require.context 一次引入資料夾所有的模組檔案,而不需要逐個模組檔案去引入。每當新增模組檔案時,就只需要關注邏輯的編寫和模組暴露,require.context 會幫助我們自動引入。

需要注意 require.context 並不是天生的,而是由 webpack 提供。在構建時,webpack 在程式碼中解析它。

let importAll = require.context('./modules', false, /\\.js$/)class Api extends Request{    constructor(){        super()        //importAll.keys()為模組路徑陣列        importAll.keys().map(path =>{            //相容處理:.default獲取ES6規範暴露的內容; 後者獲取commonJS規範暴露的內容            let api = importAll(path).default || importAll(path)            Object.keys(api).forEach(key => this[key] = api[key])        })    }}export default new Api()

require.context 引數:

資料夾路徑是否遞迴查詢子資料夾下的模組模組匹配規則,一般匹配檔案字尾名

只要是需要批量引入的場景,都可以使用這種方法。包括一些公用的全域性元件,只需往資料夾內新增元件即可使用,不需要再去註冊。如果還沒用上的小夥伴,一定要了解下,簡單實用又能提高效率。

參考文件

路由懶載入(動態chunkName)

路由懶載入作為效能優化的一種手段,它能讓路由元件延遲載入。通常我們還會為延遲載入的路由新增“魔法註釋”(webpackChunkName)來自定義包名,在打包時,該路由元件會被單獨打包出來。

let router = new Router({  routes: [    {      path:'/login',      name:'login',      component: import(/* webpackChunkName: "login" */ `@/views/login.vue`)    },    {      path:'/index',      name:'index',      component: import(/* webpackChunkName: "index" */ `@/views/index.vue`)    },    {      path:'/detail',      name:'detail',      component: import(/* webpackChunkName: "detail" */ `@/views/detail.vue`)    }  ]})

上面這種寫法沒問題,但仔細一看它們結構都是相似的,作為一名出色的開發者,我們可以使用 map 迴圈來解決這種重複性的工作。

const routeOptions = [  {    path:'/login',    name:'login',  },  {    path:'/index',    name:'index',  },  {    path:'/detail',    name:'detail',  },]const routes = routeOptions.map(route => {  if (!route.component) {    route = {      ...route,      component: () => import(`@/views/${route.name}.vue`)    }  }  return route})let router = new Router({  routes})

在書寫更少程式碼的同時,我們也把“魔法註釋”給犧牲掉了。眾所周知,程式碼中沒辦法編寫動態註釋。這個問題很尷尬,難道就沒有兩全其美的辦法了嗎?

強大的 webpack 來救場了,從 webpack 2.6.0 開始,佔位符 [index] 和 [request] 被支援為遞增的數字或實際解析的檔名。我們可以這樣使用“魔法註釋”:

const routes = routeOptions.map(route => {  if (!route.component) {    route = {      ...route,      component: () => import(/* webpackChunkName: "[request]" */ `@/views/${route.name}.vue`)    }  }  return route})

- END -

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Nuxtjs網站搭建步驟及遇到的問題