Vuex and Typescript

qingtonghub發表於2019-01-22

2018年9月30日,尤雨溪在medium個人部落格上釋出了vue3.0的開發思路。3.0帶了了很大的變化,他講了一些改進的思路以及整個開發流程的規劃。對2.0有了全面的提升,並且原始碼全部用typescript重寫,所以typescript刻不容緩。本文章翻譯自國外大牛Francesco Vitullo的文章,文章連結

以下為翻譯

最近,Typescript在Javascript生態系統中變得越來越流行,通過這篇文章,我不想深入研究Typescript,但我想展示一個基本方法,整合Vuex在Vue應用程式中與Typescript程式碼庫整合。 此時,我假設您熟悉基本的Typescript方法以及如何在Vue應用程式中使用該語言。 如果您想檢視一個基本的TS示例,我建議您檢視此repo:https://github.com/Microsoft/TypeScript-Vue-Starter。

根據官方文件,Vuex的定義方式如下: Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也整合到 Vue 的官方除錯工具 devtools extension,提供了諸如零配置的 time-travel 除錯、狀態快照匯入匯出等高階除錯功能。

由於我對Flux和Redux有豐富的經驗,這個概念對我來說聽起來並不新鮮,所以如果你熟悉這個模式,那麼獲取它並開始使用Vuex應該不是什麼大問題。 在我看來,這種模式在處理需要擴充套件和提高整體生產力的應用程式時非常有用。 回到這一點,我們如何將Vuex與Typescript結合起來?

首先,讓我們在index.ts中初始化並暴露store: index.ts檔案

// index.ts
import Vue from 'vue';
import Vuex, { StoreOptions } from 'vuex';
import { RootState } from './types';
import { profile } from './profile/index';

Vue.use(Vuex);

const store: StoreOptions<RootState> = {
    state: {
        version: '1.0.0' // a simple property
    },
    modules: {
        profile
    }
};
export default new Vuex.Store<RootState>(store);
複製程式碼

typescript特有的types.ts檔案:

// types.ts
export interface RootState {
    version: string;
}
複製程式碼

這些程式碼與建立一個標準Vuex store非常相似,但你應該注意到稍顯不同:

  1. 一個storeOpts變數應使用“StoreOptions”型別去建立,並將泛型型別定義為“RootState”(定義根狀態型別)
  2. 同樣new Vuex.Store需使用RootState型別。

由於存在這些差異,我們需要在根Vuex例項去明確定義這些types。 一般來說,我建議並推薦採用模組化方法,因為將Vuex連線到多個元件時有很多優點,所以我在store中放置了一個簡單的基本模組:Profile。

// profile/index.ts
import { Module } from 'vuex';
import { getters } from './getters';
import { actions } from './actions';
import { mutations } from './mutations';
import { ProfileState } from './types';
import { RootState } from '../types';

export const state: ProfileState = {
    user: undefined,
    error: false
};
const namespaced: boolean = true;
export const profile: Module<ProfileState, RootState> = {
    namespaced,
    state,
    getters,
    actions,
    mutations
};
複製程式碼

types.ts:

// types.ts
export interface User {
    firstName: string;
    lastName: string;
    email: string;
    phone?: string;
}

export interface ProfileState {
    user?: User;
    error: boolean;
}
複製程式碼

看一下index.ts檔案,您可能會注意到以下幾點:

  1. State被profilestate type初始化了
  2. 在這個階段,建立和匯出的模組有點複雜:它是一個定義兩種型別的模組:ProfileState(模組狀態)和RootState(Vuex儲存的根狀態)

Module是Vuex宣告的interface檔案:

// vuex/types/index.d.ts
export interface Module<S, R> {
  namespaced?: boolean;
  state?: S | (() => S);
  getters?: GetterTree<S, R>;
  actions?: ActionTree<S, R>;
  mutations?: MutationTree<S>;
  modules?: ModuleTree<R>;
}
複製程式碼

看一下暴露型別,Module是一個簡單的物件,將actions / mutation / getters / state聚合(可選)起來的和內部模組化策略。 我們來看看示例中的Actions。

Actions.ts:

// profile/actions.ts
import { ActionTree } from 'vuex';
import axios from 'axios';
import { ProfileState, User } from './types';
import { RootState } from '../types';

export const actions: ActionTree<ProfileState, RootState> = {
    fetchData({ commit }): any {
        axios({
            url: 'https://....'
        }).then((response) => {
            const payload: User = response && response.data;
            commit('profileLoaded', payload);
        }, (error) => {
            console.log(error);
            commit('profileError');
        });
    }
};
複製程式碼

為了匯出正確的Module型別,我們需要將我們actions設定為ActionTree型別,這是Vuex中定義的型別,如:

// vuex/types/index.d.ts
export interface ActionTree<S, R> {
  [key: string]: Action<S, R>;
}
複製程式碼

