provide
Used together with inject
the option requires, it allows the ancestor component to inject dependencies to all its descendants, and it will always take effect when its upstream and downstream relationships are established, no matter how deep the component hierarchy is.
1. Let's briefly review the use of provide/inject
as follows:
var Provider = {
provide: {
foo: "bar"
}
}
var Child = {
inject: ["foo"],
created() {
console.log(this.foo); // "bar"
}
}
If it is used ES5 Symbol
as a key, it is used as follows:
const s = Symbol();
var Provider = {
provide() {
return {
[s]: "bar"
}
}
}
var Child = {
inject: {
s },
created() {
console.log(this.foo); // "bar"
}
}
data/props
The injected value can be accessed in :
var Provider = {
provide: {
foo: "bar",
foo2: "bar2"
}
}
var Child = {
inject: ["foo"],
props: {
bar: {
default() {
return this.foo;
}
}
},
data() {
return {
bar2: this.foo2
}
}
}
inject
Default values that can be set :
var Provider = {
provide: {
foo: "bar"
}
}
var Child = {
inject: {
foo: {
default: "foo"}
}
}
If it needs to be injected from a property with a different name, use from
to indicate its source property:
var Provider = {
provide: {
foo: "bar"
}
}
var Child = {
inject: {
foo: {
from: "var",
default: "foo"
}
}
}
2. The internal principle of inject
inject
initialized before data/props
, and initialized provide
after data/props
. The purpose of this is to make the injected content available to users data/props
in . inject
That is to say, in order to data/props
depend inject
, the initialization needs to inject
be placed in front of the initialization data/props
.
First, we define the initialization inject
function:
export function initInjections (vm: Component) {
// 通过用户配置的inject,自底向上搜索可用的注入内容,并将搜索结果返回
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false) // 通知defineReactive不要将内容转换成响应式
Object.keys(result).forEach(key => {
defineReactive(vm, key, result[key])
})
toggleObserving(true)
}
}
resolveInject
The role of the function is configured by the user inject
, searches the available injection content from bottom to top, and returns the search results. And toggleObserving(false)
the role is to inform defineReactive not to convert the content into responsive.
So resolveInject
how? inject
The main idea of implementing this function is: read out the value set by the user in the current component key
, and then loop key
through each one key
from the current component to the parent component to find out whether there is a value, stop the loop if found, and finally convert all key
corresponding The value can be returned together.
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
const result = Object.create(null)
/**
* hasSymbol:是否支持Symbol
*/
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
return result
}
}
The first step is to define an object result
to store our results, and then inject
read key
them out. Symbol
Use read if supported , Reflect.ownKeys(inject)
read if not supported Object.keys(inject)
.
But we know inject
that the form of array is supported, if the form of array is used, such as:
var Provider = {
provide: {
foo: "bar"
}
}
var Child = {
inject: ["foo"],
created() {
console.log(this.foo); // "bar"
}
}
Is it not possible to read inject
everything in it correctly key
?
In fact, Vue.js
the first step in instantiation is to normalize the data passed in by the user. If inject
the passed content is an array, the data will be normalized into an object and stored in from
the property.
If the user is set inject
like this:
{
inject: ["foo"]
}
Then after normalization it will look like this:
{
inject: {
foo: {
from: "foo"
}
}
}
Next, you need to loop key
. key
Starting from the current component, you will continue to check whether there is a value from the parent component. If you find it, stop the loop, and finally return all the key
corresponding values together.
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
const result = Object.create(null)
/**
* hasSymbol:是否支持Symbol
*/
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
/*
* 通过from属性得到provide源属性
* 当Vue.js被实例化时,会在上下文(this)中添加$options属性,这会把inject中提供的数据规格化,包括inject
* 用户设置: { inject: [foo] }
* 规格化:{ inject: { foo: { from: "foo" }}}
*/
const provideKey = inject[key].from
let source = vm // 一开始为当前实例
// 自底向上寻找provide源属性
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent // 向上寻找
}
}
return result
}
}
In the above code, the outermost layer uses the for loop keys to get the keys in turn in the loop body, and then inject[key].from
get the provide source attribute provideKey
. Then use a while loop through the source property to search for content. At the beginning source
, it is the current component instance. If the corresponding value can be found in the original property, then set it to source
and use break to exit the loop. Otherwise, set the source to the parent component instance for the next cycle._provided
result
If provide
no injection is provided, the default configuration in inject will be used, the code is as follows:
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
const result = Object.create(null)
/**
* hasSymbol:是否支持Symbol
*/
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
/*
* 通过from属性得到provide源属性
* 当Vue.js被实例化时,会在上下文(this)中添加$options属性,这会把inject中提供的数据规格化,包括inject
* 用户设置: { inject: [foo] }
* 规格化:{ inject: { foo: { from: "foo" }}}
*/
const provideKey = inject[key].from
let source = vm // 一开始为当前实例
// 自底向上寻找provide源属性
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent // 向上寻找
}
// 没有source,设置默认值
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
}
}
source
If it is empty after the loop ends , it can be determined provide
that the corresponding value injection has not been provided. At this time, it is necessary to read inject
the default value configured in . If so 'default' in inject[key]
, it proves that the default is configured, and if not, a warning will be printed in production. It is inject[key].default
read provideDefault
, but the default value supports functions and ordinary strings. At this time, it is necessary to judge provideDefault
whether it is a function. If it is, execute it and store the result in result; if not, directly store it provideDefault
in result.
3. The complete code is as follows:
/**
* 通过用户配置的inject,自底向上搜索可用的注入内容,并将搜索结果返回
* 当使用provide注入内容时,其实是将内容注入到当前组件实例的_provide中,所以inject可以从父组件实例的_provide中获取注入的内容
*/
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
/**
* hasSymbol:是否支持Symbol
*/
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
/*
* 通过from属性得到provide源属性
* 当Vue.js被实例化时,会在上下文(this)中添加$options属性,这会把inject中提供的数据规格化,包括inject
* 用户设置: { inject: [foo] }
* 规格化:{ inject: { foo: { from: "foo" }}}
*/
const provideKey = inject[key].from
let source = vm // 一开始为当前实例
// 自底向上寻找provide源属性
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent // 向上寻找
}
// 没有source,设置默认值
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
}
}