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


Posted in Javascript onJuly 25, 2019

前言

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 的介绍有限,所以以上代码肯定有不足的地方,希望大家指正。

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

Javascript 相关文章推荐
二级域名转向类
Nov 09 Javascript
javascript中的107个基础知识收集整理 推荐
Mar 29 Javascript
js和jquery使按钮失效为不可用状态的方法
Jan 26 Javascript
javascript中判断json的方法总结
Aug 27 Javascript
JS中mouseover和mouseout多次触发问题如何解决
Jun 06 Javascript
简单实现AngularJS轮播图效果
Apr 10 Javascript
AugularJS从入门到实践(必看篇)
Jul 10 Javascript
vue 的点击事件获取当前点击的元素方法
Sep 15 Javascript
JavaScript指定断点操作实例教程
Sep 18 Javascript
Jquery实现无缝向上循环滚动列表的特效
Feb 13 jQuery
vue回到顶部监听滚动事件详解
Aug 02 Javascript
JavaScript indexOf()原理及使用方法详解
Jul 09 Javascript
vue使用代理解决请求跨域问题详解
Jul 24 #Javascript
Vue父组件如何获取子组件中的变量
Jul 24 #Javascript
mock.js模拟数据实现前后端分离
Jul 24 #Javascript
vue+mock.js实现前后端分离
Jul 24 #Javascript
微信小程序如何修改radio和checkbox的默认样式和图标
Jul 24 #Javascript
Vue封装的组件全局注册并引用
Jul 24 #Javascript
vue子路由跳转实现tab选项卡
Jul 24 #Javascript
You might like
php常用的url处理函数总结
2014/11/19 PHP
php短信接口代码
2016/05/13 PHP
jQuery实现的Email中的收件人效果(按del键删除)
2011/03/20 Javascript
JavaScript中的View-Model使用介绍
2011/08/11 Javascript
jquery插件制作简单示例说明
2012/02/03 Javascript
无缝滚动改进版支持上下左右滚动(封装成函数)
2012/12/04 Javascript
js中indexof的用法详细解析
2013/12/24 Javascript
jquery取消选择select下拉框示例代码
2014/02/22 Javascript
js模拟C#中List的简单实例
2014/03/06 Javascript
jquery实现表单验证简单实例演示
2015/11/23 Javascript
js实现自定义路由
2017/02/04 Javascript
使用jquery模拟a标签的click事件无法实现跳转的解决
2018/12/04 jQuery
用原生 JS 实现 innerHTML 功能实例详解
2019/04/03 Javascript
微信小程序自定义导航栏实例代码
2019/04/05 Javascript
JavaScript实现文件下载并重命名代码实例
2019/12/12 Javascript
VUE项目axios请求头更改Content-Type操作
2020/07/24 Javascript
JQuery基于FormData异步提交数据文件
2020/09/01 jQuery
Python Web框架Flask信号机制(signals)介绍
2015/01/01 Python
python中nan与inf转为特定数字方法示例
2017/05/11 Python
Python配置mysql的教程(推荐)
2017/10/13 Python
使用Python写一个量化股票提醒系统
2018/08/22 Python
使用Python实现NBA球员数据查询小程序功能
2020/11/09 Python
日本土著品牌,综合型购物网站:Cecile
2016/08/23 全球购物
欧洲最大的品牌水上运动服装和设备在线零售商:Wuituit Outlet
2018/05/05 全球购物
Lancome兰蔻官方旗舰店:来自法国的世界知名美妆品牌
2018/06/14 全球购物
汉米尔顿手表官网:Hamilton
2020/09/13 全球购物
Java的for语句中break, continue和return的区别
2013/12/19 面试题
what is the difference between ext2 and ext3
2013/11/03 面试题
创业计划书中要认真思考的问题
2013/12/28 职场文书
毕业证丢失证明
2014/01/15 职场文书
爱我中华演讲稿
2014/05/20 职场文书
品牌推广策划方案
2014/05/28 职场文书
总经理检讨书范文
2015/02/16 职场文书
幼师辞职信怎么写
2015/02/27 职场文书
详解Vue项目的打包方式(生成dist文件)
2022/01/18 Vue.js
【海涛教你打DOTA】剑圣第一人称视角解说
2022/04/01 DOTA