這並不難以理解,它代表ActionTree的鍵物件,定義了Action的名稱,以及與之相關的Action(仍然期望Module State和根State型別)。

本例中,我們只有一個ActionTree,它只有一個fetchData的action,它執行非同步任務(從服務中檢索一些資料)並根據網路響應提交成功或錯誤。 如果成功,將向使用者輸入有效負載。
複製程式碼

接下來mutations:

// profile/mutations.ts
import { MutationTree } from 'vuex';
import { ProfileState, User } from './types';

export const mutations: MutationTree<ProfileState> = {
    profileLoaded(state, payload: User) {
        state.error = false;
        state.user = payload;
    },
    profileError(state) {
        state.error = true;
        state.user = undefined;
    }
};
複製程式碼

Mutations與我們為Actions討論的相同方法,並期望由Vuex定義的MutationTree型別的變數,如下所示:

// vuex/types/index.d.ts
export interface MutationTree<S> {
  [key: string]: Mutation<S>;
}
複製程式碼

為了關閉模組的初始化,我們也暴露了所需的getter。 在我們的例子中,一個簡單的getter返回所選使用者的全名可能就足夠了,它結合了儲存的firstName和lastName屬性。 是的,您甚至可以在User中使用class,但這裡只是一個基本的getter示例。

Getters.ts

// profile/getters.ts
import { GetterTree } from 'vuex';
import { ProfileState } from './types';
import { RootState } from '../types';

export const getters: GetterTree<ProfileState, RootState> = {
    fullName(state): string {
        const { user } = state;
        const firstName = (user && user.firstName) || '';
        const lastName = (user && user.lastName) || '';
        return `${firstName} ${lastName}`;
    }
};
複製程式碼

在Vuex中定義如下

// vuex/types/index.d.ts
export interface GetterTree<S, R> {
  [key: string]: Getter<S, R>;
}
複製程式碼

現在,最重要的部分:我們如何將所有內容連線到Vue元件? 如下,我使用vuex-class將簡單元件連線到Vuex。

<template>
    <div class="container">
        <div v-if="profile.user">
            <p>
                Full name: {{ fullName }}
            </p>
            <p>
                Email: {{ email }}
            </p>
        </div>
        <div v-if="profile.error">
            Oops an error occured
        </div>
    </div>
</template>

<script lang="ts">
    import Vue from 'vue';
    import { State, Action, Getter } from 'vuex-class';
    import Component from 'vue-class-component';
    import { ProfileState, User } from './store/profile/types';
    const namespace: string = 'profile';
    @Component
    export default class UserDetail extends Vue {
        @State('profile') profile: ProfileState;
        @Action('fetchData', { namespace }) fetchData: any;
        @Getter('fullName', { namespace }) fullName: string;

        mounted() {
            // fetching data as soon as the component's been mounted
            this.fetchData();
        }

        // computed variable based on user's email
        get email() {
            const user = this.profile && this.profile.user;
            return (user && user.email) || '';
        }
    }
</script>
複製程式碼

上面的例子是一個非常基本的例子。 包含template的單檔案元件和暴露元件的typescript。

在該示例中,我還使用vue-class-component來使用基於類的Vue元件(也是vuex-class的依賴項)。 感謝vuex-class,可以使用裝飾器來獲得我們需要的東西:State,Actions,Mutations,Getters和namespaced decorators。

我們的元件有一些計算變數即computed,一個是由@State引入的profile,指的是Profile的狀態,另一個是我們在模組中定義的getter:get email()。

此示例使用由vuex-class公開的兩個顯式裝飾器:State和Getter。

為了訪問正確的模組,將具有namespace作為屬性的物件(或BindingOptions)作為第二個引數傳遞。

@State('profile') profile: ProfileState;
@Getter('fullName', { namespace }) fullName: string;
複製程式碼

當然,在profile的vuex中需要定義fetchData的Action,元件中才能使用

@Action('fetchData', { namespace }) fetchData: any;
複製程式碼

並且在mounted中使用fetchData

mounted() {
    // fetching data as soon as the component's been mounted
    this.fetchData();
}
複製程式碼

為了渲染正確,模板的一部分是使用先前定義的getter來顯示fullName和get email() 來獲取User的email。

<p>
    Full name: {{ fullName }}
</p>
<p>
    Email: {{ email }}
</p>
複製程式碼

基本上就是這樣。還有其他方法可以將Vue元件與Vuex連線,但我相信這是一種有效的入門方式。當然,在給定的示例中存在很大的改進空間,如增強程式碼的型別檢查使邏輯更加清晰,或通過更好的方式來呈現模組的渲染。我希望你喜歡這篇文章!

以上為翻譯內容,若有不正確之處,請不吝指出。

github上用Vue-CLI3搭了個ts+vuex小例子地址

相關文章