你也許不知道的Vuejs - 狀態管理

yugasun發表於2018-05-22

by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但需要保留原作者和出處。

前言

小擼怡情,大擼傷身,強擼灰飛煙滅,恩,大概是本人程式碼擼多了的緣故,我的女朋友就像 復聯3 結局一樣,灰飛煙滅了(分手了,當然原因不只是這麼簡單,人總得給自己的失敗找個藉口,不是嗎......)。 所以從3月份開始,心情一直很差,文章也更新的越來越慢。恰巧今天是 520,從一大早起來開始,朋友圈就異常的熱鬧,有秀恩愛的,有秀悲傷地,還有少數微商依舊堅挺的發著廣告。正是這些堅強的微商讓我明白了,無論別人如何秀,堅定自己的目標繼續做下去就是了,也就在這一天我徹底的從失戀中走了出來......

好了,言歸正傳,今天來聊聊 Vue.js 中的狀態管理,也許一提到狀態管理,大家首先想到的就是 vuex,但是如果你的應用夠簡單,其實是不需要使用 vuex 的,反而會讓你的專案變得繁瑣起來。

本文總結了4種實現元件間狀態的共享的方法:

  1. 共享物件
  2. mixins 混入
  3. 全域性的 Event Bus
  4. Vuex

下面我將分別實現一遍。

共享物件

在日常工作中,我們會經常提到物件的拷貝,由於:

Javascript 語言的物件在賦值的時候,只是將其引用地址賦值給了一個新的變數而已,所以在改變新物件屬性是,會同時改變原物件屬性。

所以大家在寫程式碼的時候會盡量的避免此類事情的發生,但是也正是因為這個特性,我們可以實現 Vuejs 中的狀態共享。

思路如下:

首先初始化一個全域性物件,維護著全域性資料,然後將其注入到所有用到的元件中,那麼在組建內改變某個屬性值,其他元件也就同步更新了。

明白了原理就很好實現了。同樣的通過 vue-cli 初始化我們的專案模板,然後新建 src/state.js 檔案:

export default {
  msg: 'Hello world',
};
複製程式碼

然後,分別建立需要引用的元件:

<!-- comp1 -->
<template>
  <div class="comp1">
    <h1>Component 1</h1>
    <input type="text" v-model="state.msg">
  </div>
</template>
<script>
import state from '../state';

export default {
  name: 'comp1',
  data() {
    return {
      state,
    };
  },
};
</script>

<!-- comp2 -->
<template>
  <div class="comp2">
    <h1>Component 2</h1>
    <input type="text" v-model="state.msg">
  </div>
</template>
<script>
import state from '../state';

export default {
  name: 'comp2',
  data() {
    return {
      state,
    };
  },
};
</script>
複製程式碼

然後在 src/App.vue 檔案中使用 comp1comp2:

<template>
  <div class="app">
    <comp1></comp1>
    <comp2></comp2>
  </div>
</template>
<script>
import comp1 from './components/comp1';
import comp2 from './components/comp2';

export default {
  name: 'App',
  components: {
    comp1,
    comp2,
  },
};
</script>
複製程式碼

ok,這樣就完成了,執行專案,你會發現,改變 comp1 中的輸入框的值,comp2 也同步發生改變,反過來也是一樣的。至於原理,上面已經說過了,這裡就不再贅述,趕緊動手實現下吧。

最終實現程式碼

mixins - 混入

我們先來看看官方對 mixins 介紹:

混入 (mixins) 是一種分發 Vue 元件中可複用功能的非常靈活的方式。混入物件可以包含任意元件選項。當元件使用混入物件時,所有混入物件的選項將被混入該元件本身的選項。

所以我們也可以通過 mixins 的方式,將我的的全域性狀態 state 通過 mixins 的方式引入到各個元件中,當然資料其實還是通過 物件引用 實現的。

我們修改一下 src/state.js

const state = {
  msg: 'Hello world',
};

export default {
  data() {
    return {
      state,
    };
  },
};
複製程式碼

然後 comp1 中引入:

<template>
  <div class="comp1">
    <h1>Component 1</h1>
    <input type="text" v-model="state.msg">
  </div>
</template>
<script>
import state from '../state';

export default {
  mixins: [state],
  name: 'comp1',
  data() {
    return {};
  },
};
</script>
複製程式碼

comp2 也是相同方法引入就行了。

其實,通過 mixins 方式跟 方法1 基本差不多,只不過相對於 方法1 的好處就是,在使用元件內,將狀態資料屬性跟 data 進行了程式碼上隔離(實際上還是混入到了 data 屬性中)。

