文章目录
- 依赖注入
- 1.provide(提供)
- 1.1 在选项式 API 中,可通过provide选项为后代提供数据
- 1.2 如果想访问到组件的实例this,provide必须采用函数的方式(不能用箭头函数),为保证注入方和供给方之间的响应性链接,必须借助组合式 API 中的computed()函数提供计算属性,还可以提供修改响应式数据的函数(响应式数据的修改,尽量放在同一个组件中,为了好维护)
- 2.inject(注入)
- 2.1 在选项式 API 中,可通过inject选项来声明需要注入祖先组件提供的数据,他们可以在JS中直接通过this来访问,在视图模板中也可直接访问
- 2.1 inject采用对象的形式来注入祖先组件提供的数据有哪些好处?
- 3. 例子
- 总结
依赖注入
如果有一个深层的子组件需要一个较远的祖先组件中的部分数据,如果实现呢?
- 可使用props沿着组件链逐级传递下去,如图 1
- 我们可在祖先组件使用provide提供数据,后代组件使用inject注入数据,如图 2
图1:
图2:
1.provide(提供)
在应用层如何提供:
在应用层方面可通过app.provide()为后代提供数据
//应用层提供数据
import { createApp } from 'vue'
const app = createApp({ })
app.provide('message', 'hello!') // message 注入名, 'hello' 值
在组件中如何提供:
1.1 在选项式 API 中,可通过provide选项为后代提供数据
//Provide 选项提供数据【选项式】
export default {
// 为后代组件提供数据选项
provide: { title: '你好世界!' } // message:注入名,'hello':值
}
在组合式 API script setup 中,可通过provide()函数来为后代组件提供数据
//使用 provide 函数提供数据
<script setup>
import { ref, provide } from 'vue'
const message = 'hello'
const title = ref('你好世界')
const subtitle = ref('分享经验')
function changeSubtitle(sub) {
this.subtitle = sub
}
provide('message', message) // 提供固定数据
provide('title', title) // 提供响应式数据
provide('subtitle', subtitle) // 提供响应式数据
provide('changeSubtitle', changeSubtitle) // 为后代组件提供修改响应式数据 subtitle 的函数
</script>
1.2 如果想访问到组件的实例this,provide必须采用函数的方式(不能用箭头函数),为保证注入方和供给方之间的响应性链接,必须借助组合式 API 中的computed()函数提供计算属性,还可以提供修改响应式数据的函数(响应式数据的修改,尽量放在同一个组件中,为了好维护)
//Provide 函数选项提供数据【选项式】
export default {
data: () => ({
title: '你好世界',
subtitle: '经验'
}),
methods: {
changeSubtitle(sub) {
this.subtitle = sub
}
},
// 使用函数的形式,可以访问到组件的实例 `this`
provide() {
return {
// 传递数据的值为数据源 title,此方式注入方和供给方之间没有响应性链接
title: this.title,
// 传递数据的值为数据源 subtitle,此方式注入方和供给方之间具有响应性链接
subtitle: computed(() => this.subtitle),
// 为后代组件提供修改响应式数据 subtitle 的函数
changeSubtitle: this.changeSubtitle
}
}
}
注意:provide选项中通过computed函数提供的响应式的数据,需要设置app.config.unwrapInjectedRef = true以保证注入会自动解包这个计算属性。这将会在 Vue 3.3 后成为一个默认行为,而我们暂时在此告知此项配置以避免后续升级对代码的破坏性。在 3.3 后就不需要这样做了。
2.inject(注入)
2.1 在选项式 API 中,可通过inject选项来声明需要注入祖先组件提供的数据,他们可以在JS中直接通过this来访问,在视图模板中也可直接访问
选项式
export default {
// 注入祖先组件提供的数据
inject: ['message', 'title', 'subtitle', 'changeSubtitle']
}
组合式
在这里插入代码片
**在组合式 API 中,使用inject()函数的返回值来注入祖先组件提供的数据
- 如果提供数据的值是一个ref,注入进来的会是该ref对象,和提供方保持响应式连接
- 如果注入的数据并没有在祖先组件中提供,则会抛出警告,可在provide()函数的第二个参数设置默认值来解决
- 他们可以在JS和视图模板中直接访问**
<script setup>
import { inject } from 'vue'
const c_message = inject('message')
const title = inject('title')
const c_subtitle = inject('subtitle')
const c_changeSub = inject('changeSubtitle')
// 祖先组件并未提供 content,则会报警告,设置默认值来解决
const c_content = inject('content', '暂时还未有内容')
</script>
2.1 inject采用对象的形式来注入祖先组件提供的数据有哪些好处?
a. 可用本地属性名注入祖先组件提供的数据(如相同时,from选项可省略)
b. 如果注入的数据并没有在祖先组件中提供,则会抛出警告,可采用defalut选项设置默认值来解决
inject 对象形式【选项式】
export default {
// 注入祖先组件提供的数据
inject: {
c_message: {
from: 'message', // 注入的哪个数据
},
// 本地属性名和需要注入的数据名一致时,from 可省略
title, // 普通数据
c_subtitle: {from: 'subtitle'}, // 响应式数据
c_changeSub: {from: 'changeSubtitle'}, // 修改响应式数据 subtitle 的函数
c_content: {
from: 'content', // 祖先组件并未提供 content,则会报警告
default: '暂时还未有内容' // 设置默认值(可为函数等),解决警告问题
}
}
}
3. 例子
main.js
import { createApp } from 'vue'
import App from './App.vue'
let app = createApp(App)
// 为所有的组件提供数据
app.provide('message', '登陆成功')
// provide 选项中通过 computed 函数提供的响应式的数据,需要设置此项以保证注入会自动解包这个计算属性。
app.config.unwrapInjectedRef = true
app.mount('#app')
app.vue 选项式
<script>
import { computed } from 'vue';
import FooterVue from './components/Footer.vue'
export default {
components: { FooterVue },
data: () => ({
title: '博客',
subtitle: '百万博主分享经验'
}),
methods: {
changeSubtitle(sub) {
this.subtitle = sub
}
},
// provide: { title: this.title }, // 对象方式访问不到 this
// 如果想访问组件实例 this,需要采用函数方式
provide() {
return {
title: this.title, // 和注入方不具备响应式连接
subtitle: computed(() => this.subtitle), // 借助组合式 API 中的 computed 函数,提供和注入方具备响应式连接的数据
changeSubtitle: this.changeSubtitle // 提供修改响应式 subtitle 的函数
}
}
}
</script>
<template>
<div class="area" style="background-color: red">
<h3>这是 APP 组件</h3>
副标题:<input type="text" v-model="subtitle">
<FooterVue />
</div>
</template>
<style>
.area {
padding: 15px;
}
</style>
app.vue 组合式
<script setup>
import { provide, ref } from 'vue';
import FooterVue from './components/Footer.vue'
let title = '博客'
let subtitle = ref('百万博主分享经验')
function changeSubtitle(sub){
subtitle.value = sub
}
// 为后代组件提供数据
provide('title', title) // 提供普通的数据
provide('subtitle', subtitle) // 提供响应式的数据,自动和注入方保持响应式的连接
provide('changeSubtitle', changeSubtitle) // 提供修改响应式的数据 subtitle 的函数
</script>
<template>
<div class="area" style="background-color: red">
<h3>这是 APP 组件</h3>
副标题:<input type="text" v-model="subtitle">
<FooterVue />
</div>
</template>
<style>
.area {
padding: 15px;
}
</style>
Footer.vue选项式
<script>
import DeepChildVue from './DeepChild.vue';
export default {
components: { DeepChildVue }
}
</script>
<template>
<div class="ar ea" style="background-color: yellow">
<h3>这是 Footer 组件</h3>
<DeepChildVue />
</div>
</template>
Footer.vue组合式
<script setup>
import DeepChildVue from './DeepChild.vue';
</script>
<template>
<div class="area" style="background-color: yellow">
<h3>这是 Footer 组件</h3>
<DeepChildVue />
</div>
</template>
DeepChild.vue选项式
<script>
export default {
// inject: ['message', 'title', 'subtitle', 'changeSubtitle'],
// 注入祖先组件提供的数据
inject: {
d_message: { from: 'message' }, // 应用层数据
title: {}, // 普通数据,如果注入属性名和本地名一致,则 from 可省略
d_subtitle: { from: 'subtitle' }, // 响应式数据
d_changeSub: { from: 'changeSubtitle' }, // 函数
d_content: { from: 'content' }, // 祖先组件并未提供数据,则会抛出警告
d_action: { from: 'action' , default: '关注博客'} // 祖先组件并未提供数据,则会抛出警告,可设置默认值,警告则取消
},
methods: {
showInjectData() {
console.log('应用层提供的数据 message 的值:' + this.d_message);
console.log('APP 组件提供的 title 的值:' + this.title);
console.log('APP 组件提供的 subtitle 的值:');
console.log(this.d_subtitle);
console.log('获取祖先组件提供 content 的数据(并没有):' + this.d_content);
console.log('获取祖先组件提供 action 的数据(并没有,但是设置默认值):' + this.d_action);
this.d_changeSub('EDF')
}
}
}
</script>
<template>
<div class="area" style="background-color: pink">
<h3>这是 DeepChild 组件</h3>
<ul>
<li>应用层提供的数据 message 的值:{{ d_message }}</li>
<li>APP 组件提供的 title 的值:{{ title }}</li>
<li>APP 组件提供的 subtitle 的值:{{ d_subtitle }}</li>
<li>获取祖先组件提供 content 的数据(并没有):{{ d_content }}</li>
<li>获取祖先组件提供 action 的数据(并没有,但是设置默认值):{{ d_action }}</li>
</ul>
<button @click="d_changeSub('ABC')">修改APP组件中 subtitle 的值</button>
<button @click="showInjectData">在 JS 中访问注入的数据</button>
</div>
</template>
DeepChild.vue组合式
<script setup>
import { inject } from 'vue';
// 注入祖先组件提供的数据
let d_message = inject('message') // 应用层提供的数据
let d_title = inject('title') // 普通数据
let d_subtitle = inject('subtitle') // ref 响应式数据,则 d_subtitle 也是 ref 对象
let d_changeSub = inject('changeSubtitle') // 函数
let d_content = inject('content') // 祖先组件并未提供,则会抛出警告
let d_action = inject('action', '关注博客') // 祖先组件并未提供,则会抛出警告
function showInjectData() {
console.log('应用层提供的数据 message 的值:' + d_message);
console.log('APP 组件提供的 title 的值:' + d_title);
console.log('APP 组件提供的 subtitle 的值:');
console.log(d_subtitle);
console.log('获取祖先组件提供 content 的数据(并没有):' + d_content);
console.log('获取祖先组件提供 action 的数据(并没有,但是设置默认值):' + d_action);
d_changeSub('EDF')
}
</script>
<template>
<div class="area" style="background-color: pink">
<h3>这是 DeepChild 组件</h3>
<ul>
<li>应用层提供的数据 message 的值:{{ d_message }}</li>
<li>APP 组件提供的 title 的值:{{ d_title }}</li>
<li>APP 组件提供的 subtitle 的值:{{ d_subtitle }}</li>
<li>获取祖先组件提供 content 的数据(并没有):{{ d_content }}</li>
<li>获取祖先组件提供 action 的数据(并没有,但是设置默认值):{{ d_action }}</li>
</ul>
<button @click="d_changeSub('ABC')">修改APP组件中 subtitle 的值</button>
<button @click="showInjectData">在 JS 中访问注入的数据</button>
</div>
</template>
总结
依赖注入(Dependency Injection, DI)是一种设计模式,也是Spring框架的核心概念之一。其作用是去除Java类之间的依赖关系,实现松耦合,以便于开发测试。为了更好地理解DI,先了解DI要解决的问题。