更新 prop
在業務中是很常見的需求,但在子組件中不容許直接修改 prop
,由於這種作法不符合單向數據流的原則,在開發模式下還會報出警告。所以大多數人會經過 $emit
觸發自定義事件,在父組件中接收該事件的傳值來更新 prop
。html
child.vue:vue
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('change-title', 'hello') } } }
parent.vue:node
<child :title="title" @change-title="changeTitle"></child>
export default { data(){ return { title: 'title' } }, methods: { changeTitle(title){ this.title = title } } }
這種作法沒有問題,我也經常使用這種手段來更新 prop
。但若是你只是想單純的更新 prop
,沒有其餘的操做。那麼 sync
修飾符可以讓這一切都變得特別簡單。webpack
parent.vue:web
<child :title.sync="title"></child>
child.vue:vue-router
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('update:title', 'hello') } } }
只須要在綁定屬性上添加 .sync
,在子組件內部就能夠觸發 update:屬性名
來更新 prop
。能夠看到這種手段確實簡潔且優雅,這讓父組件的代碼中減小一個「不必的函數」。vuex
參考文檔api
這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在其上下游關係成立的時間裏始終生效。數組
簡單來講,一個組件將本身的屬性經過 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.js import 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
中配置:
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 新值:2 unwatchFn() this.count = 3 // 什麼都沒有發生...
$watch
第三個參數接收一個配置選項:
this.$watch('count', function(){ console.log('count 新值:'+newVal) }, { immediate: true // 當即執行watch })
相信 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 = false Vue.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
參數:
只要是須要批量引入的場景,均可以使用這種方法。包括一些公用的全局組件,只需往文件夾內新增組件便可使用,不須要再去註冊。若是還沒用上的小夥伴,必定要了解下,簡單實用又能提升效率。
路由懶加載做爲性能優化的一種手段,它能讓路由組件延遲加載。一般咱們還會爲延遲加載的路由添加「魔法註釋」(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 })
往期相關文章: