最近項目中用到了 感覺蠻不錯的 所以分享下 本篇文章翻譯Francesco Vitullo大佬的文章鏈接。
最近,Typescript在Javascript生態(tài)系統(tǒng)中變得越來越流行,通過這篇文章,我不想深入研究Typescript,但是我想展示一種基本的方法,將Vuex與Typescript代碼庫集成在一個Vue應(yīng)用程序中。
現(xiàn)在,我假設(shè)您熟悉基本的Typescript方法以及如何在Vue應(yīng)用程序中使用該語言。如果你想看看一個基本的TS例子,我建議你看看這個repo: https://github.com/Microsoft/TypeScript-Vue-Starter
根據(jù)官方文件,Vuex的定義如下:
Vuex是一個狀態(tài)管理模式+ Vue.js應(yīng)用程序庫。它充當(dāng)應(yīng)用程序中所有組件的集中存儲,并使用規(guī)則確保狀態(tài)只能以可預(yù)測的方式進(jìn)行更改。
因為我對Flux和Redux有豐富的經(jīng)驗,所以這個概念對我來說并不陌生,所以如果你熟悉這個模式,那么開始使用Vuex也沒什么大不了的。
在我看來,這種模式在處理需要擴(kuò)展和提高整體生產(chǎn)力的應(yīng)用程序時非常有用。
言歸正傳,我們?nèi)绾螌uex與Typescript結(jié)合起來?
- 首先,讓我們在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);
- types.ts:
// types.ts
export interface RootState {
version: string;
}
這些代碼與創(chuàng)建一個標(biāo)準(zhǔn)Vuex store非常相似,但你應(yīng)該注意到稍顯不同:
- 使用“StoreOptions”類型創(chuàng)建storeOpts變量,并將泛型類型定義為“RootState”(它定義根狀態(tài)類型)
- 新的Vuex。Store也使用了RootState類型
由于這些差異,我們明確地定義了根Vuex實例的類型。
與往常一樣,我建議并推薦采用模塊化方法,因為在將Vuex連接到多個組件時有許多優(yōu)點,所以我用一個簡單而基本的模塊布置了存儲: 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文件,你可能會注意到以下幾點:
- 狀態(tài)正初始化為ProfileState類型
- 在這個階段,創(chuàng)建和導(dǎo)出模塊要復(fù)雜一些:它是一個定義了兩種類型的模塊:ProfileState(即模塊狀態(tài))和RootState (Vuex存儲的根狀態(tài))
- 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聚合(可選)起來的和內(nèi)部模塊化策略。
讓我們來看看示例中的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');
});
}
};
為了導(dǎo)出Vuex的模塊類型所期望的內(nèi)容,我們需要將我們的動作聚合到一個“ActionTree”中,Vuex中定義了如下類型:
// vuex/types/index.d.ts
export interface ActionTree<S, R> {
[key: string]: Action<S, R>;
}
這沒什么好理解的,它表示一個需要一些鍵的對象,定義動作的名稱,以及一個與之相關(guān)的動作(仍然需要模塊狀態(tài)和根狀態(tài)類型)
在我們的例子中,我們只有一個ActionTree,其中只包含一個名為“fetchData”的簡單操作,它執(zhí)行異步任務(wù)(從服務(wù)中檢索一些數(shù)據(jù)),并根據(jù)網(wǎng)絡(luò)響應(yīng)提交成功或錯誤。如果成功,則將有效負(fù)載類型設(shè)置為User。
- Mutations.ts
// 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;
}
};
突變是遵循相同的方法,我們討論的行動和預(yù)期的變量突變樹類型由Vuex定義如下:
// vuex/types/index.d.ts
export interface MutationTree<S> {
[key: string]: Mutation<S>;
}
為了結(jié)束模塊的初始化,我們還公開了所需的getter。在我們的例子中,一個簡單的getter返回所選用戶的全名就足夠了,它結(jié)合了存儲的firstName和lastName屬性。
是的,你甚至可以為用戶用一個類來做這個,但是我想要為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>;
}
現(xiàn)在,有趣的部分是:如何將所有內(nèi)容連接到一個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;
// @userModule.Mutation("changeLoginRegister") public changeLoginRegister!: Function;
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>
上面的例子是一個非常基本的例子。一個單獨(dú)的文件組件,包含“模板”(當(dāng)定義的條件在邏輯上變?yōu)閠rue時,使用一個粗略的策略來顯示正確的部分)和暴露組件的“腳本”。在這個例子中,我還使用vue-class-component來使用基于類的Vue組件(也是vuex-class的一個依賴項)。
由于Vuex-class,我們可以使用decorator來獲得我們需要的任何東西:狀態(tài)、操作、突變、getter和包裝“有名稱空間的decorator”。
我們的組件將有兩個計算變量,一個名為“profile”,指的是概要文件的狀態(tài),另一個指的是我們在模塊中定義的“getter”。
這個例子使用了兩個由vuex-class公開的顯式裝飾器:State和Getter。為了訪問正確的模塊,將“namespace”作為屬性的對象(或BindingOptions)作為第二個參數(shù)傳遞。
@State('profile') profile: ProfileState;
@Getter('fullName', { namespace }) fullName: string;
在我們的例子中,我們需要將動作“fetchData”與動作裝飾器連接起來:
@Action('fetchData', { namespace }) fetchData: any;
并在“掛載”的生命周期回調(diào)中執(zhí)行:
mounted() {
// fetching data as soon as the component's been mounted
this.fetchData();
}
要呈現(xiàn)一些有意義的內(nèi)容,模板的一部分是使用前面檢查過的getter來呈現(xiàn)“fullName”和一個基本的計算屬性來獲取用戶的電子郵件。
<p>
Full name: {{ fullName }}
</p>
<p>
Email: {{ email }}
</p>
基本上就是這樣。還有其他方法來連接一個Vue組件與Vuex,但我相信這是一個有效的方式開始。
當(dāng)然,在給定的示例/代碼中還有很多改進(jìn)的空間,例如,增強(qiáng)代碼的類型以獲得更健壯的邏輯或更好的方式來呈現(xiàn)模塊的更改。
我希望你喜歡這篇文章!