vuex + Typescript

最近项目中用到了 感觉蛮不错的 所以分享下 本篇文章翻译Francesco Vitullo大佬的文章链接

最近,Typescript在Javascript生态系统中变得越来越流行,通过这篇文章,我不想深入研究Typescript,但是我想展示一种基本的方法,将Vuex与Typescript代码库集成在一个Vue应用程序中。
现在,我假设您熟悉基本的Typescript方法以及如何在Vue应用程序中使用该语言。如果你想看看一个基本的TS例子,我建议你看看这个repo: https://github.com/Microsoft/TypeScript-Vue-Starter

根据官方文件,Vuex的定义如下:

Vuex是一个状态管理模式+ Vue.js应用程序库。它充当应用程序中所有组件的集中存储,并使用规则确保状态只能以可预测的方式进行更改。


因为我对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);
  • types.ts:
// types.ts
export interface RootState {
    version: string;
}

这些代码与创建一个标准Vuex store非常相似,但你应该注意到稍显不同:

  • 使用“StoreOptions”类型创建storeOpts变量,并将泛型类型定义为“RootState”(它定义根状态类型)
  • 新的Vuex。Store也使用了RootState类型

由于这些差异,我们明确地定义了根Vuex实例的类型。
与往常一样,我建议并推荐采用模块化方法,因为在将Vuex连接到多个组件时有许多优点,所以我用一个简单而基本的模块布置了存储: 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文件,你可能会注意到以下几点:

  • 状态正初始化为ProfileState类型
  • 在这个阶段,创建和导出模块要复杂一些:它是一个定义了两种类型的模块: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');
        });
    }
};

为了导出Vuex的模块类型所期望的内容,我们需要将我们的动作聚合到一个“ActionTree”中,Vuex中定义了如下类型:

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

这没什么好理解的,它表示一个需要一些键的对象,定义动作的名称,以及一个与之相关的动作(仍然需要模块状态和根状态类型)
在我们的例子中,我们只有一个ActionTree,其中只包含一个名为“fetchData”的简单操作,它执行异步任务(从服务中检索一些数据),并根据网络响应提交成功或错误。如果成功,则将有效负载类型设置为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;
    }
};

突变是遵循相同的方法,我们讨论的行动和预期的变量突变树类型由Vuex定义如下:

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

为了结束模块的初始化,我们还公开了所需的getter。在我们的例子中,一个简单的getter返回所选用户的全名就足够了,它结合了存储的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>;
}

现在,有趣的部分是:如何将所有内容连接到一个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>

上面的例子是一个非常基本的例子。一个单独的文件组件,包含“模板”(当定义的条件在逻辑上变为true时,使用一个粗略的策略来显示正确的部分)和暴露组件的“脚本”。在这个例子中,我还使用vue-class-component来使用基于类的Vue组件(也是vuex-class的一个依赖项)。
由于Vuex-class,我们可以使用decorator来获得我们需要的任何东西:状态、操作、突变、getter和包装“有名称空间的decorator”。
我们的组件将有两个计算变量,一个名为“profile”,指的是概要文件的状态,另一个指的是我们在模块中定义的“getter”。
这个例子使用了两个由vuex-class公开的显式装饰器:State和Getter。为了访问正确的模块,将“namespace”作为属性的对象(或BindingOptions)作为第二个参数传递。

@State('profile') profile: ProfileState;
@Getter('fullName', { namespace }) fullName: string;

在我们的例子中,我们需要将动作“fetchData”与动作装饰器连接起来:

@Action('fetchData', { namespace }) fetchData: any;

并在“挂载”的生命周期回调中执行:

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

要呈现一些有意义的内容,模板的一部分是使用前面检查过的getter来呈现“fullName”和一个基本的计算属性来获取用户的电子邮件。

<p>
    Full name: {{ fullName }}
</p>
<p>
    Email: {{ email }}
</p>

基本上就是这样。还有其他方法来连接一个Vue组件与Vuex,但我相信这是一个有效的方式开始。
当然,在给定的示例/代码中还有很多改进的空间,例如,增强代码的类型以获得更健壮的逻辑或更好的方式来呈现模块的更改。
我希望你喜欢这篇文章!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • vue+typescript之后,vuex调用再也不是从前的调用方式了,需要加类型校验了 对比vuex在使用ts前...
    zackxizi阅读 5,013评论 0 2
  • Typescript 作为 Javascript 的超集,越来越流行,是前端未来的一种趋势。Vue 早已经支持了 ...
    摸摸大海参阅读 3,859评论 0 1
  • 九点半到几点结束帮不到你烦恼华东交大减肥计划等哈
    山哥_71ec阅读 185评论 0 0
  • 阳光拍打着后背与双眸影子催着双腿,拉长的步伐多情的衣袖,攒满一汪花香从头浇下,隔岸的深情对望 摆渡的人与帆,尤自在...
    夏爅阅读 405评论 12 13
  • 最近几个月,从七月份开始吧,每月都要出门。四月份去的郑州,五月是泉州,现在是六月中旬,还在南宁。 南宁是个挺潮的城...
    噼里啪啦小宝阅读 159评论 0 1