[使用 Weex 和 Vue 開發原生應用] 5 使用 Vuex

系列文章的目錄在 ? 這裏javascript

什麼是 Vuex ?

Vuex 官方文檔html

Vuex 是一個專爲 Vue.js 應用程序開發的 狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。前端

Vuex 的核心工做是狀態管理,主要包含了 State, View Actions 這三部分,組成了一個簡單的「單項數據流」,避免了管理多狀態形成的數據不一致問題。vue

Data Flow

Vuex 是專門爲 Vue.js 設計的狀態管理庫,貼合 Vue 自己的數據更新特性,可以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。並且能夠是以 插件(plugin)的形式提供,安裝完成以後能夠很方便的在 VueComponent 實例中獲取到全局狀態。模塊之間的關係和操做以下圖所示:java

Vuex Structure

  • State: 應用的單一狀態樹。一般一個應用裏只保存同一份狀態(組件仍然能夠保有局部狀態),爲的是便於多個組件之間的狀態同步。狀態的更新將會觸發指定組件的從新渲染。git

  • Action: 描述組件觸發的操做。它能夠經過 commit 產生 mutation,是對邏輯的封裝,組件只須要派發 Action 而不用關心數據究竟是如何更新的。github

  • Mutations: 描述狀態應該如何更新。只有提交 mutation 才能夠更新 Store 中的狀態,它是對數據操做的封裝,定義瞭如何更新狀態數據。web

以上只是對 Vuex 的概述,想要了解詳細的原理和用法,仍是得看官方文檔vue-router

怎麼在 Weex 裏用 Vuex

Vuex 是個狀態管理的庫,用的都是 javascript 自己的語法特性,是與平臺無關的,因此它能夠徹底正常的用在 Weex 裏。vuex

不過由於 Vue.js 框架代碼已經集成在 WeexSDK (0.9.5 以上)中,因此你不須要再引入一遍 Vue 。另外由於 Vuex 在瀏覽器環境下會自動註冊,只須要在非 Web 環境下注冊 Vuex 插件便可,重複註冊的話會拋出警告的。引入 Vuex 的代碼以下:

// import Vue from 'vue'
import Vuex from 'vuex'

// Vuex is auto installed on the web
if (WXEnvironment.platform !== 'Web') {
  Vue.use(Vuex)
}

註冊成功以後,全部 Vuex 的特性都能在 Weex 裏使用! 具體用法以官方文檔爲準。

使用 Vuex 的例子

建立 Store

首先要建立全局惟一的 Store 對象,包含了惟一的狀態樹和一些操做。

const store = new Vuex.Store({
  state: {
    count: 0
  },

  mutations: {
    increment (state) {
      state.count++
    }
  }
})

除了 statemutations 之外,還能夠傳入 actionsgettersmodules 這些屬性的關係和上邊圖中一一對應。參考其 API 文檔瞭解更詳細的用法。在 weex-hackernews 項目的 src/store/index.js 中也有一個更復雜的例子。

而後在建立實例的時候傳入 store 對象,這樣 Store 就和組件創建了聯繫,每一個組件均可以經過 this.$store 的方式獲取到 Store 中的狀態和操做。

import App from 'path/to/App.vue'
import store from 'path/to/store.js'

App.el = '#root'
App.store = store

new Vue(App)

添加 Actions 和 Mutations

光有數據是不行的,還得定義 觸發數據修改的行爲(Actions) 和 對數據的操做(Mutations)。以 weex-hackernews 裏的加載用戶數據爲例,能夠簡化成下邊的代碼:

// 引入網絡操做的接口
import { fetchUser } from './fetch'

const store = new Vuex.Store({
  state: {
    users: {},
  },

  actions: {
    FETCH_USER ({ commit }, { id }) {
      // 獲取新的用戶數據,而後提交給 mutation
      return fetchUser(id).then(user => commit('SET_USER', { user }))
    }
  },

  mutations: {
    SET_USER (state, { user }) {
      // 修改 users 中的數據,而且觸發界面更新
      Vue.set(state.users, user.id, user)
    }
  }
})

在上邊的代碼中,state 裏有一個 user 對象,全部須要用戶數據的地方都將從這個變量中獲取,全部對用戶數據的修改實際上也都是改的這個變量。這是全局惟一狀態的意義。

而後代碼裏定義了 FETCH_USER 的 action,和 SET_USER 的 mutation ,注意二者的差異。真正修改 user 數據的是 SET_USER 這個 mutation,也只有 mutation 能修改 state 中的數據。FETCH_USER 負責獲取新數據而後派發 mutation,它調用了 fetchUser 獲取用戶數據,而後經過把這個新數據「提交」給 SET_USER 這個 mutation;而後在 mutation 裏執行數據更新的操做,Vue.set 這個方法會觸發更新 state.users 裏綁定的界面元素。

使用 Getters

Getters 官方文檔

Getters 相似於 Vue 組件裏的 computed 屬性,能夠根據現有的基礎狀態作運算,而後返回一個新的值。把取值過程寫成 Getter 是一種惰性求值,也減小了狀態同步的負擔。

舉個例子,假如你想渲染一個列表中的一組數據,又想渲染這組數據的總和,若是再加一條「數據總和」的屬性的話,在列表更新後還得手動更新「數據總和」這條屬性,比較麻煩也容易出錯。這種狀況下就很適合寫成一個 Getter。

const store = new Vuex.Store({
  state: {
    lists: [
      { count: 4 },
      { count: 8 },
      { count: 3 },
      { count: 9 }
    ],
  },

  getters: {
    summary ({ lists }) {
      return lists.reduce((sum, curr) => sum + curr.count, 0)
    }
  }
})

上邊代碼中的 summary 就是一個對 lists 數組求和的 Getter。在實際使用中,組件裏能夠經過 this.$store.state.lists 獲取 lists 列表數據,能夠經過 this.$store.getters.summary 獲取數據總和,更新列表的時候數據總和也會自動更新。

在項目裏的實際應用場景

在 weex-hackernews 的項目的 src/store/index.js 文件裏定義了 activeIds 的 Getter,用來獲取當前首屏 feed 列表中須要展現的數據 ID。而後還定義了 activeItems,在其中有調用了 activeIds,會根據活躍的 ID 獲取相應的數據對象。

{
  //...

  getters: {
    // ids of the items that should be currently displayed based on
    // current list type and current pagination
    activeIds (state) {
      const { activeType, lists, counts } = state
      return activeType ? lists[activeType].slice(0, counts[activeType]) : []
    },

    // items that should be currently displayed.
    // this Array may not be fully fetched.
    activeItems (state, getters) {
      return getters.activeIds.map(id => state.items[id]).filter(_ => _)
    }
  }
}

而後在 src/views/StoriesView.vue 這個文件裏定義了一個 computed 屬性,從 Store 中獲取列表須要展現的數據。

{
  //...

  computed: {
    stories () {
      return this.$store.getters.activeItems
    }
  }
}

StoriesView.vue 的模板中根據 stories 這個數據循環建立 <story> 組件。

<list>
  <cell v-for="story in stories" :key="story.id">
    <story :story="story"></story>
  </cell>
</list>

最佳實踐 ?

如下都是我的觀點。

Vuex 衍生自 Flux 架構,用來管理應用的狀態,強調單向數據流和全局惟一狀態。對於一些數據狀態複雜,並且又自上而下實現了組件化的應用裏,能發揮很大做用。狀態的更新大概能簡化爲 nextState = f(state, action),有一種函數式的感受,能夠節約邏輯。

雖然 Vuex 與其餘技術沒有耦合關係,可是一般都是用在單頁應用(SPA)裏,還常常搭配着 vue-router 使用。不過我在《【使用 Weex 和 Vue 開發原生應用】 2 編寫獨立頁面》 這篇文章裏說過,Weex 的實例在 Web 上是和「瀏覽器頁籤」的概念相對應的,一般一個 Weex 實例就是一個「頁面」,也就是說,Weex 的設計是個「多頁應用」,是多實例的。在 Weex 中使用 Vuex,它的做用域是實例級別的,不一樣頁面(實例)之間是不能經過 Vuex 共享狀態的。

Weex 畢竟渲染的是原生界面,雖然語法上貼近 Web,可是在一些基本概念上和 Native 更近一些。「單頁應用」、「單向數據流」這些概念主要是在前端裏比較流行,Weex 只是一個 SDK,在開發原生應用的時候,頁面跳轉策略這類問題,我以爲仍是應該以客戶端自身的架構設計爲主。

weex-hackernews 這個項目是爲了驗證 Vuex 和 vue-router 接入的可能性,並不必定是最佳實踐。

我以爲既然 Weex 在原生端是多實例的,就未必適合寫單頁應用。即便像 Vuex 這種相對獨立的狀態管理的庫,在 「不一樣頁面是不一樣的 Weex 實例」 這種前提下,就須要根據 App 自身的技術特性,
再考慮一下應不該該使用。

關於單頁應用,會在《使用 vue-router》裏有更多討論。

相關文章
相關標籤/搜索