依赖注入
当深层嵌套的子孙组件想要拿到父组件的数据时,我们可以使用provide和inject。官网原理图:
使用案例: 我们假设组件嵌套结构如下:
Root
└─ TodoList
├─ TodoItem
└─ TodoListFooter
├─ ClearTodosButton
└─ TodoListStatistics
我们通过provide和inject将组件TodoList的属性直接传给组件TodoListStatistics:
const app = Vue.createApp({})
app.component('todo-list', {
data() {
return {
todos: ['张三', '李四']
}
},
provide: { // provide是一个对象
name: '王五'
},
template: `
<div>
{{ todos.length }}
</div>
`
})
app.component('todo-list-statistics', {
inject: ['name'],
created() {
console.log(`Injected 属性: ${this.name}`) // > Injected 属性: 王五
}
})
上例中的provide定义为一个对象。如果需要在provide里使用data中的属性,需要把provide定义成一个方法,否则会报错。
app.component('todo-list', {
data() {
return {
todos: ['张三', '李四']
}
},
provide() { // provide是一个function
return {
todoLength: this.todos.length
}
},
template: `
...
`
})
依赖注入的优缺点如下: 优点: * 祖先组件不需要知道哪些后代组件使用它提供的数据; * 后代组件不需要知道被注入的数据来自哪里; 缺点: * 组件间的耦合较为紧密,不易重构; * 提供的属性是非响应式的;解决方案见官方文档
源码解读
组件实例初始化的时候会调用Vue.prototype._init,通过下面源码,我们可以知道: inject、provide的初始化时间在生命周期钩子函数beforeCreate之后,created之前。 **initInjections(vm)**解析inject是在初始化data/props之前, **initProvide(vm)**解析provide是在初始化data/props之后。 这也符合数据初始化的一个处理逻辑。
Vue.prototype._init源码:
//初始化inject和provide
// 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')
initInjections函数,功能是**获取组件注册的inject属性合集,然后遍历合集进行响应式监听。**源码如下:
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm) // 1、根据注册的inject,通过$parent向上查找对应的provide
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]) // 2、进行响应式监听
}
})
toggleObserving(true)
}
}
resolveInject函数,功能是通过$parent一层层向上查找祖先节点的数据,直到找到对应于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)
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) { // 遍历所有inject为其赋值
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) { // 核心原理:通过$parent一层一层向上查找祖先节点的provide,找到则对inject进行赋值
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
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
}
}
initProvide函数,该方法单纯把组件注册的provide值,赋值给vm._provided,resolveInject中有使用到。
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
总结
依赖注入,其核心原理就是通过$parent向上查找祖先组件中的provide,找到则赋值给对应的inject即可。 仔细一思量,老铁们会发想,依赖注入原理和JavaScript中的instanceof操作符原理有异曲同工之处。在instanceof中,通过__proto__向原型链中查找,如果__proto__与构造函数的prototype相等则返回true。哈哈哈哈哈,这就是研究原理的有趣之处。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!