『引言』
🙋🙋♂️提问🚩:常用的父子组件传值的方式有哪些呢❓🤔🤔
回答📒:
- 父传子:父组件通过属性绑定的方式,将数据传递给子组件,并在子组件定义
props
来接收。 - 子传父:子组件使用
this.$emit('方法名', 传递值)
方法触发自定义事件,父组件使用v-on指令监听该事件并执行相应的逻辑。 - 使用
$ref
传值:在子组件上设置一个ref="xxx"
,父组件的方法中,就可以调用this.$refs.xxx
给子组件传值,子组件定义一个变量来接收值。 Vuex
状态管理......还有很多,不一一列举了。
『回顾』
在vue2中也有『[provide & inject]
』这两个API,具体使用请查看官网。
『provide()』
『定义』
【官方解释】
提供一个值,可以被后代组件注入。
-
类型
rfunction 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
只能够向下进行传递数据- 在使用
provide
和inject
的时候需从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
。
如果当前获取的key
在provides
上有值,那么就返回该值,如果没有就判断是否存在默认内容,默认内容如果是个函数,就执行并且通过call方法
把组件实例的代理对象绑定到该函数的this
上,否则就直接返回默认内容。
『provide和inject实现原理』
provide()
和inject()
实现原理就是通过原型链实现的。provide
调用设置的时候,设置父级的provides
为当前provides对象
原型对象上的属性,在inject
获取provides对象
中的属性值时,先获取provides对象
自身的属性,如果自身查找不到,则沿着原型链向上一个对象中去查找。