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

能夠說是「數據」與視圖解耦!

感謝閱讀

相關文章
相關標籤/搜索