使用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 相关文章推荐
xml分页+ajax请求数据源+dom取结果实例代码
Oct 31 Javascript
跨浏览器开发经验总结(四) 怎么写入剪贴板
May 13 Javascript
JavaScript实现简单的时钟实例代码
Nov 23 Javascript
javascript的alert box在java中如何显示多行
May 18 Javascript
JQuery控制div外点击隐藏而div内点击不会隐藏的方法
Jan 13 Javascript
jQuery控制li上下循环滚动插件用法实例(附demo源码下载)
May 28 Javascript
基于javascript实现的购物商城商品倒计时实例
Dec 11 Javascript
Javascript自定义事件详解
Jan 13 Javascript
浅谈事件冒泡、事件委托、jQuery元素节点操作、滚轮事件与函数节流
Jul 22 jQuery
JS动态添加元素及绑定事件造成程序重复执行解决
Dec 07 Javascript
微信小程序实现跳转的几种方式总结(推荐)
Apr 24 Javascript
完美解决通过IP地址访问VUE项目的问题
Jul 18 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
php5编程中的异常处理详细方法介绍
2008/07/29 PHP
PHP准确取得服务器IP地址的方法
2015/06/02 PHP
PHP结合Ffmpeg快速搭建流媒体服务的实践记录
2018/10/31 PHP
JavaScript 学习笔记(七)字符串的连接
2009/12/31 Javascript
div拖拽插件——JQ.MoveBox.js(自制JQ插件)
2013/05/17 Javascript
浅析JQuery获取和设置Select选项的常用方法总结
2013/07/04 Javascript
c#程序员对TypeScript的认识过程
2015/06/19 Javascript
基于JavaScript怎么实现让歌词滚动播放
2015/11/03 Javascript
jQuery 3.0 的变化及使用方法
2016/02/01 Javascript
js+css实现回到顶部按钮(back to top)
2016/03/02 Javascript
快速实现JS图片懒加载(可视区域加载)示例代码
2017/01/04 Javascript
jQuery UI仿淘宝搜索下拉列表功能
2017/01/10 Javascript
用户管理的设计_jquery的ajax实现二级联动效果
2017/07/13 jQuery
js模块加载方式浅析
2017/08/12 Javascript
纯JS实现出生日期[年月日]下拉菜单效果
2018/06/01 Javascript
jQuery事件绑定和解绑、事件冒泡与阻止事件冒泡及弹出应用示例
2019/05/13 jQuery
vue 使用 canvas 实现手写电子签名
2020/03/06 Javascript
《javascript设计模式》学习笔记三:Javascript面向对象程序设计单例模式原理与实现方法分析
2020/04/07 Javascript
Vue实现附件上传功能
2020/05/28 Javascript
jQuery中event.target和this的区别详解
2020/08/13 jQuery
uniapp实现可以左右滑动导航栏
2020/10/21 Javascript
Python实现基于权重的随机数2种方法
2015/04/28 Python
Python2与Python3的区别实例总结
2019/04/17 Python
500行python代码实现飞机大战
2020/04/24 Python
纯CSS3发光分享按钮的实现教程
2014/09/06 HTML / CSS
cosme官方海外旗舰店:日本最大化妆品和美容产品的综合口碑网站
2017/01/18 全球购物
美国滑板店:Tactics
2020/11/08 全球购物
网络体系结构及协议的定义
2014/03/13 面试题
实习生个人的自我评价
2013/12/08 职场文书
给国外客户的邀请函
2014/01/30 职场文书
客服专员岗位职责
2014/02/28 职场文书
公司年会搞笑主持词
2014/03/24 职场文书
小学语文业务学习材料
2014/06/02 职场文书
个人师德师风自我剖析材料
2014/09/29 职场文书
2021-4-5课程——SQL Server查询【3】
2021/04/05 SQL Server
FFmpeg视频处理入门教程(新手必看)
2022/01/22 杂记