一個簡單案例教你如何用Typescript寫Vuex

案例源代碼: github.com/danielhuoo/…javascript

前言

相信不少人都像我同樣,學習使用了vuex後,想把項目改寫成Typescript。可是官方教程要麼晦澀難懂,要麼缺乏鮮活的例子。我花了一天時間,總結出了一些經驗。在此分享出來。html

本教程經過編寫一個簡單的demo講解vuex的實現方式,以及如何對基於vue2.x的已有項目進行Typescript重構。vue

項目初始化

如今都9012了,因此咱們直接使用vue-cli 3.x快速搭建系統。java

# 搭建項目
vue create vue2.x-vuex-typescript-demo

cd vue2.x-vuex-typescript-demo
# 引入vuex
vue add vuex
# 因爲我實在不想寫任何樣式,因此我又加一個element
vue add element
複製代碼

模塊說明

爲了用實際的代碼解釋vuex是如何搭建的,以及模塊間的通信方式,我用了一個很淺顯的例子(應該比官方的例子明朗不少)git

情景

男孩給女孩送花。github

  1. 男孩每送出10朵花,女孩會表達感謝。
  2. 女孩的感謝會增長男孩的勇氣值。
  3. 男孩能夠向花店買花。

目錄結構

你會發現默認的目錄結構是這樣的:vuex

.
├── README.md
├── babel.config.js
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── plugins
│   │   └── element.js
│   └── store.js
└── yarn.lock

可是咱們想讓vuex變得模塊化。因此咱們改爲如下的結構:vue-cli

.
├── README.md
├── babel.config.js
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── plugins
│   │   └── element.js
│   └── store
│       ├── index.js
│       └── module
│           ├── boy.js
│           └── girl.js
└── yarn.lock
  1. index.jsstore的主文件
  2. /module 下存放模塊文件。 boy.js 是男孩模塊,girl.js 是女孩模塊

模塊定義

boy.js

該模塊定義了三個action方法。action通俗來講就是你想讓模塊作的事情,它們能夠是異步或者同步的。全部對state的增刪查改的邏輯都應該在這裏,而mutation僅僅負責執行增刪查改。typescript

import { Message } from 'element-ui';
export default {
    namespaced: true,
    // state 的屬性只能經過 mutation的方法進行修改
    state: {
        currentFlower: 50,
        braveScore: 0
    },
    mutations: {
        // 修改 state 的 currentFlower 的值
        updateCurrentFlower(state, payload) {
            state.currentFlower = state.currentFlower + payload
        },
        // 修改 state 的 braveScore 的值
        updateBraveScore(state, payload) {
            state.braveScore = state.braveScore + payload.score
        }
    },
    actions: {
        // 送花
        // 方法裏 調用了 commit 和 state,須要在傳參時聲明
        sendFlower({ commit, state }, params) {
            if (!state.currentFlower) {
                Message({
                    showClose: true,
                    message: "沒花可送了",
                    type: "warning"
                });
            } else {
                // 送出一朵花,本身的庫存減 1
                commit('updateCurrentFlower', -params.sendNumber)
                // 女孩收到一朵花,女孩庫存加 1。
                // 注意這裏是跨模塊調用,因此須要加上模塊前綴 'girl/',而且 傳入參數 {root:true} 代表經過根路徑尋找目標函數。
                commit('girl/updateCurrentFlower', params.sendNumber, { root: true })
            }
        },
        // 受到鼓勵
        beEncouraged({ commit }) {
            commit('updateBraveScore', { score: 10 })
        },
        // 買花
        // 方法裏調用了 commit, dispatch。 dispatch跨模塊調用根store的action,跟送花的commit同樣,須要加上前綴和傳入{root:true}
        buyFlower({ commit, dispatch }, params) {
            setTimeout(() => {
                dispatch('sellFlower', null, { root: true }).then(() => {
                    commit('updateCurrentFlower', params.buyNumber)
                }).catch(() => {
                    Message({
                        showClose: true,
                        message: "庫存不足",
                        type: "warning"
                    });
                })
            }, 100)
        }
    }
}
複製代碼

girl.js

export default {
    namespaced: true,
    state: {
        currentFlower: 0
    },
    mutations: {
        updateCurrentFlower(state, payload) {
            state.currentFlower = state.currentFlower + payload
        }
    },
    actions: {
        // 對男孩進行鼓舞
        encourage({ dispatch }, params) {
            dispatch('boy/beEncouraged', null, { root: true })
        }
    }
}
複製代碼

index.js

import Vue from 'vue'
import Vuex from 'vuex'
// 引入模塊
import boy from './module/boy'
import girl from './module/girl'
Vue.use(Vuex)

export default new Vuex.Store({
    // 根 state
    state: {
        flowersInStock: 10
    },
    // 根 mutations
    mutations: {
        updateFlowersInStock(state, payload) {
            state.flowersInStock = state.flowersInStock + payload
        }
    },
    // 根 actions
    actions: {
        sellFlower({ commit, state }, params) {
            return new Promise((resolve, reject) => {
                if (state.flowersInStock > 0) {
                    commit('updateFlowersInStock', -1)
                    resolve()
                } else {
                    reject()
                }
            })
        }
    },
    // 註冊模塊
    modules: {
        boy,
        girl
    }
})
複製代碼

鏈接到vue組件

如今倉庫的邏輯已經寫好了,咱們就能夠在組件上使用了。實際上vuex倉庫早在main.js被引入了vue實例裏了。例如,this.$store.state.flowersInStock即表明根state的屬性值。可是這種寫法太過繁瑣,咱們引入了vuex提供的 mapStatemapActionsmapMutations 進行映射。element-ui

boy.vue

<template>
    <div>
        <div>男孩</div>
        <div>手上有{{currentFlower}}朵花</div>
        <div>
            <el-button @click="sendFlower({sendNumber:1})">送花</el-button>
            <el-button @click="buyFlower({buyNumber:1})">買花</el-button>
        </div>
        <div>勇氣值:{{braveScore}}</div>
    </div>
