前言: 使用过 vue-cli 3.x 的同学应该明白目前 vue-cli 3.x 已经对 typescript 有了很好的支持, 可以很基于 vue-cli 3.x 很方便地创建 vue + typescript 项目,但是具体操作以及常见的开发痛点文档中并没有做过多介绍。其中还是有不少坑的,本文就是基于自身使用给大家做一个关于 vue + typescript 项目基础创建的分享。
基于 vue-cli 3.x 新建typescript 项目流程
- 使用 vue-cli 3.x 命令行创建新项目 vue create projectname
- 修改 tsconfig 和 tslint 配置项
- 设置 vscode 用户代码片段 snippet
- 改写 router
- 修改 webpack 配置
- 书写一个典型的 vue-typescript 组件
- 起步demo展示
一、 基于 vue-cli 创建项目
vue create projectName
复制代码
项目设置设置中 Check the features needed for your project 记得勾选 typescript,Use class-style component syntax 选择Yes,其余按照个人习惯进行选择即可。
Vue CLI v3.1.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Router, Vuex, CSS Pre-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 fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: TSLint
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N)
复制代码
二、 修改 tsconfig 和 tslint 配置项
进入项目打开 tsconfig.json 修正 compilerOptions 下的配置项, 方便引入js模块和静态资源。
"compilerOptions": {
...
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块默认倒入
"noImplicitAny": false, // 在表达式和声明上有隐含的any类型时报错
...
}
复制代码
打开 tslint 根据个人习惯修正相关配置,以下是个人推荐配置
{
"defaultSeverity": "warning",
"extends": [
"tslint:recommended"
],
"linterOptions": {
"exclude": [
"node_modules/**"
]
},
"no-trailing-whitespace": false,
"rules": {
"quotemark": false,
"indent": [true, "spaces", 4],
"interface-name": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"no-console": false,
"no-debugger": false,
// 提升可维护性
"no-unused-expression": [true, "allow-fast-null-checks"], // 禁止无用的表达式 但是允许写法 e && e.fn()
"no-unused-variable": false, // 定义过的变量必须使用
"triple-equals": true, // 必须使用 === 或 !==,禁止使用 == 或 !=,与 null 比较时除外
"no-parameter-reassignment": true, // 禁止对函数的参数重新赋值
"no-conditional-assignment": true, // 禁止在分支条件判断中有赋值操作
"no-construct": true, // 禁止使用 new 来生成 String, Number 或 Boolean
"no-duplicate-super": true, // 禁止 super 在一个构造函数中出现两次
"no-duplicate-switch-case": true, // 禁止在 switch 语句中出现重复测试表达式的 case
"no-object-literal-type-assertion": true, // 禁止对对象字面量进行类型断言(断言成 any 是允许的)
"no-return-await": true, // 禁止没必要的 return await
"no-sparse-arrays": true, // 禁止在数组中出现连续的逗号,如 let foo = [,,]
"no-string-throw": true, // 禁止 throw 字符串,必须 throw 一个 Error 对象
"no-switch-case-fall-through": true, // switch 的 case 必须 return 或 break
"prefer-object-spread": true, // 使用 { ...foo, bar: 1 } 代替 Object.assign({}, foo, { bar: 1 }) 前者的类型检查更完善
"radix": true, // parseInt 必须传入第二个参数
"cyclomatic-complexity": [
true,
20
], // 禁止函数的循环复杂度超过 20
"deprecation": true, // 禁止使用废弃(被标识了 @deprecated)的 API
"use-isnan": true, // 必须使用 isNaN(foo) 而不是 foo === NaN
"no-duplicate-imports": true, // 禁止出现重复的 import
"no-mergeable-namespace": true, // 禁止一个文件中出现多个相同的 namespace
"encoding": true, // 文件类型必须时 utf-8
"import-spacing": true, // import 语句中,关键字之间的间距必须是一个空格
"interface-over-type-literal": true, // 接口可以 implement extend 和 merge
"new-parens": true, // new 后面只必须有一个空格
"no-angle-bracket-type-assertion": true, // 类型断言必须使用 as Type,禁止使用 <Type>, <Type> 容易被理解为 jsx
"no-consecutive-blank-lines": [
true,
3
] // 禁止连续超过三行空行
}
}
复制代码
其中有几项配置需单独进行说明
"no-unused-expression": [true, "allow-fast-null-checks"],
复制代码
打开此项目需要像上面的配置文件中一样,添加 "allow-fast-null-checks",不然类似 e && e.fn() 语法会报错 unused-expression。
"no-console": false,
"no-debugger": false,
复制代码
"no-console" 和 "no-debugger" 在调试时可以设置 false
"no-trailing-whitespace": false,
复制代码
"no-trailing-whitespace" 建议修改为 false
三、 设置 vscode 用户代码片段 snippet
这里在 vscode 中配置 snippet 方便我们新建 .vue 文件,如果是其他编辑器可按照配置自行修正。cmd + shift + P 打开设置查找,输入 snippet,新建 vuets.code-snippet.json 中进行编辑:
{
"Print to console": {
"prefix": "vuets",
"body": [
"<template>",
"",
"</template>",
"<script lang='ts'>",
"import { Component, Vue, Prop } from 'vue-property-decorator';",
"import from '@/components/ .vue'",
"@Component({",
" components: {",
" ",
" }",
"})",
"export default class componentName extends Vue {",
" @Prop(type)private propName = propValue;",
" private variableName: typeName = variableValue;",
" public methodName() {",
" ",
" }",
"}",
"</script>",
"<style lang='less' scoped >",
"",
"</style>"
],
"description": "basic vue typescript template"
}
}
复制代码
配置完成后,在 .vue 文件中输入 vuets + tap, 即可生成单 vue 文件的大体目录
<template>
</template>
<script lang='ts'>
import { Component, Vue, Prop } from 'vue-property-decorator';
import from '@/components/ .vue'
@Component({
components: {
}
})
export default class componentName extends Vue {
@Prop(type)private propName = propValue;
private variableName: typeName = variableValue;
public methodName() {
}
}
</script>
<style lang='less' scoped >
</style>
复制代码
四、 改写 router
用 import 的方式引入对应组件
Vue.use(Router);
const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
...
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: About,
},
],
复制代码
五、 修改 webpack 配置
可能很多同学已经发现 vue-cli 3 默认已经没有了 webpackconfig 之类的文件,如果我们需要进行 http 请求 或者修正其他配置项,可以在根目录下创建 vue.config.js ,书写对应配置。
module.exports = {
baseUrl: '/',
devServer: {
proxy: {
'/api': {
target: 'http://api.com',
changeOrigin: true,
},
'/test': {
target: 'http://test.com',
changeOrigin: true
},
'/res': {
target: 'http://res.com',
changeOrigin: true,
},
},
},
}
复制代码
六、 书写一个典型的 vue-typescript 组件
基础的写法中,style 部分和通常并无二致。
template 部分,在父组件中书写引入的子组件时,改为单标签书写。
<template>
<FirstPage propMessage="从home传入的值"
@greet-emit="greetEmit"/>
</template>
<script lang="ts">
import Vue from "vue";
import { Component, Prop, Watch, Emit } from "vue-property-decorator";
import FirstPage from "@/components/FirstPage.vue"; // 引入组件
@Component({
FirstPage, // 引入组件
})
export default class Home extends Vue {
}
...
</script>
复制代码
script 标签中,我们需要写 lang = 'ts' 来告诉编辑器我们使用的是 ts 来书写组件部分。常规的props,和生命周期书写方式如下:
<script lang="ts">
import Vue from "vue";
import { Component, Prop, Watch, Emit } from "vue-property-decorator";
@Component
export default class FirstPage extends Vue {
// props
@Prop([String, Boolean]) private propMessage;
@Prop(Number) private propA!: number;
@Prop({ default: 'default value' }) private propB!: string;
// 初始化 data
private msg: number = 123;
private helloMsg = "Hello, " + this.propMessage;
private emitMsg: string = "This is emitMsg";
// watch
@Watch('msg')
public onChildChanged(val: number, oldVal: number) {
if (val > 6) {
console.log('msg is changed');
}
}
// computed
get computedMsg() {
return "computed " + this.msg;
}
// 生命周期钩子
public beforeCreate() {
console.log('beforeCreate');
}
public created() {
this.sayMsg();
}
public beforeMount() {
console.log('beforeMount');
}
public mounted() {
this.greet();
}
public beforeUpdate() {
console.log('beforeUpdate');
}
public updated() {
console.log('updated');
}
public beforeDestroy() {
console.log('beforeDestroy');
}
public destroyed() {
console.log('destroyed');
}
// methods
public sayMsg() {
console.log('created');
}
public showEmit() {
this.greetEmit(this.emitMsg);
}
@Emit()
public greetEmit(msg: string) {
console.log('emit');
}
public greet() {
console.log("mounted - greeting: " + this.msg);
}
}
</script>
复制代码
需要特别注意的点:
- 想要使用 watch,props,emit 需要单独引用:
import { Component, Prop, Watch, Emit } from "vue-property-decorator";
复制代码
- props 和 data 需要设定 private 类型以及类型断言:
@Prop([String, Boolean]) private propMessage;
private msg: number = 123;
复制代码
- watch 的函数名并无实际意义,不冲突即可:
@Watch('msg')
public onChildChanged(val: number, oldVal: number) { // 此处函数名随意
if (val > 6) {
console.log('msg is changed');
}
}
复制代码
- emit 传值处理, 需要传入的值在调用已声明的 emit 中传入:
public showEmit() {
this.greetEmit(this.emitMsg);
}
@Emit()
public greetEmit(msg: string) {
console.log('emit');
}
复制代码
六、 起步 demo