provide/inject 官方文檔說明
官網(wǎng)總結(jié):
- 配對使用:provide 和 inject 需要成對使用
- 作用:祖先組件可向其所有子孫后代傳遞數(shù)據(jù)
- 優(yōu)點:當(dāng)孫組件想要訪問祖先組件的 property 時,通過 provide/inject 可以輕松實現(xiàn)跨級訪問數(shù)據(jù)
- 場景:provide/inject 主要在開發(fā)高階插件/組件庫時使用铅搓,并不推薦用于普通應(yīng)用程序代碼中
??從使用 provide/inject 優(yōu)點來看筐带,當(dāng)我們的組件嵌套了多層,孫組件可輕松訪問祖先組件的數(shù)據(jù)茂装,可能大家會和我有同樣的疑惑:為什么開發(fā)中卻很少用到?
使用場景分析
為什么會不推薦用于普通應(yīng)用程序代碼呢善延?
??因為數(shù)據(jù)追蹤比較困難少态,不知道是哪一個層級聲明了這個或者不知道哪一層級或若干個層級使用了。如果數(shù)據(jù)在多個組件都需要使用到易遣,可以使用 vueX 來進(jìn)行狀態(tài)管理彼妻。如果只是子組件想要使用父組件上的數(shù)據(jù),可直接通過 props 來讓父組件給子組件傳值豆茫。
為什么推薦使用在高階插件/組件庫侨歉?
??因為在 vue 中父子組件可以用 props 傳值,子組件也可以通過 $parent 訪問父組件的 property(不推薦)揩魂。但父子組件 props 傳值需要需要知道往哪一個子組件傳值幽邓,而在組件庫中的組件中引入的子組件是不確定的。而 provide 只需要將傳遞的值注入 火脉,不需要知道使用哪一個子組件牵舵,子組件通過 inject 獲取注入的數(shù)據(jù)柒啤,也不需要知道父組件是誰,因此再進(jìn)行封裝組件庫的時候很方便棋枕。
借用網(wǎng)上很火的 element-ui 組件庫的案例來說明一下白修。
??在 element-ui 中,我們經(jīng)常使用里面的表單重斑,看一段熟悉的代碼兵睛,DOM 結(jié)構(gòu): el-form > el-form-item > el-input:
打開 el-form 這個組件,發(fā)現(xiàn)使用到了 provide:
??這里的 el-form組件窥浪,提供給子孫組件的數(shù)據(jù)是當(dāng)前的 vue 實例祖很,這樣在子孫組件中,都可以使用 inject 來接收 el-form 組件提供的 vue 實例漾脂。
??再打開 el-form 組件的子組件 el-form-item假颇,果不其然,發(fā)現(xiàn)用到了 inject 注入數(shù)據(jù):
provide/inject 語法
??provide:是一個對象或返回一個對象的函數(shù)骨稿。該對象包含可注入其子孫組件的數(shù)據(jù)
??inject:一個字符串?dāng)?shù)組笨鸡,或一個對象。如果是對象坦冠,對象的 key 是本地的綁定名形耗,value 是,包含from和default默認(rèn)值辙浑。
自制小案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<script src="../../dist/vue.js"></script>
<script>
Vue.component('parent', {
template: `
<div>
<child></child>
</div>
`,
provide: {
A: 'ParentA'
}
})
Vue.component('child', {
template: `
<div>
<h2>child 組件中獲取tA---{{ A }}</h2>
<sunzi></sunzi>
</div>
`,
provide: {
A: 'ChildA',
B: 'ChildB'
},
inject: ['A']
})
Vue.component('sunzi', {
template: `
<div>
<h2>sunzi 組件中獲取A ---{{ injectA }}</h2>
<h2>sunzi 組件中獲取B ---{{ injectB }}</h2>
</div>
`,
inject: {
injectA: {
from: 'A',
default: '默認(rèn)信息1'
},
injectB: {
from: 'B',
default: '默認(rèn)信息2'
}
}
})
const app = new Vue({
el: '#app',
data: {}
})
</script>
</body>
</html>
運行結(jié)果如下:
從上結(jié)果可以看出激涤,組件在查找注入內(nèi)容時是一級一級往上查找的,就近原則判呕。
源碼解析
??首先我們知道在使用 vue 的時候倦踢,先通過CDN方式 引入vue 或者 npm 下載Vue包,再通過 new Vue(options)
即可侠草,其實在我們執(zhí)行 new Vue(options)
代碼的時候辱挥,vue內(nèi)部是做了很多初始化操作的,比如現(xiàn)在要說的 initInjections(vm)
初始化組件的 inject 注入內(nèi)容梦抢。直接上源碼
initInjections 方法
文件路徑:src/core/instance/inject.js
/**
* 初始化 inject 選項
* 獲取 inject 里的所有 key 的值般贼,得到 result[key] = val 對象,
* 并做響應(yīng)式處理:把 key 代理到當(dāng)前 vue 實例上奥吩,方便通過 this.key 訪問
* @param {*} vm
*/
export function initInjections (vm: Component) {
// 從配置項上解析 inject 選項,最后得到 result[key] = val
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 關(guān)閉觀察者 observe 模式
toggleObserving(false)
// 將解析結(jié)果做響應(yīng)式處理蕊梧,將 key 代理到當(dāng)前 vue 實例上霞赫,在組件里可以通過 this.key 來進(jìn)行訪問,響應(yīng)式處理代碼 defineReactive 后面過幾天再更肥矢。
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])
}
})
// 開啟觀察者 observe 模式
toggleObserving(true)
}
}
resolveInject 方法
文件路徑:src/core/instance/inject.js
/**
*
* @param {*} inject = {
* key: {
* from: provideKey,
* default: xx
* }
* } 或 inject = [key..]
* @param {*} vm
* @returns { key: val }
*/
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
const result = Object.create(null)
// 創(chuàng)建一個數(shù)組 keys端衰,保存 inject 的所有 key
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
// 遍歷 keys 數(shù)組項
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key === '__ob__') continue
// 獲取 key 的 from 屬性叠洗,并賦值給 provideKey
const provideKey = inject[key].from
// 一層一層地向祖代組件的配置項查找 provide 選項,并查找對應(yīng)的 key 值
let source = vm
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
// 如果所有祖代組件都已查找完旅东,則先去 inject 獲取 key 時的設(shè)置的 deault 默認(rèn)值
// 如果沒有默認(rèn)值且不是生產(chǎn)環(huán)境灭抑,則直接警示 `Injection "${key}" not found`
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
toggleObserving 方法
文件路徑:src/core/observe/index.js
/**
* 控制是否使用觀察者 observe 模式
*/
export function toggleObserving (value: boolean) {
shouldObserve = value
}