最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue中,provide/inject + mixin 有什么妙用?

    正文概述 掘金(阿远Carry)   2020-12-15   531

    相信大家在写业务的时候

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

    这样就有解耦性、组件化

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

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

    比如以下情景,

    我们就可以用provide/inject + mixin

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

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

    确实,这也官方推荐的

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

    要写的业务代码也比较多

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

    一、先说什么是provide/inject?

    字面意思,provide是提供的意思、inject是注入的意思,

    事实上也是这样,provide 是在父组件使用、inject是在后代组件使用

    这里的后代组件的意思其实就是父组件包含的子、子孙组件,不管你层级多深都是后代组件

    例如

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

    在这里,我们可以把整个页面容器作为父组件,提供数据使用provide

    在父组件中所包含的子组件、子孙组件可以使用inject获取父组件提供的数据

    我们在这选取图中上半部分作为示例代码,让小伙伴们看得更清晰

    项目文件所的层级结构:

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

    页面布局结构:

    vue中,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>
    

    子组件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>
    

    来看看效果:

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

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

    效果: vue中,provide/inject + mixin 有什么妙用? 是不是有趣的事发生了?

    我们依旧在孙子B组件上添加computed缓存,可是在改变其他数据的时候,B却不变,

    而在改变B的时候,其他数据也会跟着改变

    原因就是 inject 传入的不是响应式数据

    有心的小伙伴就会发现,我特别打开了vue devtool

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

    或许你可以再回去看看动图

    你就会特别明显发现,inject里的数据一直是不变的!

    所以B组件的缓存依赖就不会发生改变,

    而相对传入父组件的this(this.$data)是发生改变,其实,他就是响应式数据

    当然,我相信,你看到这儿的话就明白了官方文档这句话是什么意思

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

    让我们再看看源码

    // 在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中,provide/inject + mixin 有什么妙用?

    为什么可以这样用?

    来来来,上源码

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

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

    我们在父容器组件中, 在生命周期函数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中,provide/inject + 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 有什么妙用?

    工程目录:

    vue中,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

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

    感谢阅读


    起源地下载网 » vue中,provide/inject + mixin 有什么妙用?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元