"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
复制代码
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
id='todo'
v-model="todo"
placeholder="请输入内容"
></el-input>
</el-col>
<el-col :span="2">
<el-button
id="add"
type="primary"
icon="el-icon-circle-plus-outline"
@click="addItem"
>add</el-button>
</el-col>
</el-row>
</div>
v-for="(item,index) in todoList"
:key="index"
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 {
return `${this.todo}`;
@Emit('removeItem')
private removeItem(index: number): number {
return index;
</script>
<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 代码的用法指出以下几点:
prop 建议写成 xxx!: type
的形式,不然要写成 xxx : type | undefined
@Emit 可以不传参数,emit 出去的事件名默认是修饰的函数名,但是当函数的命名规则为 camelCase 时需要注册的函数名必须是 kebab-case
@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';
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
import Vue from 'vue';
import Vuex from 'vuex';
import todolist from './todoList';
Vue.use(Vuex);
export default new Vuex.Store({
modules: { todolist }
复制代码
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="removelistItem"
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import todoList from '@/components/todoList.vue';
import { State, Getter, Action } from 'vuex-class';
const namespace = { namespace: 'todolist' };
@Component({
components: {
todoList
export default class Home extends Vue {
@State('todoList', namespace) public todoList!: string[];
@Action('addList', namespace) private addList!: (val: string) => void;
@Action('removeItem', namespace) private removeItem!: (
payload: object
) => void;
public addTodoList(val: string) {
console.log('val', val);
if (val) {
this.addList(val);
private created() {
console.log('i add life cycle funciton -- created');
private removelistItem(index: number) {
console.log(index);
this.removeItem({ index });
</script>
复制代码
有关 vuex-class 的调用有以下几点注意
修饰器的所有参数声明可以使用 command + 左键点击去查看,有多中写法,以上代码使用最优雅的写法
@Action 中函数的声明,形参必须和方法保持一致
所有的代码到此为止,使用 npm run serve
即可查看应用,保留原有 routes 文件,保持应用的健壮性。
本项目使用 vue-test-utils 进行编写,结合多种情况,得出以下代码:
在 src/tests/unit 新建 todoList.spec.ts
// todoList.spec.ts
import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import Vue from 'vue'
import Vuex from 'vuex'
import ElementUI from 'element-ui'
import todoList from '@/components/todoList.vue'
import home from '@/views/Home.vue'
const localVue = createLocalVue()
localVue.use(ElementUI)
localVue.use(Vuex)
describe('todoList.vue', () => {
let actions: any
let store: any
beforeEach(() => {
actions = { addList: jest.fn(), removeItem: jest.fn() }
const todolist = {
namespaced: true,
state: { todoList: [] },
actions
store = new Vuex.Store({
modules: {
todolist
it('renders props.msg when passed', () => {
const father = mount(home, { store, localVue })
// const child = shallowMount(todoList, { localVue }) as any
const child = father.find(todoList) as any
child.vm.todo = 'todo 1'
child.find('
expect(child.emitted()['add-item']).toBeTruthy()
expect(actions.addList).toHaveBeenCalled()
使用了第三方库需要使用 createLocalVue
进行处理
vuex 的模拟如果有用到 modules
必须要指定 namespaced: true,
通过父组件拿到 子组件 后,如果要对子组件使用 find 方法,需使用 mount
包裹,不能使用 shallowMount
在命令行调用 npm run test:unit
可查看测试结果
最终项目目录结构如下
╭─~/otherEWokspace/ts-vuex-demo ‹master›
╰─➤ tree -L 4 -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
│ │ └── logo.png
│ ├── components
│ │ └── todoList.vue
│ ├── main.ts
│ ├── router.ts
│ ├── shims-tsx.d.ts
│ ├── shims-vue.d.ts
│ ├── store
│ │ ├── index.ts
│ │ └── todoList.ts
│ └── views
│ ├── About.vue
│ └── Home.vue
├── tests
│ └── unit
│ └── todoList.spec.ts
├── tsconfig.json
└── tslint.json
复制代码
写在最后
本文只是介绍了一个简单构建 ts-vue 应用的例子,对于框架的健壮和可扩展性有需要慢慢考虑,比如 webpack 的配置,适应测试,生产等各种环境的区分,axois 的封装,等等。
对于vue + ts 的配方,文章还有很多 vue 的特性没有去兼容,比如 this.refs 的使用,比如 vue-property-decorator 其他特性的使用。
由于官方文档对 ts 的介绍有限,所以以上代码肯定有不足的地方,希望大家指正。
github.com/EricLLLLLL/…