「Vue3学习篇」-provide 与 inject

『引言』

🙋🙋‍♂️提问🚩:常用的父子组件传值的方式有哪些呢❓🤔🤔

回答📒:

  1. 父传子:父组件通过属性绑定的方式,将数据传递给子组件,并在子组件定义props来接收。
  2. 子传父:子组件使用this.$emit('方法名', 传递值)方法触发自定义事件,父组件使用v-on指令监听该事件并执行相应的逻辑。
  3. 使用 $ref 传值:在子组件上设置一个 ref="xxx",父组件的方法中,就可以调用this.$refs.xxx 给子组件传值,子组件定义一个变量来接收值。
  4. Vuex 状态管理......还有很多,不一一列举了。

『回顾』

在vue2中也有『[provide & inject]』这两个API,具体使用请查看官网

『provide()』

『定义』

【官方解释】 提供一个值,可以被后代组件注入。

  • 类型

    r 复制代码
    function provide<T>(key: InjectionKey<T> | string, value: T): void
  • 详细信息

    provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

    当使用 TypeScript 时,key 可以是一个被类型断言为 InjectionKey 的 symbol。InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide()inject() 之间值的类型。

    与注册生命周期钩子的 API 类似,provide() 必须在组件的 setup() 阶段同步调用。

  • 示例

    xml 复制代码
    <script setup>
    import { ref, provide } from 'vue'
    import { fooSymbol } from './injectionSymbols'
    
    // 提供静态值
    provide('foo', 'bar')
    
    // 提供响应式的值
    const count = ref(0)
    provide('count', count)
    
    // 提供时将 Symbol 作为 key
    provide(fooSymbol, count)
    </script>

『用法』

provide(name,value)

『provide参数说明』

provide 支持两个参数

  • name: 提供被注入的属性名
  • value: 提供的属性值

『inject()』

『定义』

【官方解释】 注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。

  • 类型

    typescript 复制代码
    // 没有默认值
    function inject<T>(key: InjectionKey<T> | string): T | undefined
    
    // 带有默认值
    function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
    
    // 使用工厂函数
    function inject<T>(
      key: InjectionKey<T> | string,
      defaultValue: () => T,
      treatDefaultAsFactory: true
    ): T
  • 详细信息

    第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会"覆盖"链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。

    第二个参数是可选的,即在没有匹配到 key 时使用的默认值。

    第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,你必须将 true 作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。

    与注册生命周期钩子的 API 类似,inject() 必须在组件的 setup() 阶段同步调用。

    当使用 TypeScript 时,key 可以是一个类型为 InjectionKey 的 symbol。InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide()inject() 之间值的类型。

  • 示例

    假设有一个父组件已经提供了一些值,如前面 provide() 的例子中所示:

    xml 复制代码
    <script setup>
    import { inject } from 'vue'
    import { fooSymbol } from './injectionSymbols'
    
    // 注入不含默认值的静态值
    const foo = inject('foo')
    
    // 注入响应式的值
    const count = inject('count')
    
    // 通过 Symbol 类型的 key 注入
    const foo2 = inject(fooSymbol)
    
    // 注入一个值,若为空则使用提供的默认值
    const bar = inject('foo', 'default value')
    
    // 注入一个值,若为空则使用提供的函数类型的默认值
    const fn = inject('function', () => {})
    
    // 注入一个值,若为空则使用提供的工厂函数
    const baz = inject('factory', () => new ExpensiveObject(), true)
    </script>

『用法』

inject(name,default)

『inject参数说明』

  • name: 接收provide 提供的属性名
  • default: 设置默认值,也可以不写

『注意』

  • provide的第二个参数作为导出的值可以是任意参数(非相响应式响应式)
  • provide只能够向下进行传递数据
  • 在使用provideinject的时候需从vue中引入

使用方法就是父级组件使用provide向下进行传递数据,子级组件使用inject来获取上级组件传递过来的数据。 下面通过一个示例,来感受一下。

『示例🌰』

xml 复制代码
//App.vue
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{person.name}}</p>
    <p>年龄:{{person.age}}岁</p>
    <p>地址:{{address.provice}} - {{address.city}}</p>
    <button @click="modifyInfo">
      修改数据
    </button>
    <ProvideInject></ProvideInject>
  </div>
</template>

