vue中,provide/inject + mixin 有什么妙用?

相信你们在写业务的时候vue

有追求的程序员都会把一个页面分为好几个组件程序员

这样就有解耦性、组件化vuex

虽然这样作当然好,但有时候很是麻烦,api

一个页面有很是多组件,通讯就很是繁琐缓存

好比如下情景,markdown

咱们就能够用provide/inject + mixinide

但看到这,熟悉vue的小伙伴确定脱口而出这样也能够用vuex啊函数

确实,这也官方推荐的组件化

但!我的以为vuex在这种状况下仍是比较“重”的,布局

要写的业务代码也比较多

那不如往下看看provide/inject + mixin的妙用?

1、先说什么是provide/inject?

字面意思,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>
复制代码

来看看效果:

2、使用provide/inject须要注意什么?

① 响应式?非响应?

从效果图能够看出,

不论修改哪一个组件数据,其余组件数据也会跟着改变,

不知道小伙伴有没有发现,这其实有点相似vuex?

vuex将状态放在state里,使用mutation对state修改状态

一样,咱们将页面数据text放在最顶层的父容器组件中,

使用provide暴露改变text的changeText方法

但重点是,咱们注入了页面容器父组件整个this

// 父组件部分代码
  ....
  data () {
    return {
      text: '内容'
    }
  },
  methods: {
    changeText (value) {
      this.text = value
    }
  },
  provide () {
    return {
      pageThis: this
    }
  }
复制代码

与此同时,在子组件使用computed作一个监听缓存

因此,不论修改哪一个组件数据,其余组件数据也会跟着改变

但,咱们稍微把例子改一下就会发生有趣的事情

咱们在父容器组件增长提供pageTextpageChangeText方法

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 对应普通的值

② get和set?

而,其实想要响应式其实有不少方法

好比用类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
  }
}
复制代码

能够发现,原来!若是是 functionprovide.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 = '内容...'
}
复制代码

缘由是:initInjectionsinitProvide在生命周期函数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的底层写法吗?

不是能够用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

3、什么是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
}
复制代码

4、那provide/inject + mixin 有什么妙用?

工程目录:

代码:

父组件(页面容器组件)

<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>
复制代码

这样一使用就能够减小了代码量

固然,减小的同时可能会形成可读性下降~

因此小伙伴能够衡量一下,这也是不错的办法~

5、总结一下!

本文侧重介绍了 provide/inject 用法,还有常常会入的“坑”

同时,也扯了下 mixin,但其实仍是特别不错的特性,

看完之后,您也能够试试用vuex + mixin

能够说是“数据”与视图解耦!

感谢阅读

相关文章
相关标签/搜索