第27-30講、Vuex教程-Vuex 中的 State Mutation Getters mapGetters Actions Modules

一、Vuex

Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式

官網(wǎng):https://next.vuex.vuejs.org/

主要功能:

1、vuex可以實現(xiàn)vue不同組件之間的狀態(tài)共享 (解決了不同組件之間的數(shù)據(jù)共享)

2、可以實現(xiàn)組件里面數(shù)據(jù)的持久化。

Vuex的幾個核心概念:

State

Getters

Mutations

Actions

Modules

二、Vuex的基本使用

1、安裝依賴

NPM
npm install vuex@next --save
yarn
yarn add vuex@next --save

2、src目錄下面新建一個vuex的文件夾,vuex 文件夾里面新建一個store.js

import { createStore } from 'vuex'
const store = createStore({
    state () {
      return {
        count: 1
      }
    },
    mutations: {
        increment (state) {     
          state.count++
        }
      }
  })  

export default store;

3、main.ts中掛載Vuex

import { createApp } from 'vue'
import App from './App.vue'
import route from './routes'
import store from './vuex/store'

let app=createApp(App);
//掛載路由
app.use(route)
//掛載vuex
app.use(store)
app.mount('#app')

4、獲取 修改state里面的數(shù)據(jù)

<template>
    <div>
      增加新聞--{{count}}
      <br>
      <button @click="incCount">改變Vuex里面的count</button>
    </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
    data(){
        return{}
    },methods:{        
        incCount(){
            this.$store.commit('increment')
        }
    },
    computed:{
        count():number{            
            return this.$store.state.count
        }
    }
})
</script>

三、Vuex中的State

State在Vuex中主要用于存儲數(shù)據(jù),State是存儲在 Vuex 中的數(shù)據(jù)和 Vue 實例中的 data 遵循相同的規(guī)則。

import { createStore } from 'vuex'
const store = createStore({
    state () {
      return {
        count: 1,
        list:['馬總','雷總','王總']
      }
    },
    mutations: {
        increment (state) {     
          state.count++
        }
      }
  })  

export default store;

3.1、第一種獲取State的方法(不推薦)

用到的組件里面引入store,然后計算屬性里面獲取

computed: {
    count () {
      return store.state.count
    }
  }

3.2、第二種獲取State的方法

由于全局配置了Vuex app.use(store),所以直接可以通過下面方法獲取store里面的值。

computed: {
    count () {
      return this.$store.state.count
    }
}

3.3、第三種獲取State的方法-通過mapState助手

方法 1:

<template>
  <div>修改新聞--{{ count }}</div>

  <br />
  <ul>
    <li v-for="(item, index) in list" :key="index">{{ item }}</li>
  </ul>
  <br />
</template>

<script>
import { defineComponent } from "vue";
import { mapState } from "vuex";
export default defineComponent({
  data() {
    return {};
  },
  methods: {},
  computed: {
    ...mapState({
      count: (state) => state.count,
      list: (state) => state.list,    
    }),
  },
});
</script>

方法 2:

<template>
  <div>修改新聞--{{ count }}</div>

  <br>  
  <ul>
    <li v-for="(item,index) in list" :key="index">{{item}}</li>
  </ul>
  <br>

</template>

<script>
import { defineComponent } from "vue";
import { mapState } from "vuex";
export default defineComponent({
  data() {
    return {};
  },
  methods: {},
  computed: {
    ...mapState([     
      "count",
      "list"
    ]),
  },
});
</script>

四:Vuex中的Getters

Getter有點類似我們前面給大家講的計算屬性。

Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算。

4.1 、定義Getter

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

4.2、訪問Getter的第一種方法

Getter 會暴露為 store.getters 對象,你可以以屬性的形式訪問這些值:

store.getters.doneTodos

4.3、訪問Getter的第二種方法

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

4.4、訪問Getter的第四種方法 通過mapGetters 輔助函數(shù)

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用對象展開運算符將 getter 混入 computed 對象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

如果你想將一個 getter 屬性另取一個名字,使用對象形式:

...mapGetters({
  // 把 `this.doneCount` 映射為 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

五、Vuex中的Mutations

更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數(shù) (handler)。這個回調函數(shù)就是我們實際進行狀態(tài)更改的地方,并且它會接受 state 作為第一個參數(shù)。

4.1、定義Mutations 觸發(fā)Mutations里面的方法

const store = createStore({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // mutate state
      state.count++
    }
  }
})

觸發(fā)mutations里面的方法:

store.commit('increment')

4.2、執(zhí)行方法傳入?yún)?shù):

mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)

4.3 對象方式提交數(shù)據(jù)

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit({
  type: 'increment',
  amount: 10
})

4.4 在組件中提交 Mutation

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
    })
  }
}

六、Vuex中的Actions

Action 類似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接變更狀態(tài)。
  • Action 可以包含任意異步操作。

6.1、定義Action

const store = createStore({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

另一種寫法

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

6.2、分發(fā) Action(觸發(fā)Action中的方法)

store.dispatch('increment')

乍一眼看上去感覺多此一舉,我們直接分發(fā) mutation 豈不更方便?實際上并非如此,還記得 mutation 必須同步執(zhí)行這個限制么?Action 就不受約束!我們可以在 action 內部執(zhí)行異步操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Actions 支持同樣的載荷方式和對象方式進行分發(fā):

// 載荷方式
store.dispatch('incrementAsync', {
  amount: 10
})

// 對象方式
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

來看一個更加實際的購物車示例,涉及到調用異步 API分發(fā)多重 mutation

actions: {
  checkout ({ commit, state }, products) {
    // 把當前購物車的物品備份起來
    const savedCartItems = [...state.cart.added]
    // 發(fā)出結賬請求,然后樂觀地清空購物車
    commit(types.CHECKOUT_REQUEST)
    // 購物 API 接受一個成功回調和一個失敗回調
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失敗操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

注意我們正在進行一系列的異步操作,并且通過提交 mutation 來記錄 action 產(chǎn)生的副作用(即狀態(tài)變更)

6.3 在組件中分發(fā) Action

你在組件中使用 this.$store.dispatch('xxx') 分發(fā) action,或者使用 mapActions 輔助函數(shù)將組件的 methods 映射為 store.dispatch 調用(需要先在根節(jié)點注入 store):

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // map `this.increment()` to `this.$store.dispatch('increment')`

      // `mapActions` also supports payloads:
      'incrementBy' // map `this.incrementBy(amount)` to        `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // map `this.add()` to `this.$store.dispatch('increment')`
    })
  }
}

6.4 組合 Action

Action 通常是異步的,那么如何知道 action 什么時候結束呢?更重要的是,我們如何才能組合多個 action,以處理更加復雜的異步流程?

首先,你需要明白 store.dispatch 可以處理被觸發(fā)的 action 的處理函數(shù)返回的 Promise,并且 store.dispatch 仍舊返回 Promise:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

現(xiàn)在你可以:

store.dispatch('actionA').then(() => {
  // ...
})

在另外一個 action 中也可以:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}

最后,如果我們利用 async / await,我們可以如下組合 action:

// assuming `getData()` and `getOtherData()` return Promises

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // wait for `actionA` to finish
    commit('gotOtherData', await getOtherData())
  }
}

七、Modules

由于使用單一狀態(tài)樹,應用的所有狀態(tài)會集中到一個比較大的對象。當應用變得非常復雜時,store 對象就有可能變得相當臃腫。

為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割:

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state

模塊的局部狀態(tài)

對于模塊內部的 mutation 和 getter,接收的第一個參數(shù)是模塊的局部狀態(tài)對象

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // `state` is the local module state
      state.count++
    }
  },
  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

八 、Vuex項目結構

Vuex 并不限制你的代碼結構。但是,它規(guī)定了一些需要遵守的規(guī)則:

  1. 應用層級的狀態(tài)應該集中到單個 store 對象中。
  2. 提交 mutation 是更改狀態(tài)的唯一方法,并且這個過程是同步的。
  3. 異步邏輯都應該封裝到 action 里面。

只要你遵守以上規(guī)則,如何組織代碼隨你便。如果你的 store 文件太大,只需將 action、mutation 和 getter 分割到單獨的文件。

對于大型應用,我們會希望把 Vuex 相關代碼分割到模塊中。下面是項目結構示例:

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

九、Vuex結合組合式合成API

組合式api中沒有this.$store,可以使用useStore來替代

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()
  }
}

9.1、組合式api中訪問state 和 getters

const store = new createStore({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // access a state in computed function
      count: computed(() => store.state.count),

      // access a getter in computed function
      double: computed(() => store.getters.double)
    }
  }
}

9.2、組合式api中訪問 Mutations and Actions

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // access a mutation
      increment: () => store.commit('increment'),

      // access an action
      asyncIncrement: () => store.dispatch('asyncIncrement')
    }
  }
}

十、Vue+Typescript的項目里面集成Vuex

首先需要在vue項目中集成typescript

vue add typescript

提示:如果配置完ts后調用this.$store有警告信息,請重啟vscode,或者安裝vue3的插件后重啟vscode充實

一、修改store.js 為store.ts

二、配置store.ts中的代碼

Vuex與TypeScript一起使用時,必須聲明自己的模塊擴充。

import { ComponentCustomProperties } from 'vue'
import { createStore,Store  } from 'vuex'

//配置讓Vuex支持ts
declare module '@vue/runtime-core' {
  //declare your own store states
  interface State {
    count: number,
    list:string[]
  }

  // provide typings for `this.$store`
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}

const store = createStore({
    state () {
      return {
        count: 1,
        list:['馬總','雷總','王總']
      }
    },
    mutations: {
        increment (state:any):void {     
          state.count++
        }
      }
  })
export default store;

三、main.ts中掛載

import { createApp } from 'vue'
import App from './App.vue'
import route from './routes'
import store from './vuex/store'

let app=createApp(App);
//掛載路由
app.use(route)
//掛載vuex
app.use(store)
app.mount('#app')

四、組件中使用掛載

<template>
  <div>修改新聞--{{ count }}</div>

  <br />
  <ul>
    <li v-for="(item, index) in list" :key="index">{{ item }}</li>
  </ul>
  <br />
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { mapState } from "vuex";
export default defineComponent({
  data() {
    return {};
  },
  methods: {},
  computed: {
    mylist():string[]{
      return  this.$store.state.list
    },
    ...mapState({
      count: (state:any) => state.count,
      list: (state:any) => state.list,    
    })   
  },
});
</script>
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容