JavaScript

超轻量级php框架startmvc

使用VueCli3+TypeScript+Vuex一步步构建todoList的方法

更新时间:2020-09-05 20:06:02 作者:startmvc
前言Vue3.x即将来袭,使用TypeScirpt重构,TypeScript将成为vue社区的标配,出于一名程序员的焦

前言

Vue3.x 即将来袭,使用 TypeScirpt 重构,TypeScript 将成为 vue 社区的标配,出于一名程序员的焦虑,决定现在 Vue2.6.x 踩一波坑。

vue 官方文档已经简略地对 typescript 的支持进行了介绍,我们使用 Vue Cli3 直接生成项目

创建项目

❓为什么使用 Vue Cli3 构建项目

官方维护,后续升级减少兼容性问题

使用以下配置进行项目的生成:

  • Babel 对 Ts 进行转译
  • TSLint 对 TS 代码进行规范,后续会使用 prettier 对项目进行编码的统一
  • 默认安装 Vuex 和 Router , Router 使用  history 模式
  • 使用 Jest 进行单元测试

╭─~/otherEWokspace
╰─➤ vue create ts-vuex-demo


Vue CLI v3.6.3
┌───────────────────────────┐
│ Update available: 3.9.3 │
└───────────────────────────┘
? Please pick a preset: Manually select features

? Check the features needed for your project: Babel, TS, Router, Vuex, CSS P
re-processors, Linter, Unit

? Use class-style component syntax? Yes

? Use Babel alongside TypeScript for auto-detected polyfills? Yes

? Use history mode for router? (Requires proper server setup for index fallb
ack in production) Yes

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are suppor
ted by default): Sass/SCSS (with node-sass)

? Pick a linter / formatter config: TSLint

? Pick additional lint features: (Press <space> to select, <a> to toggle all
, <i> to invert selection)Lint on save

? Pick a unit testing solution: Jest

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In de
dicated config files

? Save this as a preset for future projects? Yes

? Save preset as: ts-vue-demo

看一下新项目的层级目录


╭─~/otherEWokspace/ts-vuex-demo ‹master›
╰─➤ tree -L 2 -I node_modules
.
├── README.md
├── babel.config.js
├── jest.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ ├── components
│ ├── main.ts
│ ├── router.ts
│ ├── shims-tsx.d.ts
│ ├── shims-vue.d.ts
│ ├── store.ts
│ └── views
├── tests
│ └── unit
├── tsconfig.json
└── tslint.json

tsconfig.json

对 lib 、 target 、 module 进行解释


{
 "compilerOptions": {
 "target": "esnext",
 "module": "esnext",
 "strict": true,
 "jsx": "preserve", // 开启对 jsx 的支持
 "importHelpers": true,
 "moduleResolution": "node",
 "experimentalDecorators": true,
 "esModuleInterop": true,
 "allowSyntheticDefaultImports": true,
 "sourceMap": true,
 "baseUrl": ".",
 "types": [
 "webpack-env",
 "jest"
 ],
 "paths": {
 "@/*": [
 "src/*"
 ]
 },
 "lib": [
 "esnext",
 "dom",
 "dom.iterable",
 "scripthost"
 ]
 },
 "include": [
 "src/**/*.ts",
 "src/**/*.tsx",
 "src/**/*.vue",
 "tests/**/*.ts",
 "tests/**/*.tsx"
 ],
 "exclude": [
 "node_modules"
 ]
}
  • target --- 被 tsc 编译后生成 js 文件代码风格
  • module --- 被 tsc 编译后生成 js 文件的模块风格
  • lib --- 原 ts 文件支持的代码库

我们来看一下示例:


// index.ts
export const Greeter = (name: string) => `Hello ${name}`;

"module": "commonjs", "target": "es5"


// index.js
"use strict";

Object.defineProperty(exports, "__esModule", { value: true });

exports.Greeter = function (name) { return "Hello " + name; };

"module": "es2015", "target": "es5"


// index.js
export var Greeter = function (name) { return "Hello " + name; };

"module": "es2015", "target": "es6"


// index.js
export const Greeter = (name) => `Hello ${name}`;

"module": "commonjs", "target": "es6"


// index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Greeter = (name) => `Hello ${name}`;

如果lib没有指定默认注入的库的列表。默认注入的库为:

  • 针对于 target:ES5:DOM,ES5,ScriptHost
  • 针对于 target:ES6:DOM,ES6,DOM.Iterable,ScriptHost

tslint

类似于 eslint ,对 ts 代码进行检测。

vscode 需要安装tslint 插件 ,并在 vscode 的用户配置中加入以下配置,用来在保存时自动解决 ts 的错误。


// settings.json
 "editor.codeActionsOnSave": {
 "source.fixAll.tsLint": true
 }

❗️ vue cli3 已经安装了tslint依赖

使用prettier 插件,对项目进行代码风格的统一和规范


npm i tslint-config-prettier -D

添加 tslint.json  extends 字段如下:


"extends": ["tslint:recommended", "tslint-config-prettier"]

设置 vscode

  • 勾选 tslintIntegration ,使 prittier 支持格式化 ts 文件
  • "editor.formatOnSave": true 保存时自动格式化

也可以使用 shift + option + f 进行格式化

在根目录下添加 .prttierrc 文件 (应对 prittier 格式化 vue 文件中的 ts 文件时,没办法使用 tslint 规则进行格式化,需要对它单独处理,以免 tslint 报错)


{ "singleQuote": true }

shims-vue.d.ts


declare module "*.vue" {
 import Vue from "vue";
 export default Vue;
}

声明所有以 .vue 结尾的文件,默认导入 vue ,默认导出 Vue,用以在项目中ts文件识别 .vue 结尾文件。

在 main.ts 中,引入一个 vue 组件必须以 .vue 结尾。


import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

Vue.config.productionTip = false;

new Vue({
 router,
 store,
 render: (h) => h(App),
}).$mount('#app');

Vue class

vue-property-decorator

写一个 todolist 组件顺便来介绍 vue-property-decorator,为了方便页面构建,使用 element-ui

element-ui 使用 ts 开发,默认有 .d.ts 的声明文件


npm i element-ui

// main.ts

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

在 /src/compenents/ 新建 todoList.vue , 代码如下:


<template>
 <div class="todo_list">
 <el-card class="box-card">
 <div slot="header">
 <el-row :gutter="18">
 <el-col :span="18">
 <el-input
 v-model="todo"
 placeholder="请输入内容"
 ></el-input>
 </el-col>
 <el-col :span="2">
 <el-button
 type="primary"
 icon="el-icon-circle-plus-outline"
 @click="addItem"
 >add</el-button>
 </el-col>

 </el-row>

 </div>
 <div
 v-for="(item,index) in todoList"
 :key="item"
 class="text item"
 @click="removeItem(index)"
 >{{ item }}</div>
 </el-card>
 <label
 class="text"
 style="margin-top:20px"
 >{{todoLength}} records</label>
 </div>
</template>


<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
 public todo: string = '';

 @Prop({ default: [] }) private readonly todoList!: string[];

 get todoLength(): number {
 return this.todoList.length;
 }

 @Emit()
 private addItem(): string | undefined {
 if (this.todo) {
 return this.todo;
 }
 }

 @Emit('removeItem')
 private removeItem(index: number): number {
 return index;
 }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.todo_list {
 display: flex;
 justify-content: center;
 flex-direction: column;
 align-items: center;
 .box-card {
 width: 480px;
 }

 .text {
 font-size: 14px;
 text-align: left;
 }

 .item {
 margin-bottom: 18px;
 }
}
</style>

对 ts 代码的用法指出以下几点:

  1. prop 建议写成 xxx!: type 的形式,不然要写成 xxx : type | undefined
  2. @Emit 可以不传参数,emit 出去的事件名默认是修饰的函数名,但是当函数的命名规则为 camelCase 时需要注册的函数名必须是 kebab-case
  3. @Emit 传参是由修饰的函数 return value

改造 Home.vue 如下:


<template>
 <div class="home">
 <todoList
 :todoList="[]"
 @add-item="addTodoList"
 @removeItem="addTodoLisItem"
 />
 </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import todoList from '@/components/todoList.vue'; // @ is an alias to /src
import { State, Getter, Action } from 'vuex-class';

@Component({
 components: {
 todoList
 }
})
export default class Home extends Vue {
 
 public addTodoList(val: string) {
 console.log(val);
 
 }

 private created() {
 console.log('i add life cycle funciton -- created');
 }

 private addTodoLisItem(index: number) {
 console.log(index);
 }
}
</script>

Vuex

有关 ts 中的 vuex 的写法要从vuex-class 说起,在 官方的 vue-property-decorator 中也推荐使用该库。


npm i vuex-class

在 src 文件夹中新建 store 文件夹, 在 store 新建 index.ts,todoList.ts


// index.ts

import Vue from 'vue';
import Vuex from 'vuex';

import todolist from './todoList';

Vue.use(Vuex);

export default new Vuex.Store({
 modules: { todolist }
});


// todoList.ts

import { Commit, Dispatch, GetterTree, ActionTree, MutationTree } from 'vuex';

const ADD_TODOLIST = 'ADD_TODOLIST';
const REMOVE_ITEM = 'REMOVE_ITEM';

export interface RootState {
 version: string;
}

interface Payload {
 [propName: string]: any;
}

interface TodoListType {
 todoList: string[];
}

interface Context {
 commit: Commit;
 dispatch: Dispatch;
}

const dataSource: TodoListType = {
 todoList: []
};

const getters: GetterTree<TodoListType, RootState> = {
 getTodoList(state: TodoListType): string[] {
 return state.todoList;
 }
};

const mutations: MutationTree<TodoListType> = {
 ADD_TODOLIST: (state: TodoListType, item: string) => {
 console.log(item);
 state.todoList.push(item);
 },
 REMOVE_ITEM: (state: TodoListType, removeIndex: number) => {
 state.todoList = state.todoList.filter((item: string, index: number) => {
 return removeIndex !== index;
 });
 }
};

const actions: ActionTree<TodoListType, RootState> = {
 addList: async ({ commit }: Context, item: string) => {
 await Promise.resolve(
 setTimeout(() => {
 commit(ADD_TODOLIST, item);
 }, 100)
 );
 },
 removeItem: async ({ commit }: Context, { index }: Payload) => {
 await Promise.resolve(
 setTimeout(() => {
 commit(REMOVE_ITEM, index);
 }, 100)
 );
 }
};

export default {
 namespaced: true,
 state: dataSource,
 getters,
 mutations,
 actions
};

删除原来与 main.ts 同级的 store.ts

对 todoList.ts 需要注意以下几点:

  • 对于 getters 、mutations 、actions 响应的 type 可以使用 command + 左键点击 进入声明文件查看,也可以不指定 type ,但是建议写上
  • 对于 Payload 解构  tslint 报错的,可以为 Payload 添加类型声明

interface Payload {
 [propName: string]: any;
}

代码中的 dataSource 本意为 state ,但是不能用 state 命名,tslint 会和形参 state 冲突

改造 /views/Home.vue 如下:


<template>
 <div class="home">
 <todoList
 :todoList="todoList"
 @add-item="addTodoList"
 @removeItem="addTodoLisItem"
 />
 </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import todoList from '@/components/todoList.vue'; // @ is an alias to /src
import { State, Getter, Action } from 'vuex-class';

@Component({
 components: {
 todoList
 }
})
export default class Home extends Vue {
 @State(state => state.todolist.todoList) private todoList!: string[];

 @Action('todolist/addList') private addList!: (val: string) => void;
 @Action('todolist/removeItem') private removeItem!: (index: number) => void;

 public addTodoList(val: string) {
 console.log(val);
 this.addList(val);
 }

 private created() {
 console.log('i add life cycle funciton -- created');
 }

 private addTodoLisItem(index: number) {
 this.removeItem(index);
 }
}
</script>

有关 vuex-class 的调用有以下几点注意

  • @State 如果有分模块,必须使用 state => state.xxx.xxx 的形式获取state
  • @Action 中函数的声明,形参必须和方法保持一致

所有的代码到此为止,使用 npm run serve 即可查看应用,保留原有 routes 文件,保持应用的健壮性。

写在最后

  1. 本文只是介绍了一个简单构建 ts-vue 应用的例子,对于框架的健壮和可扩展性有需要慢慢考虑,比如 webpack 的配置,适应测试,生产等各种环境的区分,axois 的封装,等等。
  2. 对于vue + ts 的配方,文章还有很多 vue 的特性没有去兼容,比如 this.refs 的使用,比如 vue-property-decorator 其他特性的使用。
  3. 由于官方文档对 ts 的介绍有限,所以以上代码肯定有不足的地方,希望大家指正。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

VueCli3 TypeScript Vuex构建todoList