相信你们在写业务的时候vue
有追求的程序员都会把一个页面分为好几个组件程序员
这样就有解耦性、组件化vuex
虽然这样作当然好,但有时候很是麻烦,api
一个页面有很是多组件,通讯就很是繁琐缓存
好比如下情景,markdown
咱们就能够用provide/inject
+ mixin
ide
但看到这,熟悉vue的小伙伴确定脱口而出这样也能够用vuex啊函数
确实,这也官方推荐的组件化
但!我的以为vuex在这种状况下仍是比较“重”的,布局
要写的业务代码也比较多
那不如往下看看provide/inject
+ mixin
的妙用?
字面意思,provide是提供的意思、inject是注入的意思,
事实上也是这样,provide 是在父组件使用、inject是在后代组件使用
这里的后代组件的意思其实就是父组件包含的子、子孙组件,无论你层级多深都是后代组件
例如
在这里,咱们能够把整个页面容器做为父组件,提供数据使用provide
在父组件中所包含的子组件、子孙组件可使用inject
获取父组件提供的数据
咱们在这选取图中上半部分做为示例代码,让小伙伴们看得更清晰
项目文件所的层级结构:
页面布局结构:
代码:
父组件(页面容器)
<template>
<div>
父组件输入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '内容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this
}
}
}
</script>
复制代码
子组件A
<template>
<div>
子组件A输入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
复制代码
子孙组件B
<template>
<div>
子孙组件B输入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
复制代码
来看看效果:
从效果图能够看出,
不论修改哪一个组件数据,其余组件数据也会跟着改变,
不知道小伙伴有没有发现,这其实有点相似vuex?
vuex将状态放在state里,使用mutation对state修改状态
一样,咱们将页面数据text放在最顶层的父容器组件中,
使用provide暴露改变text的changeText方法
但重点是,咱们注入了页面容器父组件整个this
// 父组件部分代码
....
data () {
return {
text: '内容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this
}
}
复制代码
与此同时,在子组件使用computed作一个监听缓存
因此,不论修改哪一个组件数据,其余组件数据也会跟着改变
但,咱们稍微把例子改一下就会发生有趣的事情
咱们在父容器组件增长提供pageText
和pageChangeText
方法
A组件保持不变,修改B组件
代码:
父组件(页面容器组件)
<template>
<div>
页面父组件输入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '内容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this,
pageText: this.text,
pageChangeText: this.changeText
}
}
}
</script>
复制代码
子组件A(代码不改动)
<template>
<div>
子组件A输入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
复制代码
子孙组件B
<template>
<div>
子孙组件B输入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis', 'pageText', 'pageChangeText'], // 注入 pageText 和 pageChangeText
computed: {
text () {
return this.pageText
}
},
methods: {
change () {
this.pageChangeText(event.currentTarget.value)
}
}
}
</script>
复制代码
效果: 是否是有趣的事发生了?
咱们依旧在孙子B组件上添加computed缓存,但是在改变其余数据的时候,B却不变,
而在改变B的时候,其余数据也会跟着改变
缘由就是 inject 传入的不是响应式数据
有心的小伙伴就会发现,我特别打开了vue devtool
或许你能够再回去看看动图
你就会特别明显发现,inject里的数据一直是不变的!
因此B组件的缓存依赖就不会发生改变,
而相对传入父组件的this(this.$data)是发生改变,其实,他就是响应式数据
固然,我相信,你看到这儿的话就明白了官方文档这句话是什么意思
让咱们再看看源码
// 在vue中的 src/core/instance/inject.js
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
复制代码
不少人看到defineReactive
就认为他就是响应式的!
其实,不对!
小伙伴应该有看到
toggleObserving(false)
// ...
toggleObserving(true)
复制代码
这个方法设置是否为响应式数据的方法
/**
* In some cases we may want to disable observation inside a component's
* update computation.
*/
export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {
shouldObserve = value
}
复制代码
因此说,初始化inject,只是在 vm 下挂载 key 对应普通的值
而,其实想要响应式其实有不少方法
好比用类Java思想,设置一个get/set
仍是以前的例子,略微修改下
去除pageText
,增长pageGetText
方法
代码:
父组件(页面容器组件)
<template>
<div>
页面父组件输入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '内容'
}
},
methods: {
changeText (value) {
this.text = value
},
getText () {
return this.text
}
},
provide () {
return {
pageThis: this,
pageGetText: this.getText,
pageChangeText: this.changeText
}
}
}
</script>
复制代码
子组件A(代码不改动)
<template>
<div>
子组件A输入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
复制代码
子孙组件B
<template>
<div>
子孙组件B输入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis', 'pageGetText', 'pageChangeText'],
computed: {
text () {
return this.pageGetText()
}
},
methods: {
change () {
this.pageChangeText(event.currentTarget.value)
}
}
}
</script>
复制代码
效果图:
为何能够这样用?
来来来,上源码
// 在vue中的 src/core/instance/inject.js
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
复制代码
能够发现,原来!若是是 function
就 provide.call(vm)
若是是函数类型,那就把this指向当前实例!
有了以上两种解决方法,其实对有小伙伴们,解决这个问题不是很难
可是本着帮助,给小伙伴们“脱坑”的思想
仍是但愿小伙伴们注意下 代码:
父组件(容器组件)
<template>
<div>
页面父组件输入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: ''
}
},
created () {
this.text = '内容...'
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this,
pageText: this.text,
pageChangeText: this.changeText
}
}
}
</script>
复制代码
子组件A (不改动代码)
<template>
<div>
子组件A输入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
</script>
复制代码
子孙组件B
<template>
<div>
子孙组件B输入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
export default {
inject: ['pageThis', 'pageText', 'pageChangeText'], // 注入 pageText 和 pageChangeText
computed: {
text () {
return this.pageText
}
},
methods: {
change () {
this.pageChangeText(event.currentTarget.value)
}
}
}
</script>
复制代码
咱们在父容器组件中, 在生命周期函数created
赋值
然而,咱们发现,怎么样,B怎么都是初始值,而不是咱们所赋值
// 父容器组件部分代码
// ...
data () {
return {
text: ''
}
},
created () {
this.text = '内容...'
}
复制代码
缘由是:initInjections
和initProvide
在生命周期函数created
初始化以前
// 在vue中的 src/core/instance/init.js
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
复制代码
那咱们只要用 ① 和 ② 所说的就能够完美搞定(传入响应式)
而,有小伙伴可能以为是否是还有第三种方法,
你这个不是v-model的底层写法吗?
不是能够用v-model吗?
确实能够! 代码: 父组件(容器组件)
<template>
<div>
页面父组件输入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '内容'
}
},
provide () {
return {
pageThis: this
}
}
}
</script>
复制代码
子组件A
<template>
<div>
子组件A输入框:
<input type="text" v-model="pageThis.text">
<BComp />
</div>
</template>
<script>
import BComp from './BComp'
export default {
components: {
BComp
},
inject: ['pageThis']
}
</script>
复制代码
子组件B
<template>
<div>
子孙组件B输入框:
<input type="text" v-model="pageThis.text">
</div>
</template>
<script>
export default {
inject: ['pageThis']
}
</script>
复制代码
不过在业务多数状况下是不能使用v-model,
咱们这个例子中是在input标签
才能使用v-model
那就让我来介绍一个神器mixin
mixin就是混合机制,当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”
其实彻底能够理解为,原来组件混入了你想使用的方法,成为了一个新组件
看看源码?
// 在vue中 src/core/global-api/mixin.js
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
复制代码
// 在vue中 src/core/util/options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
复制代码
工程目录:
代码:
父组件(页面容器组件)
<template>
<div>
页面父组件输入框:
<input type="text" v-model="text">
<a-comp />
</div>
</template>
<script>
import AComp from './components/AComp'
export default {
components: {
AComp
},
data () {
return {
text: '内容'
}
},
methods: {
changeText (value) {
this.text = value
}
},
provide () {
return {
pageThis: this
}
}
}
</script>
复制代码
mixin.js 提取组件A和B的公共代码
// mixin.js
export default {
inject: ['pageThis'],
computed: {
text () {
return this.pageThis.text
}
},
methods: {
change () {
this.pageThis.changeText(event.currentTarget.value)
}
}
}
复制代码
子组件A
<template>
<div>
子组件A输入框:
<input type="text" :value="text" @input="change">
<BComp />
</div>
</template>
<script>
import dataMixin from '../mixin/dataMixin'
import BComp from './BComp'
export default {
components: {
BComp
},
mixins: [dataMixin]
}
</script>
复制代码
子孙组件B
<template>
<div>
子孙组件B输入框:
<input type="text" :value="text" @input="change">
</div>
</template>
<script>
import dataMixin from '../mixin/dataMixin'
export default {
mixins: [dataMixin]
}
</script>
复制代码
这样一使用就能够减小了代码量
固然,减小的同时可能会形成可读性下降~
因此小伙伴能够衡量一下,这也是不错的办法~
本文侧重介绍了 provide/inject 用法,还有常常会入的“坑”
同时,也扯了下 mixin,但其实仍是特别不错的特性,
看完之后,您也能够试试用vuex + mixin
能够说是“数据”与视图解耦!
感谢阅读