Vue.js簡單的狀態管理和 Vuex的幾個核心概念使用。



由於狀態零散地分布在許多組件和組件之間的交互中,大型應用復雜度也經常逐漸增長。

  • 如果多層組件嵌套使用,傳遞prop,和事件emit。都很不方便。
  • 不方便對數據的修改進行歷史記錄。影響后續的調試!

為了解決這個問題,Vue 提供 vuex

vuex 甚至集成到 vue-devtools,無需配置即可進行時光旅行調試

 


 


Vuex 

 

前置知識:理解什么是component!

組件就是函數。編程就是通過組織小的函數們來解決問題!類似組件!

於是問題就成為:如何傳遞arguments, 組件是怎樣和它的環境互動的?

就像parameters和返回函數中的值。

 

在Vue中我們使用props來傳遞data到組件和組件事件,來和周邊環境交互。

prop例子

message prop從父組件傳遞到子組件,通過一個template。

//childrenComponent
{
  props: ['message'],
  created(){
    console.log(message)  
  },
  template: '<div>{{ message }}</div>'
}

// Parent Component
{
  template: `<child-component message="'hello'"></child-component>`,
  components: { childrenComponent }
}

 

event例子(見鏈接) 主要使用$emit方法和v-on

 

一個問題: 

這是很好的移動數據的辦法。只是有一個組件的問題:當嵌套3+層時,交互成了困難。

你不得不傳遞數據從組件A到B,然后從B到C,等等。 反向events, C emits to B, B to A等等。

 

於是使用state進行狀態管理出現了,但這有缺陷,所以出現了Vue, 往后看👇:

 

簡單狀態管理

經常被忽略的是,Vue 應用中原始數據對象的實際來源 。

當訪問數據對象時,一個 Vue 實例只是簡單的代理訪問。

所以,如果你有一處需要被多個實例間共享的狀態,可以簡單地通過維護一份數據來實現共享:

const sourceOfTruth = {}

const vmA = new Vue({ data: sourceOfTruth }) const vmB = new Vue({ data: sourceOfTruth })

 子組件的實例可以通過this.$root.$data來訪問sourceOfTruth.

 

問題出現:任何對數據的改變,不會留下變更過的記錄。這對調試是一個噩夢!!

因此可以使用store模式:

var store = {
  debug: true, state: { message: 'Hello' }, setMessageAction(newVaule) { if (this.debug) console.log('setMessageAction triggered with', newValue) this.state.message = newValue }, clearMessageAction() { if (this.debug) console.log('clearMessageAction triggered') this.state.message = '' } }

 store中的state的改變,都放置在store自身的函數去管理!這就是集中方式的狀態管理!

 當錯誤出現時, 就有了log來記錄bug出現之前發生了什么。

 

 此外,每個實例/組件,可以有自己的私有狀態:

var vma = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

var vmb = new Vue({ data: { privateState: {}, sharedState: store.state } })

 

接下來的延伸約定:

組件不應該直接修改屬於store實例的state ,而應該執行store實例內的action函數來dispatch事件通知store去改變。

這樣約定的好處:

可以記錄所有store中發生的state的改變,同時實現能做到記錄變更mutation, 保存狀態快照,歷史回滾/時光旅行的先進的調試工具:vuex.


 

 

Vuex

Vuex用於集中管理state。或者說是集中式的狀態管理Vue依賴庫。 Vuex所做的不是在組件內協調state的數據(coordinate state's data in components), 而是,在一個巨大的state object內,協調coordinate它們(data)。 任何組件需要一個portion of the state, 就傳遞它們。

這個state或大或小由程序決定,不過最開始就是一個空對象{ }。

 

安裝

npm install vuex  
//或者進入ui,在網頁上手動添加。

//然后配置到entry point,或自建一個store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//如果自建store.js, 加上:
export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {}
});

 

 

Vuex是一個全局對象,還有2個額外的功能:

  1. Vuex的state儲存是響應式的。
  2. 不可以直接改變store中的state。需要使用(commit)mutation.

簡單計數:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

store.commit('increment')
store.commit('increment')

store.state.count  //輸出2

 

 


 

核心概念:

  • State: 保存組件的共享的狀態。
  • Getter:用於state的延伸,就是computed計算屬性的功能。
  • Mutation: 唯一可以改變store的狀態的方法:(commit)mutation
  • Action: 只能提交Mutation,可以包括async操作。
  • Module: 解決單一狀態樹,導致對象過於龐大的問題。將store分割為多個module模塊。相當於是嵌套子模塊。

State

單一狀態樹,一個對象包含全部應用層級狀態。 每個應用只有一個store實例。

Vuex通過store選項,把狀態從根組件‘注入’到子組件中。


//
輔助函數mapState的用法 //在子組件中,無需每個state都聲明computed屬性,使用輔助函數,幫助我們生成計算屬性! //計算屬性? //如果想要在子組件得到store實例中的狀態,最簡單的方法就是在計算屬性中返回某個狀態
const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } }

 

store.state.count變化的時候,會從新求取計算屬性, 並觸發更新相關的DOM。

但是,這種模式有個缺陷!!❌!! 

即簡單使用store實例,集中管理的缺陷

vue使用的是模塊化的構建方法,每個需要用到state的組件都會頻繁的導入store.state。過於頻繁降低效能。

Vuex通過store選項,提供了一種機制將state從根組件‘注入’到每個子組件中(調用Vue.use(Vuex))

const app = new Vue({
  el: '#app',
  // 把 store 對象提供給 “store” 選項,這可以把 store 的實例注入所有的子組件
  store,

 

子組件可以通過this.$store來訪問state。

例子:

根實例vm, 子組件Counter。

vm中注冊了store選項,獲得了store對象,然后把store實例注入到Counter子組件內。

Counter使用this.$store.state.count訪問到store實例。

(我的理解是每個子組件,都有一個store實例,這樣子組件內部可以反復調用這個實例狀態,無需再從store對象那里取數據)

Counter還可以使用this.$store.commit("increment")來修改store對象的state。

(如果是修改state,則調用store對象的action修改它的state)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

const  Counter = {
  template: `<div>count: {{ count }}</div>`,
  computed: {
    count() {
      return this.$store.state.count
    }
  }
}

var vm = new Vue({
  el: "#app",
 store,
  components: { Counter },
})

 

mapState 輔助函數

當一個組件需要獲取多個狀態時候,將這些狀態都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState 輔助函數幫助我們生成計算屬性,讓你少按幾次鍵:

import { mapState} from 'vuex'

export default{
  computed: mapState({
     count: state => state.count
     countAlias: 'count'

     //為了能夠使用 `this` 獲取局部狀態,必須使用常規函數
     countPlusLocalState(state) {
        return state.count + this.localCount
     }

 })
}

 

如果映射的計算屬性的名字和state的子節點名字相同時,可以給mapState傳遞一個string數組

We can also pass a string array to mapState when the name of a mapped computed property is the same as a state sub tree name.

computed: mapState([
  // 映射 this.count 為 store.state.count
  // this是指子組件實例
  'count'
])

 

 

對象展開符號...

使用..., 簡化了上面的代碼

computed: {
  localComputed() { /* ... */},
  ...mapState({
    // ...
  })
}

 

 

 


 

Getter

一個簡單的演示:mycode pen:https://codepen.io/chentianwei411/pen/JmyeGq

 

針對state的派生的狀態的操作:

(store的計算屬性),getter返回值根據它的依賴被緩存起來,只當它的依賴值改變了才會被重新計算。

Getter接受state為第一個參數

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

 

通過屬性訪問:

store.getters對象中的函數可以訪問:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

 

Getter可以接受其他getter作為第二個參數:我的理解,就是store.getters對象被當作第二個參數

const mystore = new Vuex.Store({
  //略。。
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
 doneTodosCount: (state, getters) => { return getters.doneTodos.length }
    
  }
})

 

我們可以在任何組件中使用getters中的函數:

const  Counter = {
  template: `<div>count: {{ count }}</div>`,
  computed: {
    count() {
      return this.$store.getters.doneTodosCount
    }
  }
}

var vm = new Vue({
  el: "#app",
  store: mystore
  components: { Counter },
})

 

通過方法訪問:返回的是一個函數 (state) => {  return function(){} }

  getters: {
    //..略
    getTodoById: (state) => (id) => {
      return state.todos.find(todo => todo.id === id)
    }
  },

mystore.getters.getTodoById(2)   
//->返回todo對象一個。{ id: 2, text: '...', done: false }

 


 

Mutation

Mycodepen:2個演示:

https://codepen.io/chentianwei411/pen/PyKyNr

mutations屬性中的函數可以接受2個參數,

  1. 第一個參數是state.
  2. 第2個參數最好是一個包含record的對象
  mutations: {//添加參數payload,約定它是對象。
    incrementPayload (state, payload) {
      state.count += payload.amount
    }
  }

 

 

Mutation必須是同步函數,不能說異步函數,因為當mutation觸發時,回調函數沒能被調用,那么就不能記錄追蹤回調函數中進行的狀態的改變。

使用action處理異步操作:


 

 

Action


Action 類似於 mutation,不同在於:

    • Action 提交的是 mutation,而不是直接變更狀態。
    • Action 可以包含任意異步操作
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
 actions: { increment (context) { setTimeout( () => { context.commit('increment') }, 1000) } }
}) 

 

 

Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 :

  • context.commit 提交一個 mutation。
  • context.state
  • context.getters

當我們在之后介紹到 Modules 時,你就知道 context 對象為什么不是 store 實例本身了。

 

dispatch Action

不受必須同步執行的限制。

store.dispatch('increment')

 

 

同樣可以使用載荷,對象的方式分發:

我的理解:action中異步調用mutations中的函數。actions和mutations中的函數名字一一對應。

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
   //...略...
    incrementPayload (state, payload) {
      state.count += payload.amount 
    }
  },
  actions: {
   //...略... incrementPayload (context, payload) {
      setTimeout( () => {
        context.commit('incrementPayload', payload)
      }, 1000)
    }
  }
}) 

 

 

在組件中分發Action(未看)

組合 Action(未看)

 


 

項目結構:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API請求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我們組裝模塊並導出 store 的地方
    ├── actions.js        # 根級別的 action
    ├── mutations.js      # 根級別的 mutation
    └── modules
        ├── cart.js       # 購物車模塊
        └── products.js   # 產品模塊


 

 

插件

Vuex.Store的實例方法:

subscribe(handler: Function): Function

hander會在每個mutation完成后調用,接收mutation和經過mutation后的state作為參數:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM