本文主要介绍了Vue项目引入Typescript及相关组件ts形式的编写语法
<!--more-->
Vue项目引入TypeScript
新项目支持TypeScript
对于新项目,新的Vue CLI
可以直接配置好所有TypeScript
的配置,只需要在vue create
的时候添加class-style
和TypeScript
支持即可
现有项目引入TypeScript
引入依赖
对于老项目,需要先引入相关依赖:
vue-class-component:强化 Vue 组件,使用 TypeScript/装饰器 增强 Vue 组件
vue-property-decorator:在 vue-class-component 上增强更多的结合 Vue 特性的装饰器
ts-loader:TypeScript 为 Webpack 提供了 ts-loader,其实就是为了让webpack识别 .ts .tsx文件
以及lint相关的:
tslint-loader跟tslint:作用等同于eslint
tslint-config-standard:tslint 配置 standard风格的约束
更改webpack配置
依赖引入之后,进行修改webpack配置,路径为:build/webpack.base.conf.js
- 修改
entry
:
entry: {
app: './src/main.js'
}
入口的main.js
可以修改为main.ts
,内容保持不变。(其实也可以不改为ts文件,入口文件内容不多也没那么大必要去改)
- 修改
resolve.extensions
:
resolve: {
extensions: ['.js', '.vue', '.json', '.ts'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
// ...
}
},
添加.ts
后缀之后引入ts
文件就可以不写后缀了
- 修改
module.rules
添加对ts
文件的解析:
{
test: /\.ts$/,
exclude: /node_modules/,
enforce: 'pre',
loader: 'tslint-loader'
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
- 添加
tsconfig.json
在项目根目录下下创建tsconfig.json
,在这贴一份推荐配置:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"strictPropertyInitialization": false,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"mocha",
"chai"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
值得一提的是,
strictPropertyInitialization
这个选项建议修改为false,不然会要求每个属性值都必须有初始值。
- 创建tslint.json
{
"extends": "tslint-config-standard",
"globals": {
"require": true
}
}
- 在
src
目录下创建.d.ts
声明文件
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
一般命名为shims-vue.d.ts
意为告诉TypeScript``*.vue
后缀的文件可以交给vue
模块来处理
而在代码中导入
*.vue
文件的时候,需要写上.vue
后缀。因为Typescript只识别ts文件
当想在项目中使用
window
等变量时,也需要额外书写声明文件,如declare let window: any
经过上面几步相应配置就可以支持ts了,值得一提的是typescript
版本可能会和webpack
版本不兼容,所以升级的时候需要开发自己去注意一下版本适配的问题
补充说明
比较新的ts-loader
会强制要求webpack
版本到4.0
以上,所以如果涉及到了webpack
的升级的话,需要注意一下:
html-webpack-plugin
,uglifyjs-webpack-plugin
的更新以及配置文件写法改动eslint
eslint-loader
url-loader
file-loader
的更新webpack.optimize.CommonsChunkPlugin
和webpack.optimize.UglifyJsPlugin
插件废弃,转为在配置中新增optimization
选项extract-text-webpack-plugin
loader改为使用mini-css-extract-plugin
- 同时也要注意
vue-loader
的更新,vue-loader
到15之后需要注册一个插件(这个应该都知道)
另外如果启动项目时出现类型检测错误时,可能是
Vue
不同版本的声明文件不配套导致的,比如简单的import { Vue, Component } from 'vue-property-decorator'; class App extends Vue{}
就报错了,那么需要更新Vue
版本,而无需改tsconfig.json
的strictFunctionTypes
选项。
重写.vue
文件
配置好之后就可以开始重写组件了,主要借助了vue-property-decorator,
具体的装饰器的基本使用请参见文档,这里只提一些文档中没有细说的decorator的写法:
- 注册子组件的写法:
<script lang="ts">
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
import {Vue, Component } from "vue-property-decorator";
@Component({
components: { // 在这里注册子组件 后面紧跟本身组件的类即可
HelloWorld
}
})
export default class Home extends Vue{
// ...
}
</script>
@Watch
劫持数据中的属性的写法:
<script lang="ts">
import {Vue, Component, Watch} from "vue-property-decorator";
@Component
export default class Home extends Vue{
obj: any = {
name: "ZZ",
};
@Watch("obj.name", {immediate: true, deep: true}) // 在这里直接watch属性即可
nameChange(newVal: string) {
console.log("obj.name ", newVal);
}
}
</script>
- 详细规定
@Prop
:
@Prop({
type: [String, Number],
default: "this is default msg"
}) readonly msg: string | number;
另外,对于自定义组件想
emit
@Model
中定义的事件时,不能使用@Emit
装饰器而是必须自己手动emit
- 注册钩子
除了Vue
本身的生命周期钩子之外,其他插件的钩子要使用的话是需要提前注册的,比如我们熟悉的Vue-router
提供的beforeRouterEnter
等
vue-class-component
提供了Component.registerHooks
来注册钩子,在vue-property-decorator
我们也可以直接调用
比如我们直接在App.vue
里进行注册:
<script lang="ts">
import {Vue, Component} from "vue-property-decorator";
Component.registerHooks([
"beforeRouteEnter"
]);
@Component
export default class App extends Vue {
}
</script>
在子组件里面就可以自由使用这些钩子了:
export default class Home extends Vue{
beforeRouteEnter():void {
console.log("beforeRouteEnter");
}
}
- 方法内部
this
的指向问题
使用ts的话,现有2种方法定义:
export default class Home extends Vue{
foo = () => {
console.log(this);
}
bar() {
console.log(this);
}
}
对于
foo = () => {
console.log(this); // 内部this为Home的实例
}
这种定义方法,内部的this其实是为Home这个类的实例,而对于:
bar() {
console.log(this); // 内部this为Vue示例
}
其内部的this
才为当前Vue
组件的实例
所以注意要使用:
bar() {
console.log(this); // 内部this为Vue示例
}
这种格式。
- Mixin的实现
Mixin的实现就是一个简单的类的继承
假设现在要混入如下方法和属性:
// TestMixin.ts
import {Vue, Component} from "vue-property-decorator";
@Component
export default class TestMixin extends Vue{
mixinVal: string = "abc";
mixinMethod(): void {
console.log("mixinMethod");
}
}
我们在要混入的组件中就可以直接继承该类而不是Vue了:
import {Component} from "vue-property-decorator";
@Component
export default class Home extends TestMixin{
created() {
console.log(this.mixinVal);
this.mixinMethod();
}
}
vuex-class
的使用
我们使用了ts之后,对于Vuex
的使用方式也要得到更新,首先安装vuex-class
:
npm install -S vuex-class
比方说我现在store
文件夹下有一个index.ts
如下,作为我Vuex
的主模块来使用:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
stateVal1: "123"
},
getters: {
getStateVal1: state => state.stateVal1
},
mutations: {
changeStateVal(state, payload?: string): void {
if(!payload) {
state.stateVal1 = "abc";
}else {
state.stateVal1 = payload;
}
}
},
actions: {
consoleStateVal(ctx): Promise<string> {
return new Promise((resolve: (stateVal: string) => void) => {
setTimeout(() => {
ctx.commit("changeStateVal", "def");
resolve(ctx.state.stateVal1);
}, 3000)
});
}
},
})
我们在组件内部就可以这么使用:
<script lang="ts">
import {Vue, Component} from "vue-property-decorator";
import {
State,
Getter,
Action,
Mutation,
namespace
} from 'vuex-class';
const TestVuexModule = namespace('@/store/TestVuexModule.ts'); // 此处引入的是别的module中的store
@Component
export default class Home extends Vue{
@State stateVal1; // 同名情况可以直接引用
@Getter("getStateVal1") getStateVal; // 也可以重新命名 比方说store/index.ts下为getStateVal1, 在这重命名为为getStateVal
@Mutation changeStateVal;
@Action consoleStateVal;
@TestVuexModule.State TestVal1; // 注册别的store中的状态
created() {
console.log(this.getStateVal);
this.changeStateVal();
console.log(this.getStateVal);
this.consoleStateVal().then(res => {
console.log("res", res);
console.log(this.getStateVal);
})
console.log(this.TestVal1); // 获取到别的store中的state
}
}
</script>