07 | 【阅读Vue2源码】Provide/Inject实现原理

前言

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.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
  }
}
/* @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的实现很简单,代码量也很少,几十行就搞定了。

下面我们来分析代码逻辑,结合上面的链路图一起看,

  1. 初始化Vue,首先初始化inject,如果是根组件,则是没有inject的,子组件才会有inject,继续往下走
  2. 初始化provide,调用initProvide(),很简单,直接把provide的值赋值给组件的_provide属性,保存起来,等待子组件inject读取其值
  3. 子组件开始初始化inject,调用initInjections(),先把调用resolveInject()inject解析出来,再调用解析出来的值,根据对应的key取到值,再调用defineReactive()inject的key定义到组件实例中(defineReactive()就是调用Object.defineProperty()),至此provide/inject就实现了。
  4. 那么resolveInject()是怎么解析inject的呢?很简单,遍历inject的key,同时遍历当前组件实例中父级组件的provide属性,如果就返回就ok了,查找provide的过程其实就是数据结构中链表的查找的操作。

总结

provide/inject实现原理很简单:

组件初始化时先初始化inject注入的值,初始化的过程就是去查找父组件中的provide的值,再把对应的键和值通过Object.defineProperty把键值定义到当前组件中,所以组件可以通过this访问到inject注入的值。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYxRglsc' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片