
作者: chanwahfung
轉發連結:https://www.cnblogs.com/chanwahfung
優雅更新props
更新 prop 在業務中是很常見的需求,但在子元件中不允許直接修改 prop,因為這種做法不符合單向資料流的原則,在開發模式下還會報出警告。是以大多數人會通過 $emit 觸發自定義事件,在父元件中接收該事件的傳值來更新 prop。
child.vue:
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('change-title', 'hello') } }}
parent.vue:
export default { data(){ return { title: 'title' } }, methods: { changeTitle(title){ this.title = title } }}
這種做法沒有問題,我也常用這種手段來更新 prop。但如果你隻是想單純的更新 prop,沒有其他的操作。那麼 sync 修飾符能夠讓這一切都變得特别簡單。
parent.vue:
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 })
使用:
{{ count }}
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 在開發中使用得最多的指令,那麼你一定遇到過這樣的場景,多個元素需要切換,而且切換條件都一樣,一般都會使用一個元素包裹起來,在這個元素上做切換。
Title
Title
Paragraph 1
Paragraph 2
如果像上面的 div 隻是為了切換條件而存在,還導緻元素層級嵌套多一層,那麼它沒有“存在的意義”。
我們都知道在聲明頁面模闆時,所有元素需要放在 元素内。除此之外,它還能在模闆内使用, 元素作為不可見的包裹元素,隻是在運作時做處理,最終的渲染結果并不包含它。
Title
Title
Paragraph 1
Paragraph 2
同樣的,我們也可以在 上使用 v-for 指令,這種方式還能解決 v-for 和 v-if 同時使用報出的警告問題。
{{item}}
template使用v-if,template使用v-for
過濾器複用
過濾器被用于一些常見的文本格式化,被添加在表達式的尾部,由“管道”符号訓示。
{{ text | capitalize }}
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 -
作者: chanwahfung
轉發連結:https://www.cnblogs.com/chanwahfung