最終實現程式碼

全域性的事件匯流排(Event Bus)

我們知道 Vuejs 有一對API $on/$emit,是用來自定義事件監聽和觸發的,而且在觸發的時候可以攜帶資料。這樣我們就可以通過建立一個全域性的 Vue 例項 $bus,然後在各個元件中進行相關事件的觸發和監聽,資料流的傳遞和共享,這也就是大家常說的 釋出訂閱模式

熟悉 $on/$emit API的同學,應該很容易就可以實現了,先上程式碼:

簡潔版

src/state.js:

import Vue from 'vue';

const EventBus = new Vue();

export default EventBus;
複製程式碼

src/components/comp1.vue:

<template>
  <div class="comp1">
    Change by event: <input type="text" v-model="msg" @change="handleChange">
  </div>
</template>
<script>
export default {
  name: 'comp1',
  data() {
    return {
      msg: this.$bus.msg,
    };
  },
  methods: {
    handleChange(e) {
      const newVal = e.target.value;
      this.$bus.$emit('msg-change', { value: newVal });
    },
  },
  created() {
    this.$bus.$on('msg-change', (payload) => {
      this.msg = payload.value;
    });
  },
};
</script>
複製程式碼

src/components/comp2 程式碼同 src/components/comp1

好了,先執行一下,同樣達到了我們想要的效果。上面程式碼中,我們先在 src/state.js 中建立了一個 Vue 例項,然後在元件中 created 的時候監聽 msg-change 事件,在 input 值改變的時候,觸發 msg-change 事件,這樣就實現了 comp1comp2msg 資料共享。

升級版

我們知道 $bus 實際上也是個物件,那麼我們其實也可以通過給他新增 data 屬性來進行資料共享的,現在將上面程式碼再稍作修改:

src/state.js:

import Vue from 'vue';

const EventBus = new Vue({
  data: {
    msg: 'Hello world',
  },
});

export default EventBus;
複製程式碼

然後分別在 src/components/comp1src/components/comp1 中新增資料引用:

<!-- ... -->
Change by object reference: <input type="text" v-model="$bus.msg"><br/>
<!-- ... -->
複製程式碼

然後試著改變新新增的輸入框資料,你會發現,新新增的兩個輸入框,資料也是同步更新的。

細心的同學,會發現有個問題,雖然新新增的兩個輸入框同步了,但是之前的兩個事件觸發的輸入框並未同步更新。因為我們只是單純地修改了物件屬性,並未觸發 msg-change 事件,所以還需要在 $bus 中新增 watcher,在 msg 變化時,觸發 msg-change 事件;反過來,$bus自身需要監聽 msg-change 事件,在觸發的時候,修改自身 msg 的值就可以了。

我們再修改下 src/state.js 程式碼:

import Vue from 'vue';

const EventBus = new Vue({
  data: {
    msg: 'Hello world',
  },
  watch: {
    // 這裡為了實現物件引用監聽,然後出發change事件,實現狀態同步
    msg(val) {
      this.$emit('msg-change', { value: val });
    },
  },
});

EventBus.$on('msg-change', (payload) => {
  console.log(`Msg has changed to ${payload.value}`);
  EventBus.msg = payload.value;
});

export default EventBus;
複製程式碼

當然我們這裡只是為了demo,實際開發過程中,自己一定要清楚,這裡其實有兩個資料管道,一個是 釋出/訂閱 可以理解為單工(單向資料流)模式,一個是 物件共享 也就是 方法2,可以理解為雙工(雙向資料流)模式。首先你需要理解清楚 當前需要共享的資料是單向還是雙向,然後根據場景靈活運用。

最終實現程式碼

Vuex

至於 Vuex 的使用,本文就不再實現了,官方文件已經寫得很清楚,請細心閱讀 官方文章

總結

本文寫了這麼多其實想告訴大家,對於 Vuejs 狀態管理,我們不僅僅只有 vuex,框架或工具的選擇不是固定的,實際開發中,可以多嘗試,找到最適合的架構才是最好的。引用 Vue 的作者的話就是:

如果您不打算開發大型單頁應用,使用 Vuex 可能是繁瑣冗餘的。確實是如此——如果您的應用夠簡單,您最好不要使用 Vuex。一個簡單的 global event bus 就足夠您所需了。但是,如果您需要構建一箇中大型單頁應用,您很可能會考慮如何更好地在元件外部管理狀態,Vuex 將會成為自然而然的選擇。

專題目錄

You-May-Not-Know-Vuejs

相關文章