</template>
<script> import { mapState, mapActions } from "vuex"; export default { computed: { // 你會發現state的映射放在了computed裏面。這麼作的好處是因爲 Vuex 的狀態存儲是響應式的,從 store 實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態。 // 經過映射,this.$store.state.currentFlower 就能夠表示爲 this.currentFlower ...mapState("boy", { currentFlower: state => state.currentFlower, braveScore: state => state.braveScore }) }, methods: { // actions 放在了methods裏面。這不奇怪,由於actions跟mutations同樣,都是vuex裏面的方法。 ...mapActions("boy", ["sendFlower", "buyFlower"]) } }; </script>
<style> </style>
複製代碼

不少人在剛開始用vuex都會記不住,究竟stateactionsmutations放哪裏。其實很好記:

  • state是屬性,放computed裏。
  • actionsmutations是方法,放methods裏。

girl.vue 同理,就不贅述了。下一步,咱們開始用Typescript改寫代碼。

安裝Typescript

在安裝以前,請必定要先作備份。由於安裝後App.vue會被改寫。

yarn add vuex-class
vue add typescript
? Use class-style component syntax? (Y/n)  Yes
? Use Babel alongside TypeScript for auto-detected polyfills? (Y/n) Yes
複製代碼

改寫開始

你會發現全部.js文件都被改爲.ts後綴了。這時候整個項目是跑不起來的。命令行控制檯會爆出幾十個error。事實上,在你沒有把全部該改的地方改好以前,項目是不會跑通的。

index.ts

被改寫的地方:

  • 引入module的方式。改成import對象中的一個屬性
  • 定義了store的類別。
  • 新增了一個RootState
import Vue from 'vue'
import Vuex, { StoreOptions } from 'vuex'
import { boy } from './module/boy'
import { girl } from './module/girl'
import { RootState } from './root-types';
Vue.use(Vuex)
const store: StoreOptions<RootState> = {
    // 裏面的內容不用修改
    state: {
        flowersInStock: 10
    },
    modules: {
        boy,
        girl
    },
    mutations: {
        updateFlowersInStock(state, payload) {
            state.flowersInStock = state.flowersInStock + payload
        }
    },
    actions: {
        sellFlower({ commit, state }) {
            return new Promise((resolve, reject) => {
                if (state.flowersInStock > 0) {
                    commit('updateFlowersInStock', -1)
                    resolve()
                } else {
                    reject()
                }
            })
        }
    }
}
export default new Vuex.Store<RootState>(store)
複製代碼

root-types.ts

這是對根state的約束

export interface RootState {
    flowersInStock: number
}
複製代碼

boy.ts

模塊的改動是巨大的。

  • 新增了模塊的State接口
  • 定義mutations的類爲 MutationTree
  • 定義actions的類爲 ActionTree
  • 定義模塊的類爲 Module
import { Message } from 'element-ui';
import { BoyState } from './module-types';
import { MutationTree, ActionTree, Module } from 'vuex';
import { RootState } from '../root-types';
const state: BoyState = {
    currentFlower: 50,
    braveScore: 0
}

// 傳入的泛型能夠經過查看源代碼得知。
const mutations: MutationTree<BoyState> = {
    updateCurrentFlower(state, payload) {
        state.currentFlower = state.currentFlower + payload
    },

    updateBraveScore(state, payload) {
        state.braveScore = state.braveScore + payload.score
    }
}
const actions: ActionTree<BoyState, RootState> = {
    sendFlower({ commit, state }, params) {
        if (!state.currentFlower) {
            Message({
                showClose: true,
                message: "沒花可送了",
                type: "warning"
            });
        } else {
            commit('updateCurrentFlower', -params.sendNumber)
            commit('girl/updateCurrentFlower', params.sendNumber, { root: true })
        }

    },
    buyFlower({ commit, dispatch }, params) {
        setTimeout(() => {
            dispatch('sellFlower', null, { root: true }).then(() => {
                commit('updateCurrentFlower', params.buyNumber)
            }).catch(() => {
                Message({
                    showClose: true,
                    message: "庫存不足",
                    type: "warning"
                });
            })
        }, 100)
    },
    beEncouraged({ commit }) {
        commit('updateBraveScore', { score: 10 })
    }
}
export const boy: Module<BoyState, RootState> = {
    namespaced: true,
    state,
    mutations,
    actions
}
複製代碼

boy.vue

vue文件改動的地方也是不少的:

  • script標籤指定了ts語言
  • 使用Component修飾組件
  • export 組件 從 對象變爲 類
  • 棄用 mapState 等方法,使用 StateActionMutation 修飾器綁定 vuex
  • 棄用computedmethodsdata 等寫法,使用get + 方法表示 computedmethods裏的方法直接被抽出來,data的屬性直接被抽出來。
<script lang="ts"> import { Vue, Component, Watch } from "vue-property-decorator"; import { State, Action, Mutation, namespace } from "vuex-class"; import { BoyState } from "../store/module/module-types"; @Component export default class boyComponent extends Vue { @State("boy") // 感嘆號不能省略 boyState!: BoyState; @Action("sendFlower", { namespace: "boy" }) sendFlower: any; @Action("buyFlower", { namespace: "boy" }) buyFlower: any; get currentFlower(): number { return this.boyState.currentFlower; } get braveScore(): number { return this.boyState.braveScore; } } </script>
複製代碼

其餘文件也是用相似的方法去改寫。換湯不換藥。

以上就是Typescript改寫的例子。有些地方沒有解釋得很清楚,由於我也是一個小白啊,不懂的地方仍是不要誤導你們了。若是你的項目的邏輯比這個更復雜(確定吧),而本項目沒有覆蓋到你的疑惑,你能夠去看個人另外一個改好的項目Jessic

相關文章
相關標籤/搜索