<script>
import { ref, reactive, provide } from 'vue'
import ProvideInject from './components/provideInject.vue'
export default {
  components: {
    ProvideInject
  },
  setup() {
    const person = ref({
      name: 'pupu',
      age: 10
    })
    const address = reactive({
      provice: '浙江省',
      city: '杭州市'
    })
    provide('changePerson',person)
    provide('changeAddress',address)
    const modifyInfo = () => {
      person.value.name = 'wnxx'
        person.value.age = 3 
        address.provice = '云南省'
        address.city = '丽江市'
    }
    return {
      person,
      address,
      modifyInfo
    }
  }
}
</script>
xml 复制代码
//provideInject.vue
<template>
  <div>
    <h1>子组件</h1>
    <div>接受到的数据:{{ getPerson }} - {{ getAddress }}
    <h1>人物简介</h1>
    <p>姓名:{{getPerson.name }}</p>
    <p>年龄:{{getPerson.age}}岁</p>
    <p>地址:{{getAddress.provice}}- {{ getAddress.city }}</p>
    </div>
  </div> 
</template>

<script setup>
import { inject } from 'vue'

    const getPerson = inject('changePerson', '这是默认值我叫pupu,今年10岁')
    const getAddress = inject('changeAddress', '我来自浙江省-杭州市')
    console.log(getPerson, 'getperson')
    console.log(getAddress, 'getaddress')
</script>

『效果展示』

『代码解析』

上述代码中,定义了『person和address』,然后使用provide('changePerson',person) 和provide('changeAddress',address)provideInject组件传递数据。

provideInject组件中通过inject('changePerson')和inject('changeAddress')接收APP组件传递过来的数据。

『provide源码』

provide源码如下⬇️:

typescript 复制代码
export function provide<T, K = InjectionKey<T> | string | number>(
  key: K,
  value: K extends InjectionKey<infer V> ? V : T
) {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    let provides = currentInstance.provides
    // by default an instance inherits its parent's provides object
    // but when it needs to provide values of its own, it creates its
    // own provides object using parent provides object as prototype.
    // this way in `inject` we can simply look up injections from direct
    // parent and let the prototype chain do the work.
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
    if (parentProvides === provides) {
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    provides[key as string] = value
  }
}

provide通过获取当前组件的实例对象,获取当前组件实例上provides属性,获取当前父级组件的provides属性,将传进来的数据存储在当前的组件实例对象上的provides上,如果当前的provides父级的provides相同则说明还没赋值,就通过Object.create把父组件的provides属性设置到当前的组件实例对象的provides属性的原型对象上。

『inject源码』

inject源码如下⬇️:

typescript 复制代码
export function inject(
  key: InjectionKey<any> | string,
  defaultValue?: unknown,
  treatDefaultAsFactory = false
) {
  const instance = currentInstance || currentRenderingInstance
  if (instance || currentApp) {

    const provides = instance
      ? instance.parent == null
        ? instance.vnode.appContext && instance.vnode.appContext.provides
        : instance.parent.provides
      : currentApp!._context.provides

    if (provides && (key as string | symbol) in provides) {
      // TS doesn't allow symbol as index type
      return provides[key as string]
    } else if (arguments.length > 1) {
      return treatDefaultAsFactory && isFunction(defaultValue)
        ? defaultValue.call(instance && instance.proxy)
        : defaultValue
    } else if (__DEV__) {
      warn(`injection "${String(key)}" not found.`)
    }
  } else if (__DEV__) {
    warn(`inject() can only be used inside setup() or functional components.`)
  }
}

inject先获取当前组件的实例对象,然后判断是否根组件,如果intance位于根目录下,就返回到appContext的provides,否则就返回父组件的provides

如果当前获取的keyprovides上有值,那么就返回该值,如果没有就判断是否存在默认内容,默认内容如果是个函数,就执行并且通过call方法把组件实例的代理对象绑定到该函数的this上,否则就直接返回默认内容。

『provide和inject实现原理』

provide()inject()实现原理就是通过原型链实现的。provide调用设置的时候,设置父级的provides当前provides对象原型对象上的属性,在inject获取provides对象中的属性值时,先获取provides对象自身的属性,如果自身查找不到,则沿着原型链向上一个对象中去查找。

相关推荐
musk121211 分钟前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
翻滚吧键盘40 分钟前
js代码09
开发语言·javascript·ecmascript
万少1 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL1 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl022 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang2 小时前
前端如何实现电子签名
前端·javascript·html5
海天胜景2 小时前
vue3 el-table 行筛选 设置为单选
javascript·vue.js·elementui
今天又在摸鱼2 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿2 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再2 小时前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref