劍走偏鋒之Vue元件通訊(二)——利用provide / inject屬性構建全域性狀態管理

空城裡發表於2019-04-06

平常我們的全域性狀態管理通通都交給Vuex來,Vuex也的確是非常好的工具,並被官方作為指定的狀態管理器。今天給大家介紹有個新的思路進行狀態管理。可能大家對provide / inject這對api有點陌生,的確也是,平常開發中我們基本也不會用到。

劍走偏鋒之Vue元件通訊(二)——利用provide / inject屬性構建全域性狀態管理
官方api有這樣一句話:這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在起上下游關係成立的時間裡始終生效。

看圖片可以知道,provide期望傳入一個Object,而inject可以接受的值包括(1)一個字串陣列 (2)一個物件。程式碼如下:(最簡單的寫法)

// 父元件 App.vue
export default {
  name: 'App',
  provide: {
    parentKey: 'aaa'
  }
 }

複製程式碼
// 子元件 chidlren.vue
export default {
  inject: ['parentKey'],
  created () {
    console.log(this.parentKey)
  }
}
複製程式碼

在列印臺可以看到aaa被輸出了出來,既然inject期望值是個陣列,那應該也可以這樣

// 父元件 App.vue
export default {
  name: 'App',
  provide: {
    parentKey: { a: 'haha' },
    parentKey2: [1, 2]
  }
 }

複製程式碼
// 子元件 chidlren.vue
export default {
  inject: ['parentKey', 'parentKey2'],
  created () {
    console.log(this.parentKey, this.parentKey2)
  }
}
複製程式碼

之前我們傳遞是個字串,發現其實傳遞什麼都可以,對應的物件和陣列都列印出來了。 如果僅僅只是這樣,好像也沒什麼。看到api有這樣一句話:

提示:provideinject繫結並不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的物件,那麼其物件的屬性還是可響應的。

我們可以試下:

// 父元件 App.vue
<template>
  <div class="app">
    <router-view></router-view>
    <button @click="changeKey"> 按鈕</button>
  </div>
</template>
export default {
provide () {
    return {
      parentKey: this.key
    }
  },
  methods: {
    changeKey () {
      this.key.push(2)
    }
  },
  data () {
    return {
      key: [1]
    }
  }
 }
複製程式碼
// 子元件 chidlren.vue
export default {
  inject: ['parentKey'],
  created () {
    let afterKey = JSON.stringify(this.parentKey)
    let timer = setInterval(() => {
      console.log(this.parentKey)
      if (afterKey !== JSON.stringify(this.parentKey)) clearInterval(timer)
    }, 1000)
  }
 }
複製程式碼

最後可以看見當點選按鈕之後,在子元件chidlren.vue中輸出的值變化了,定時器也停止了。

這裡面有幾個要注意的地方 (1). 這時候provide必須為一個工廠函式,如果你是這樣寫的provide: {parentKey: this.key}也就是跟之前這種寫法,控制檯會報未定義key的錯誤 (2)不要傳字串之類的值過去,比如:parentKey: this.key 而你在data中是這樣定義keykey: 'aaa,提示下,他並不是一個可響應的資料,你可能會說,那不對啊,我在當前元件改變這個值,模板裡輸出的也會變啊。這時候其實可響應的值是this當前元件 (3)大家不要試圖替換陣列來響應(為什麼說這條,因為不注意我也犯了這個錯誤 :) (4)一旦注入了某個資料,比如上面示例中的 parentKey,那這個元件中就不能再宣告 parentKey 這個資料了,因為它已經被父級佔有)

注意:在變異 (不是替換) 物件或陣列時,舊值將與新值相同,因為它們的引用指向同一個物件/陣列。Vue 不會保留變異之前值的副本。(來源Vue官網)

好像說了這麼久,沒說到與標題相關的。:)-- 接下來久介紹下構建全域性狀態管理的思路

// 父元件(也可以說是根元件) App.vue
export default {
  name: 'App',
  provide () {
    return {
      app: this
    }
  },
  methods: {
    changeKey () {
      this.key.push(2)
    }
  },
  data () {
    return {
      key: [1],
      transitionName: 'slide-right'
    }
  }
}

複製程式碼
// 子元件 chidlren.vue
export default {
  inject: ['app'],
  created () {
    console.log(this.app)
    console.log(this.app.key)
    this.app.changeKey()
  }
}
複製程式碼

這樣你就可以拿到根節點app所有的東西了,還可以訪問他的方法。以此將根節點作為狀態管理器。 如果你覺得這樣狀態多起來,十分難管理,你可以使用混合mixins,將不同的邏輯分開到不同的 js 檔案裡。當然這種事是仁者見仁智者見智,你可以使用this.$root訪問根節點,還可以掛載在Vue.prototype的原型上。哈哈,這些方法是不是感覺有點邪門歪道的感覺。所以官網有這麼一句話:

provide 和 inject 主要為高階外掛/元件庫提供用例。並不推薦直接用於應用程式程式碼中。

給大家介紹下這個網站dev.iviewui.com,這個網站就是使用此方法做出來的,這樣減少了Vuex的依賴使用vue本身作為狀態管理器,當然,如果你的網站很大,還是建議使用Vuex

這對api還有很多東西可以挖掘的,比如使用ES2015的Symbol作為唯一值,inject中可以使用default作為預設值等等。用法還是同學們去看看官網。這次久到此為止,清明放假了,還是休息休息。:)

如有錯誤,歡迎指出。

相關文章