稀土掘金 稀土掘金

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

相信大家在写业务的时候

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

这样就有解耦性、组件化

虽然这样做固然好,但有时候非常麻烦,

一个页面有非常多组件,通信就非常繁琐

比如以下情景,

我们就可以用provide/inject + mixin

但看到这,熟悉vue的小伙伴肯定脱口而出这样也可以用vuex啊

确实,这也官方推荐的

但!个人觉得vuex在这种情况下还是比较“重”的,

要写的业务代码也比较多

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

一、先说什么是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>

来看看效果:

二、使用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

三、什么是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
}

四、那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>

这样一使用就可以减少了代码量

当然,减少的同时可能会造成可读性降低~

所以小伙伴可以衡量一下,这也是不错的办法~

五、总结一下!

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

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

看完以后,您也可以试试用vuex + mixin

可以说是“数据”与视图解耦!

感谢阅读

玻璃钢生产厂家蛋型玻璃钢花盆价格上海梳头人物玻璃钢雕塑扬州仿铜玻璃钢雕塑徐汇区玻璃钢雕塑价格栖霞春季商场美陈玻璃钢红军主题雕塑常州玻璃钢雕塑租赁泉州玻璃钢雕塑价格表惠州玻璃钢卡通雕塑玻璃钢动物雕塑供销张家口玻璃钢雕塑厂家玻璃钢雕塑卡通性价比哪个好贵阳玻璃钢卡通动物雕塑哪有玻璃钢雕塑设计与制作广州玻璃钢雕塑设计厂家铜陵广场玻璃钢雕塑价格北京大型商场美陈市场价和龙玻璃钢气球雕塑浙江商场创意商业美陈风格苏州大型玻璃钢雕塑工厂北京常用商场美陈销售公司咸阳玻璃钢雕塑设计玻璃钢雕塑能放室外多少年多彩玻璃钢雕塑优势鹤壁校园不锈钢玻璃钢卡通雕塑云南景观玻璃钢雕塑联系方式四川商场美陈批发商场美陈太空人潮汕蓝色玻璃钢花盆花钵武威玻璃钢植物雕塑安装香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化