理解依赖注入(DI – Dependency Injection)


依赖注入

如果有一个深层的子组件需要一个较远的祖先组件中的部分数据,如果实现呢?

  1. 可使用props沿着组件链逐级传递下去,如图 1
  2. 我们可在祖先组件使用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()函数的返回值来注入祖先组件提供的数据

  1. 如果提供数据的值是一个ref,注入进来的会是该ref对象,和提供方保持响应式连接
  2. 如果注入的数据并没有在祖先组件中提供,则会抛出警告,可在provide()函数的第二个参数设置默认值来解决
  3. 他们可以在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要解决的问题。

猜你喜欢

转载自blog.csdn.net/www61621/article/details/129312856