Understanding Dependency Injection (DI – Dependency Injection)


dependency injection

What if there is a deep child component that needs some data from a distant ancestor component?

  1. Props can be used to pass them step by step along the component chain, as shown in Figure 1
  2. We can use provide to provide data in ancestor components, and use inject to inject data in descendant components, as shown in Figure 2

figure 1:
Insert image description here

figure 2:
Insert image description here

1.provide(provide)

How to provide it at the application layer:
At the application layer, you can provide data for future generations through app.provide()

//应用层提供数据
import {
    
     createApp } from 'vue'
const app = createApp({
    
     })
app.provide('message', 'hello!') // message 注入名, 'hello'

How to provide in component:

1.1 In the optional API, data can be provided for future generations through the provide option
//Provide 选项提供数据【选项式】
export default {
    
    
    // 为后代组件提供数据选项
    provide: {
    
     title: '你好世界!' } // message:注入名,'hello':值
}

In the combined API script setup, data can be provided to descendant components through the provide() function.

//使用 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 If you want to access the component instance this, provide must be in the form of a function (arrow functions cannot be used). In order to ensure the responsive link between the injector and the supplier, the computed() function in the combined API must be used to provide calculations. Attributes can also provide functions for modifying responsive data (modification of responsive data should be placed in the same component as much as possible for easy maintenance)
//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
        }
	}
}

Note: For responsive data provided through the computed function in the provide option, app.config.unwrapInjectedRef = true needs to be set to ensure that the injection will automatically unwrap this calculated property. This will become a default behavior after Vue 3.3, and we temporarily inform this configuration here to avoid subsequent upgrades from being destructive to the code. This is no longer necessary after 3.3.

2.inject(injection)

2.1 In the optional API, you can use the inject option to declare that the data provided by the ancestor component needs to be injected. They can be accessed directly through this in JS and can also be accessed directly in the view template.

Optional

export default {
    
    
    // 注入祖先组件提供的数据
    inject: ['message', 'title', 'subtitle', 'changeSubtitle'] 
}

Combined

在这里插入代码片

**In the combined API, use the return value of the inject() function to inject the data provided by the ancestor component

  1. If the value of the provided data is a ref, the ref object will be injected, maintaining a responsive connection with the provider.
  2. If the injected data is not provided in the ancestor component, a warning will be thrown. This can be solved by setting a default value in the second parameter of the provide() function.
  3. They can be accessed directly in JS and view templates**
<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 What are the benefits of using inject to inject data provided by ancestor components in the form of objects?

a. You can use local attribute names to inject data provided by ancestor components (if the same, the from option can be omitted)
b. If the injected data is not provided in the ancestor component, a warning will be thrown. You can use the defalut option to set a default value. solve

inject object form [optional]

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. Example

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 options

<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 combined

<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 option

<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 combined

<script setup>
import DeepChildVue from './DeepChild.vue';
</script>

<template>
    <div class="area" style="background-color: yellow">
        <h3>这是 Footer 组件</h3>
        <DeepChildVue />
    </div>
</template>

DeepChild.vue option

<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 combined

<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>

Summarize

Dependency Injection (DI) is a design pattern and one of the core concepts of the Spring framework. Its function is to remove dependencies between Java classes and achieve loose coupling to facilitate development and testing. In order to better understand DI, first understand the problem that DI needs to solve.

Guess you like

Origin blog.csdn.net/www61621/article/details/129312856