相信你們在寫業務的時候vue
有追求的程序員都會把一個頁面分爲好幾個組件程序員
這樣就有解耦性、組件化vuex
雖然這樣作當然好,但有時候很是麻煩,api
一個頁面有很是多組件,通訊就很是繁瑣緩存
好比如下情景,markdown
咱們就能夠用provide/inject
+ mixin
ide
但看到這,熟悉vue的小夥伴確定脫口而出這樣也能夠用vuex啊函數
確實,這也官方推薦的組件化
但!我的以爲vuex在這種狀況下仍是比較「重」的,佈局
要寫的業務代碼也比較多
那不如往下看看provide/inject
+ mixin
的妙用?
字面意思,provide是提供的意思、inject是注入的意思,
事實上也是這樣,provide 是在父組件使用、inject是在後代組件使用
這裏的後代組件的意思其實就是父組件包含的子、子孫組件,無論你層級多深都是後代組件
例如
在這裏,咱們能夠把整個頁面容器做爲父組件,提供數據使用provide
在父組件中所包含的子組件、子孫組件可使用inject
獲取父組件提供的數據
咱們在這選取圖中上半部分做爲示例代碼,讓小夥伴們看得更清晰
項目文件所的層級結構:
頁面佈局結構:
代碼:
父組件(頁面容器)
<template>
<div>
父組件輸入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '內容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this
}
}
}
</script>
複製代碼
子組件A
<template>
<div>
子組件A輸入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
子孫組件B
<template>
<div>
子孫組件B輸入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
來看看效果:
從效果圖能夠看出,
不論修改哪一個組件數據,其餘組件數據也會跟着改變,
不知道小夥伴有沒有發現,這其實有點相似vuex?
vuex將狀態放在state裏,使用mutation對state修改狀態
一樣,咱們將頁面數據text放在最頂層的父容器組件中,
使用provide暴露改變text的changeText方法
但重點是,咱們注入了頁面容器父組件整個this
// 父組件部分代碼
....
data () {
return {
text: '內容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this
}
}
複製代碼
與此同時,在子組件使用computed作一個監聽緩存
因此,不論修改哪一個組件數據,其餘組件數據也會跟着改變
但,咱們稍微把例子改一下就會發生有趣的事情
咱們在父容器組件增長提供pageText
和pageChangeText
方法
A組件保持不變,修改B組件
代碼:
父組件(頁面容器組件)
<template>
<div>
頁面父組件輸入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '內容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this,
pageText: this.text,
pageChangeText: this.changeText
}
}
}
</script>
複製代碼
子組件A(代碼不改動)
<template>
<div>
子組件A輸入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
子孫組件B
<template>
<div>
子孫組件B輸入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis', 'pageText', 'pageChangeText'], // 注入 pageText 和 pageChangeText
computed: {
text () {
return this.pageText
}
},
methods: {
change () {
this.pageChangeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
效果: 是否是有趣的事發生了?
咱們依舊在孫子B組件上添加computed緩存,但是在改變其餘數據的時候,B卻不變,
而在改變B的時候,其餘數據也會跟着改變
緣由就是 inject 傳入的不是響應式數據
有心的小夥伴就會發現,我特別打開了vue devtool
或許你能夠再回去看看動圖
你就會特別明顯發現,inject裏的數據一直是不變的!
因此B組件的緩存依賴就不會發生改變,
而相對傳入父組件的this(this.$data)是發生改變,其實,他就是響應式數據
固然,我相信,你看到這兒的話就明白了官方文檔這句話是什麼意思
讓咱們再看看源碼
// 在vue中的 src/core/instance/inject.js
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
複製代碼
不少人看到defineReactive
就認爲他就是響應式的!
其實,不對!
小夥伴應該有看到
toggleObserving(false)
// ...
toggleObserving(true)
複製代碼
這個方法設置是否爲響應式數據的方法
/**
* In some cases we may want to disable observation inside a component's
* update computation.
*/
export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {
shouldObserve = value
}
複製代碼
因此說,初始化inject,只是在 vm 下掛載 key 對應普通的值
而,其實想要響應式其實有不少方法
好比用類Java思想,設置一個get/set
仍是以前的例子,略微修改下
去除pageText
,增長pageGetText
方法
代碼:
父組件(頁面容器組件)
<template>
<div>
頁面父組件輸入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '內容'
}
},
methods: {
changeText (value) {
this.text = value
},
getText () {
return this.text
}
},
provide () {
return {
pageThis: this,
pageGetText: this.getText,
pageChangeText: this.changeText
}
}
}
</script>
複製代碼
子組件A(代碼不改動)
<template>
<div>
子組件A輸入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
子孫組件B
<template>
<div>
子孫組件B輸入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis', 'pageGetText', 'pageChangeText'],
computed: {
text () {
return this.pageGetText()
}
},
methods: {
change () {
this.pageChangeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
效果圖:
爲何能夠這樣用?
來來來,上源碼
// 在vue中的 src/core/instance/inject.js
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
複製代碼
能夠發現,原來!若是是 function
就 provide.call(vm)
若是是函數類型,那就把this指向當前實例!
有了以上兩種解決方法,其實對有小夥伴們,解決這個問題不是很難
可是本着幫助,給小夥伴們「脫坑」的思想
仍是但願小夥伴們注意下 代碼:
父組件(容器組件)
<template>
<div>
頁面父組件輸入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: ''
}
},
created () {
this.text = '內容...'
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this,
pageText: this.text,
pageChangeText: this.changeText
}
}
}
</script>
複製代碼
子組件A (不改動代碼)
<template>
<div>
子組件A輸入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
子孫組件B
<template>
<div>
子孫組件B輸入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis', 'pageText', 'pageChangeText'], // 注入 pageText 和 pageChangeText
computed: {
text () {
return this.pageText
}
},
methods: {
change () {
this.pageChangeText(event.currentTarget.value)
}
}
}
</script>
複製代碼
咱們在父容器組件中, 在生命週期函數created
賦值
然而,咱們發現,怎麼樣,B怎麼都是初始值,而不是咱們所賦值
// 父容器組件部分代碼
// ...
data () {
return {
text: ''
}
},
created () {
this.text = '內容...'
}
複製代碼
緣由是:initInjections
和initProvide
在生命週期函數created
初始化以前
// 在vue中的 src/core/instance/init.js
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
複製代碼
那咱們只要用 ① 和 ② 所說的就能夠完美搞定(傳入響應式)
而,有小夥伴可能以爲是否是還有第三種方法,
你這個不是v-model的底層寫法嗎?
不是能夠用v-model嗎?
確實能夠! 代碼: 父組件(容器組件)
<template>
<div>
頁面父組件輸入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '內容'
}
},
provide () {
return {
pageThis: this
}
}
}
</script>
複製代碼
子組件A
<template>
<div>
子組件A輸入框:
<input type="text" v-model="pageThis.text">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis']
}
</script>
複製代碼
子組件B
<template>
<div>
子孫組件B輸入框:
<input type="text" v-model="pageThis.text">
</div>
</template>
<script>
export default {
inject: ['pageThis']
}
</script>
複製代碼
不過在業務多數狀況下是不能使用v-model,
咱們這個例子中是在input標籤
才能使用v-model
那就讓我來介紹一個神器mixin
mixin就是混合機制,當組件和混入對象含有同名選項時,這些選項將以恰當的方式進行「合併」
其實徹底能夠理解爲,原來組件混入了你想使用的方法,成爲了一個新組件
看看源碼?
// 在vue中 src/core/global-api/mixin.js
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
複製代碼
// 在vue中 src/core/util/options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
複製代碼
工程目錄:
代碼:
父組件(頁面容器組件)
<template>
<div>
頁面父組件輸入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '內容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this
}
}
}
</script>
複製代碼
mixin.js 提取組件A和B的公共代碼
// mixin.js
export default {
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
複製代碼
子組件A
<template>
<div>
子組件A輸入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import dataMixin from '../mixin/dataMixin'
import BComp from './BComp'
export default {
components: {
BComp
},
mixins: [dataMixin]
}
</script>
複製代碼
子孫組件B
<template>
<div>
子孫組件B輸入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
import dataMixin from '../mixin/dataMixin'
export default {
mixins: [dataMixin]
}
</script>
複製代碼
這樣一使用就能夠減小了代碼量
固然,減小的同時可能會形成可讀性下降~
因此小夥伴能夠衡量一下,這也是不錯的辦法~
本文側重介紹了 provide/inject 用法,還有常常會入的「坑」
同時,也扯了下 mixin,但其實仍是特別不錯的特性,
看完之後,您也能夠試試用vuex + mixin
能夠說是「數據」與視圖解耦!
感謝閱讀