阅读 1147

vue2--provide/inject 的使用及思考

背景

最近,项目中需要实现一个按月份切换且展示多块数据、多种操作的数据大盘页面,所以我们按照数据类型,切分了多个子组件来进行开发。

而大盘中对数据的操作,需要使用当前选中的月份信息,所以我们打算将月份信息month传入数据子组件中。但是考虑到有的子组件层级相对较深,不想写太多的prop,我们就打算使用provide/inject来进行组件间的数据交互。

初步使用

我们可以知道,provide/inject中绑定的数据并不是可响应的(详情见官网),不能直接传递一个数值,所以我们使用了传递一个函数来返回月份month的方法。

// 父组件
<template>
    <input type="month" id="myMonth" v-model="month">
    <Test />
</template>

<script>
import Test from './components/test.vue';

export default {
    components: { Test },
    
    data() {
        return {
            month: '2021-10',
        };
    },

    provide() {
        return {
            getProvideMonth: this.getProvideMonth,
        };
    },

    methods: {
        getProvideMonth() {
            return this.month;
        },
    },
}
</scritp>

// 子组件 components/test.vue
<template>
    <span>month:{{ getProvideMonth() }}</span>
</template>

<script>
export default {
    inject: ['getProvideMonth']
}
</scritp>
复制代码

效果如下,子组件能随着父组件中月份信息变化而变化。

inject.gif

继续深入

可监听对象

官方文档中有如下描述,

如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

那么,首先我就想在data中声明一个对象来实现月份的可响应逻辑。

// 父组件
<template>
    <input type="month" id="myMonth" v-model="monthObj.value">
    <Test />
</template>

<script>
import Test from './components/test.vue';

export default {
    components: { Test },
    
    data() {
        return {
            monthObj: {
                value: '2021-10',
            },
        };
    },

    provide() {
        return {
            monthObj: this.monthObj,
        };
    },
}
</scritp>

// 子组件 components/test.vue
<template>
    <div>
        <div>monthObj:{{ monthObj.value }}</div>
    </div>
</template>

<script>
export default {
    inject: ['monthObj']
}
</scritp>
复制代码

inject2.gif

这里我们将month的信息放置在可响应的对象monthObj中,使其数据能在子组件中获取变化后的值。

引用不变的可监听对象

这里需要注意的一个点是,这里的响应式对象需要是一个引用不变的可监听对象。 那么,在下面几种情况中,就会出现可响应失效的情况。

  • 使用computed来返回对象
// 父组件
<template>
    <mtd-date-picker type="month" placeholder="选择时间" value-format="yyyy-MM" v-model="month" />
    <div>父组件monthObj:{{ monthObj.value }}</div>
    <Test />
</template>

<script>
import Test from './components/test.vue';

export default {
    components: { Test },
    
    data() {
        return {
            month: '2021-10',
        };
    },
    
    computed: {
        monthObj() {
            return {
                value: this.month,
            };
        },
    },

    provide() {
        return {
            monthObj: this.monthObj,
        };
    },
}
</scritp>

// 子组件 components/test.vue
<template>
    <div>
        <div>monthObj:{{ monthObj.value }}</div>
    </div>
</template>

<script>
export default {
    inject: ['monthObj']
}
</scritp>
复制代码

inject3.gif

  • 用一个对象给monthObj赋值
// 父组件
<template>
    <mtd-date-picker type="month" placeholder="选择时间" value-format="yyyy-MM" v-model="month" />
    <div>父组件monthObj:{{ monthObj.value }}</div>
    <Test />
</template>

<script>
import Test from './components/test.vue';

export default {
    components: { Test },
    
    data() {
        return {
            month: '2021-10',
            monthObj: {
                value: '2021-10',
            },
        };
    },
    
    watch: {
        month() {
            this.monthObj = { value: this.month };
        },
    },

    provide() {
        return {
            monthObj: this.monthObj,
        };
    },
}
</scritp>

// 子组件 components/test.vue
<template>
    <div>
        <div>monthObj:{{ monthObj.value }}</div>
    </div>
</template>

<script>
export default {
    inject: ['monthObj']
}
</scritp>
复制代码

inject3.gif

最终解法

上面的例子中,我们传递的是一个参数,但是实际可能传递多个参数,而多个参数会带来针对多个对应函数或者对象的维护。为了能早点下班,这里我们可以直接传入this作为可响应的对象。

// 父组件
<template>
    <mtd-date-picker type="month" placeholder="选择时间" value-format="yyyy-MM" v-model="month" />
    <Test />
</template>

<script>
import Test from './components/test.vue';

export default {
    components: { Test },
    
    data() {
        return {
            month: '2021-10',
        };
    },
    

    provide() {
        return {
            app: this,
        };
    },
}
</scritp>

// 子组件 components/test.vue
<template>
    <div>
        <div>子组件app:{{ app.month }}</div>
    </div>
</template>

<script>
export default {
    inject: ['app']
}
</scritp>
复制代码

inject4.gif

当然,有的时候我们不想暴露将父组件全部的属性给子组件,那么可以添加一个函数,返回所有需要暴露给子组件的属性(当前项目最终采用方法)。

// 父组件
<template>
    <mtd-date-picker type="month" placeholder="选择时间" value-format="yyyy-MM" v-model="month" />
    <Test />
</template>

<script>
import Test from './components/test.vue';

export default {
    components: { Test },
    
    data() {
        return {
            month: '2021-10',
        };
    },
    

    provide() {
        return {
            getApp: this.getApp,
        };
    },
    
    methods: {
        getApp() {
            return {
                month: this.month,
                // 其他需要暴露的属性
            };
        },
    },
}
</scritp>

// 子组件 components/test.vue
<template>
    <div>
        <div>子组件app:{{ getApp().month }}</div>
    </div>
</template>

<script>
export default {
    inject: ['getApp']
}
</scritp>
复制代码

inject4.gif

总结

provide/inject中绑定的数据并不是可响应的,若要实现响应需要传递一个可监听对象,并注意该对象的应用不能发生变化。

参考资料

文章分类
前端
文章标签