前言
provide/inject在面试中,偶尔会问到,它通常在回答数据通信的内容中提到,那么它是怎么实现的呢?一起来分析源码吧。
官方定义:provide/inject这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
provide/inject基本使用
在分析源码之前,先看下其基本使用,其实一般业务开发代码中比较少用到provide/inject,在写一些库的时候才会用到,那么看看基本使用方法吧:
index.html
<section id="app"><button @click="plus">+1</button><div id="count">count:{{ count }}</div><display-count></display-count></section><script src="../../dist/vue.js"></script><script src="app.js"></script><section id="app"> <button @click="plus">+1</button> <div id="count">count:{{ count }}</div> <display-count></display-count> </section> <script src="../../dist/vue.js"></script> <script src="app.js"></script><section id="app"> <button @click="plus">+1</button> <div id="count">count:{{ count }}</div> <display-count></display-count> </section> <script src="../../dist/vue.js"></script> <script src="app.js"></script>
app.js
const store = {}const DisplayCount = {name: 'DisplayCount',template: `<div>DisplayCount: {{$store.state.count}}</div>`,inject: ['$store'],data() {return {}},}var app = new Vue({name: 'SimpleDemoAPI_Provide_Inject',components: {'display-count': DisplayCount},provide: {$store: store},data() {return {count: 0}},created() {store.state = this.$data},methods: {plus() {this.count += 1;}}})const store = {} const DisplayCount = { name: 'DisplayCount', template: ` <div> DisplayCount: {{$store.state.count}} </div> `, inject: ['$store'], data() { return { } }, } var app = new Vue({ name: 'SimpleDemoAPI_Provide_Inject', components: { 'display-count': DisplayCount }, provide: { $store: store }, data() { return { count: 0 } }, created() { store.state = this.$data }, methods: { plus() { this.count += 1; } } })const store = {} const DisplayCount = { name: 'DisplayCount', template: ` <div> DisplayCount: {{$store.state.count}} </div> `, inject: ['$store'], data() { return { } }, } var app = new Vue({ name: 'SimpleDemoAPI_Provide_Inject', components: { 'display-count': DisplayCount }, provide: { $store: store }, data() { return { count: 0 } }, created() { store.state = this.$data }, methods: { plus() { this.count += 1; } } })
效果:
思维导图
provide/inject链路图:
分析源码
初始化vue的代码片段
源码位置:src\core\instance\init.js
export function initMixin (Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {// ...initInjections(vm) // 初始化inject,读取provide的值,绑定到组件实例中// ...initProvide(vm) // 初始化 provide,原理:把options.provide挂载到vm._provide,inject时读取值,绑定到组件实例中// ...}}export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { // ... initInjections(vm) // 初始化inject,读取provide的值,绑定到组件实例中 // ... initProvide(vm) // 初始化 provide,原理:把options.provide挂载到vm._provide,inject时读取值,绑定到组件实例中 // ... } }export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { // ... initInjections(vm) // 初始化inject,读取provide的值,绑定到组件实例中 // ... initProvide(vm) // 初始化 provide,原理:把options.provide挂载到vm._provide,inject时读取值,绑定到组件实例中 // ... } }
provide/inject的实现代码片段
源码位置:src\core\instance\inject.js
/* @flow */import { hasOwn } from 'shared/util'import { warn, hasSymbol } from '../util/index'import { defineReactive, toggleObserving } from '../observer/index'export function initProvide (vm: Component) {const provide = vm.$options.provideif (provide) {vm._provided = typeof provide === 'function'? provide.call(vm): provide}}export function initInjections (vm: Component) {// 根据inject的提供的key,寻找provide中对应的值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 {// 将inject的key对应的provide的值,绑定到vue/vue组件实例上defineReactive(vm, key, result[key])}})toggleObserving(true)}}// 根据inject的提供的key,寻找provide中对应的值export function resolveInject (inject: any, vm: Component): ?Object {if (inject) {// inject is :any because flow is not smart enough to figure out cachedconst result = Object.create(null)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__') continueconst provideKey = inject[key].fromlet source = vm// 遍历找父级providewhile (source) {if (source._provided && hasOwn(source._provided, provideKey)) {// 找到父级的provide的属性,放到result,再返回result[key] = source._provided[provideKey]break}source = source.$parent}if (!source) {if ('default' in inject[key]) {const provideDefault = inject[key].defaultresult[key] = typeof provideDefault === 'function'? provideDefault.call(vm): provideDefault} else if (process.env.NODE_ENV !== 'production') {warn(`Injection "${key}" not found`, vm)}}}return result}}/* @flow */ import { hasOwn } from 'shared/util' import { warn, hasSymbol } from '../util/index' import { defineReactive, toggleObserving } from '../observer/index' export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide } } export function initInjections (vm: Component) { // 根据inject的提供的key,寻找provide中对应的值 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 { // 将inject的key对应的provide的值,绑定到vue/vue组件实例上 defineReactive(vm, key, result[key]) } }) toggleObserving(true) } } // 根据inject的提供的key,寻找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++) { const key = keys[i] // #6574 in case the inject object is observed... if (key === '__ob__') continue const provideKey = inject[key].from let source = vm // 遍历找父级provide while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { // 找到父级的provide的属性,放到result,再返回 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 } }/* @flow */ import { hasOwn } from 'shared/util' import { warn, hasSymbol } from '../util/index' import { defineReactive, toggleObserving } from '../observer/index' export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide } } export function initInjections (vm: Component) { // 根据inject的提供的key,寻找provide中对应的值 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 { // 将inject的key对应的provide的值,绑定到vue/vue组件实例上 defineReactive(vm, key, result[key]) } }) toggleObserving(true) } } // 根据inject的提供的key,寻找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++) { const key = keys[i] // #6574 in case the inject object is observed... if (key === '__ob__') continue const provideKey = inject[key].from let source = vm // 遍历找父级provide while (source) { if (source._provided && hasOwn(source._provided, provideKey)) { // 找到父级的provide的属性,放到result,再返回 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 } }
从源码上看,可以看出provide/inject的实现很简单,代码量也很少,几十行就搞定了。
下面我们来分析代码逻辑,结合上面的链路图一起看,
- 初始化Vue,首先初始化
inject
,如果是根组件,则是没有inject
的,子组件才会有inject
,继续往下走 - 初始化
provide
,调用initProvide()
,很简单,直接把provide
的值赋值给组件的_provide
属性,保存起来,等待子组件inject读取其值 - 子组件开始初始化
inject
,调用initInjections()
,先把调用resolveInject()
把inject
解析出来,再调用解析出来的值,根据对应的key取到值,再调用defineReactive()
把inject
的key定义到组件实例中(defineReactive()
就是调用Object.defineProperty()
),至此provide/inject
就实现了。 - 那么
resolveInject()
是怎么解析inject的呢?很简单,遍历inject
的key,同时遍历当前组件实例中父级组件的provide
属性,如果就返回就ok了,查找provide
的过程其实就是数据结构中链表的查找的操作。
总结
provide/inject实现原理很简单:
组件初始化时先初始化inject注入的值,初始化的过程就是去查找父组件中的provide的值,再把对应的键和值通过Object.defineProperty把键值定义到当前组件中,所以组件可以通过this访问到inject注入的值。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END