1

Provide与Inject

一、父子邻代传值的逐级​问题

透传、Props、v-model传值都是父传子,属邻代传值,邻代传值不能䣓代,如父值孙用,需父传子、子再传孙的逐级传递。
而Provide与Inject函数能解决隔代传值问题。一个父组件使用Provide函数构造的对象,任何后代的组件树(无论层级有多深),都可以通过Inject函数注入后使用。

二、Provide(提供)

1、书写规范

provide('注入名', 值)
注入名: 需用引号括起,可以随便命名,但后代使用Inject函数注入时,一定要与provide函数注入名相同。
值: 可以是基本类型、函数、变量、对象、数组。当值是基本类型的字符时,要加引号如:provide('注入名', '字符')

2、禁止可写readonly函数

当provide函数传的值是引用型数据时,可以使用readonly函数对值进行包装;经readonly函数包装的值,在后代使用时禁止可写,强行写会报警。
基本类型或基本类型变量不能使用readonly函数包装,系统会报错。
ref变量会失效(即使他的value值是引用型数据),请用reactive构建响应性函数而不要用ref去构建。
书写格式:provide('注入名', readonly(值))
注意:provide与readonly要先用import从库中导入。
实例如下:
父组件

<template>
  <HelloWor/>
  {{location}}
</template>
<script setup>
import { provide, ref ,readonly} from 'vue'
import HelloWor from './Hello.vue'
const location = ref('North Pole')
const loca = {aaa:8}
function updateLocation() {location.value = 'South Pole'}
provide('location', {
  location,
  updateLocation
})
provide('bbb', readonly(loca))
</script>

子组件

<template>
  <button @click="updateLocation">{{ location }}</button>
  {{tyt.aaa}}
</template>
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
const tyt = inject('bbb')
tyt.aaa=9
</script>

3、全局Provide(提供)

在用createApp函数构造出app对象后,app对象​内含Provide函数,调用app对象的Provide函数生成的提供可在全局的组件中使用。代码如下:

import { createApp } from 'vue'
const app = createApp({})
app.provide('注入名', {值,函数})

三、Inject (注入)

1、书写规范

const value = inject('注入名',默认值, true)
当不需设置默认值时,可简写成:inject('注入名')
注入名: 需用引号括起,一定要与provide函数注入名相同。
默认值: 当没有Provide(提供)时,可以设置默认值代替。默认值可以是变量、函数、也可以是基本类型,当值是基本类型的字符时,要加引号。
true: 当默认值是函数时,可以加入第三个参数'true'。当为true时,函数会运行后返回值给变量,无true时,是整个函数体赋给变量,如下:
const va=inject('key',()=>{return '我是返回值'})等同于const va=function(){return '我是返回值'}而有true时,等同于const va='我是返回值'。

2、类型继承

inject函数构造的对象继承Provide注入值的类型。

3、解构

当Provide注入值的类型是对象或数组时,可以解构。对象解构 const{属性名1,属性名2}=inject('注入名')。数组解构 const[a,b]=inject('注入名')。
对象解构,变量名一定要与父值的属性同名。

4、readonly包装对象中的ref自动解包特性

父组件用readonly函数包装的对象里的ref变量,在后代组件inject函数注入时会自动解包,不需加value。readonly函数直接包装的ref、包装的数组里的ref不会自动解包,需加value。没经readonly函数包装的对象里的ref变量也不会自动解包,需加value。
如果ref的value属性值是基本类型且放在对象中被readonly包装,子组件从对象里解构出来的变量会失去value沦为基本类型​变量,失去与父的全等相互响应性。
为避免ref在子组件沦为基本类型​变量,请避免ref的value属性值为基本类型。

四、通过父函修改父值

1、改值规范

当值是引用型数据时,通过子可改父值,但建议尽可能将改变传值保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。

2、改值规范​的实现

Provide隔代传值可以通过readonly函数对引用型数据在子组件写禁用,防止子组件对父值的污染。可以将修改父值函数一并Provide给子组件,当子组件需要修改父值时,可通过调用父函数修改父值。
实例如下:
父组件

<template>
  <HelloWor/>
  {{location.tt}}
</template>
<script setup>
import { provide, ref ,readonly,reactive} from 'vue'
import HelloWor from './Hello.vue'
const location =reactive({tt:'点击父函修改值'})
function updateLocation() {location.tt = '父函改值成功'}
provide('location', readonly({
  updateLocation,
  location
}))
</script>

子组件

<template>
  <button @click="updateLocation">{{location.tt}}</button>
  <button @click="upda">测试location可写禁用</button>
</template>
<script setup>
import { inject } from 'vue'
const  {updateLocation, location}= inject('location')
function upda() {location.tt=87}
</script>

五、隔代传值(Provide)与邻代传值的曲别

1、传值机制

邻代传值(透传、Props、v-model)需要把变量赋在父标签的属性上,属性再赋给子变量。
隔代传值(Provide)变量不需借助父标签上的属性,直接赋值给子组件的变量。

2、父子响应性

引用型变量: 无论是邻代还是隔代传值,父子都可互相响应更新。
基本类型变量: 邻代传值子组件只可读,子组件能借助父标签,利用连带更新手动响应父(单向响应)。
隔代传值子组件可写,但互不响应,因不能借助父标签,不能利用连带更新(其它响应性变量更新)手动响应父。
关于连带更新详见vue3个人心得---(响应式初解)响应式对象ref、reactive

3、DOM响应性

响应性对象: 无论是邻代还是隔代传值,子组件都有DOM响应性。但响应性实现原理不同
邻代传值:子组件变量更新=>父变量更新=>父标签的DOM更新=>子组件刷新=>子DOM响应更新。
隔代传值:子组件继承父变量的响应性,子组件变量更新,子DOM响应更新,无需通过父组件。
非响应性对象: 邻代传值,利用父组件的响应性变量,连带更新父标签实现子组件变量的手动DOM响应。隔代传值,利用子组件的响应性变量,连带更新DOM实现手动DOM响应。

4、关于ref

邻代传值: ref在父标签上会自动解包,传值是value属性而不是ref对象,当value值是基本类型时,子变量沦为基本类型,只能手动响应父,且子变量只读。
隔代传值 是整个ref对象赋给子变量。子变量继承父变量的ref,在子组件中使用需加上value。

5、生命周期

除DOM对象外父参在子组件初始化时即可使用。
在<template>使用,DOM对象需定义为ref响应性变量且变量要赋空对象(避免‘无法读取未定义属性’警告),注ref变量不要使用readonly函数包装,否则失去相互响应性。在<template>使用在<script setup>下引用需放在onBeforeUpdate生命函数之上的函数体内。


百分之一百零八
15 声望3 粉丝