赞
踩
参考vue-typescript-admin-element-ui 基于Vue+typescript版的后台管理系统模板。
众所周知,js是一门弱类型的语言,尤其是在变量赋值时,永远都是给变量直接赋值各种类型值来初始化,线上一些隐藏的bug就冷不防会暴露出来。把这种错误扼杀在项目开发编译阶段而非上线阶段,所有就有了typescript超集的出现。那Vue中是怎么引用typescript项目的呢
vue-property-decorator在vue-class-component的基础上增加了更多与Vue相关的装饰器,使Vue组件更好的跟TS结合使用。这两者都是离不开装饰器的,(decorator)装饰器已在ES提案中。Decorator是装饰器模式的实践。装饰器模式呢,它是继承关系的一个替代方案。动态地给对象添加额外的职责。在不改变接口的前提下,增强类的性能。
vue-property-decorator是这个Vue项目文件中完全依赖的库,它是Vue官方推荐的并且依赖于vue-class-component,先介绍下它在项目中的常见用法。
@Component
@Emit
@Provice @Inject
@Prop
@Watch
@Model
@Minxins
首先,Vue页面中的script部分要加一个lang=ts,这样安装好typescript正能引用
<script lang="ts"> import {Vue, Component} from 'vue-property-decorator'; import BaseHeader from '@/components/BaseHeader'; //公共头部组件 @Component({ components: { BaseHeader } }) export default class extends Vue { private stateA:boolean = true private stateB:string = '' private stateC:number = 0 private stateD:any = {} stateE:any[] = [] } </script>
等同于
<script> import Vue from 'vue'; import BaseHeader from '@/components/BaseHeader'; //公共头部组件 export default { components: { BaseHeader }, data(){ return { stateA: true, stateB: '', stateC: 0, stateD: {}, stateE: [] } } } </script>
vue-property-decorator在项目中的应用最主要是起一个装饰器的作用,差异化的话看对比就非常直观了
data变量的定义比较多元化,这里区别有加private,不加就是public,当变量标记为private时,它就不能在声明它的类的外部访问。
@Component装饰器属性名必须得写上
父子组件之间的属性传值
export default class extends Vue {
@Prop({ default: 0 }) private propA!: number
@Prop({ default: () => [10, 20, 30, 50] }) private propB!: number[]
@Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private propC!: string
@Prop({ default: true }) private propD!: boolean,
@prop([String, Boolean]) propE: string | boolean;
}
等同于
export default { props: { propA: { type: Number }, propB: { type: Array, default: [10, 20, 30, 50] }, propC: { type: String, default: 'total, sizes, prev, pager, next, jumper' }, propD: { type: String, default: 'total, sizes, prev, pager, next, jumper' }, propE: { type: [String, Boolean] } } }
这里有两个常用修饰符!``?,!和可选参数?是相对的, !表示强制解析(也就是告诉typescript编译器,我这里一定有值),你写?的时候再调用,typescript会提示可能为undefined
Component export default class YourComponent extends Vue { count = 0 @Emit('reset') resetCount() { this.count = 0 } @Emit() returnValue() { return 10 } @Emit() onInputChange(e) { return e.target.value } }
等同于
export default { data() { return { count: 0 } }, methods: { resetCount() { this.count = 0 this.$emit('reset') }, returnValue() { this.$emit('return-value', 10) }, onInputChange(e) { this.$emit('on-input-change', e.target.value, e) } } }
@Emit装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杠式写法)的事件, 并将其函数传递给$emit
@Emit触发事件有两种写法
@Watch装饰器主要用于替代Vue属性中的watch属性,监听依赖的变量值变化而做一系列的操作
@Component
export default class YourComponent extends Vue {
@Watch('child')
onChildChanged(val: string, oldVal: string) {}
@Watch('person', { immediate: true, deep: true })
onPersonChanged(val: Person, oldVal: Person) {}
}
等同于
export default {
watch: {
child(val, oldVal) {},
person: {
handler(val, oldVal) {},
immediate: true,
deep: true
}
}
}
watch 是一个对象,对象就有键,有值。
@Watch使用非常简单,接受第一个参数为要监听的属性名, 第二个属性为可选对象。@Watch所装饰的函数即监听到属性变化之后应该执行的函数。
@Watch装饰的函数的函数名并非如上onStateChanged严格命名,它是多元化的,你可以随心所欲的命名,当然,能按照规范化的命名会使你的代码阅读性更好。
// myMixin.ts
@Component
export default class MyMixin extends Vue {
mixinValue:string = 'Hello World!!!'
}
// 引用mixins
import MyMixin from './myMixin.js'
@Component
export default class extends mixins(MyMixin) {
created () {
console.log(this.mixinValue) // -> Hello World!!!
}
}
然后我又偷学到了另外一种mixins写法,记录一下
先改造一下myMixin.ts,定义vue/type/vue模块,实现Vue接口
// myMixin.ts
import { Vue, Component } from 'vue-property-decorator';
declare module 'vue/types/vue' {
interface Vue {
mixinValue: string;
}
}
@Component
export default class myMixins extends Vue {
mixinValue: string = 'Hello World!!!'
}
引用
import { Vue, Component, Prop } from 'vue-property-decorator';
import MyMixin from './myMixin.js'
@Component({
mixins: [MyMixin]
})
export default class extends Vue{
created(){
console.log(mixinValue) // => Hello World!!!
}
}
两种方式不同在于定义mixins时如果没有定义vue/type/vue模块, 那么在混入的时候就要继承该mixins; 如果定义vue/type/vue模块,在混入时可以在@Component中mixins直接混入。
@Model装饰器允许我们在一个组件上自定义v-model,接收两个参数:
import { Vue, Component, Model } from 'vue-property-decorator'
@Component
export default class MyInput extends Vue {
@Model('change', { type: String, default: 'Hello world!!!' }) readonly value!: string
}
等同于
<template> <input type="text" :value="value" @change="$emit('change', $event.target.value)" /> </template> export default { model: { prop: 'value', event: 'change' }, props: { value: { type: String, default: 'Hello world!!!' } } }
@Provide 声明一个值 , 在其他地方用 @Inject 接收,在实战项目中用得不多,一般用于不依赖于任何第三方状态管理库(如vuex)的组件编写
@Ref装饰器接收一个可选参数,用来指向元素或子组件的引用信息。如果没有提供这个参数,会使用装饰器后面的属性名充当参数
import { Vue, Component, Ref } from 'vue-property-decorator' import { Form } from 'element-ui' @Componentexport default class MyComponent extends Vue { @Ref() readonly loginForm!: Form @Ref('changePasswordForm') readonly passwordForm!: Form public handleLogin() { this.loginForm.validate(valide => { if (valide) { // login... } else { // error tips } }) } }
等同于
export default { computed: { loginForm: { cache: false, get() { return this.$refs.loginForm } }, passwordForm: { cache: false, get() { return this.$refs.changePasswordForm } } } }
使用时切记要引入修饰器
import {
Vue,
Component,
Prop,
Component,
Emit,
Provice,
Inject,
Watch,
Model,
Minxins,
} from 'vue-property-decorator'
以下的public、private在引入tslint后是必写的,否则会有警告,如果没有引的话是可以不写的
Ts | Js | 说明 |
---|---|---|
public created() {} | created() {} | 初始化 |
public mounted() {} | mounted() {} | 挂载完毕 |
private _getInitData() {} | methods: { _getInitData() {} } | 方法 |
private get _userName() | {} computed: { _userName() {} } | 计算属性 |
public destroyed() {} | destroyed() {} | 销毁生命周期 |
传统的vuex在vue+ts的项目里面是行不通的,vue 2.0版本对ts的兼容性本身并不是特别友好,所以要达到状态管理的效果,这里要额外引用一个类库vuex-module-decorators,它是基于vue-class-component 所做的拓展,它提供了一系列的装饰器,让vue+ts结合的项目达到状态管理的作用。
import { VuexModule, Module, Action, Mutation, getModule, State } from 'vuex-module-decorators'
.
├─ src/
│ ├─ store/
│ ├─── modules/
│ │ ├─ app.ts
│ │ ├─ user.ts
│ ├─── index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import { IAppState } from './modules/app'
import { IUserState } from './modules/user'
Vue.use(Vuex)
export interface IRootState {
app: IAppState
user: IUserState
}
// Declare empty store first, dynamically register all modules later.
export default new Vuex.Store<IRootState>({})
等同于
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
user
}
})
export default store
这样,模块化状态管理的雏形就完成了。对比来看,只是语法风格的变化,其它的变化不大。ts版的状态管理最大的改变体现在各个功能功能函数上
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
});
为了显得不那么啰嗦,直接上版ts版的状态管理吧,可以有个直观的对比
// user.ts import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators' import store from '@/store' export interface IUserState { id_token: string } @Module({ dynamic: true, store, name: 'user' }) class User extends VuexModule implements IUserState { public id_token = '' @Mutation private SET_TOKEN(token: string) { this.id_token = token } @Action public async Login(params: any) { this.SET_TOKEN(`token!!!`) } } export const UserModule = getModule(User)
解析:
我们看到了一堆的@开头的装饰器函数@Mutation @Mutation @Module…
先来一张表格对比一下差异化吧
Ts | Js |
---|---|
public State | state |
@Mutations | mutations |
@Action | action |
get | getters |
定义一个modules,直接使用装饰器@Module
注意:原始的vuex同样有一个名为Module的类,但它不是一个装饰器,所以别用混淆了
@Module({ dynamic: true, store, name: 'user' })
从上面可以看到,我们定义modules不单单用了装饰器,还带了参数值,这个是表明是通过命名空间的形式来使用module,如上,这里的namespaced值即为user
详细vuex命名空间的说明,可以参考vuex命名空间
除了namespaced,我们看到还有另外一个参数值store,它即为主入口页对应的整个vuex模块的store
import store from '@/store'
如果去掉它的话,浏览器会报错误
这里所有的state属性因为加了tslint都会添加上public修饰,其它的用法都是相似的
原始的getters计算函数,在这里对应的即使get方法,即
@Module
export default class UserModule extends VuexModule {
countsNum = 2020
get calculatCount() {
return countsNum / 2
}
}
等同于
export default {
state: {
countsNum: 2
},
getters: {
calculatCount: (state) => state.countsNum / 2
}
}
@Mutation
private SET_TOKEN(token: string) {
this.token = token
}
@Mutation
...
等同于
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
...
}
说明:
两者的区别其实就是语法糖,原始的Mutation同步方法都是定义在mutations内,而ts版的每一个Mutation都要加上装饰器@Mutation修饰
注意: 一旦使用@Mutation装饰某一函数后, 函数内的this上下文即指向当前的state,所以想引用state的值,可以直接this.token访问即可。
Muation函数不可为async函数, 也不能使用箭头函数来定义, 因为在代码需要在运行重新绑定执行的上下文
@Action
public async Login(userInfo: { username: string, password: string}) {
...
this.SET_TOKEN(data.accessToken)
}
等同于
actions: {
async Login({ commit }, data) {
...
commit('SET_TOKEN', data.accessToken)
}
}
说明:
异步函数Action和同步函数Mutation使用方法大同小异,区别就是一个是同步,一个是异步,只要做好区分即可
注意:
import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators' import { login } from '@/api/users' //调用api方法 import store from '@/store' //声明user模块的state变量类型 //export interface 只是对一个东西的声明(不能具体的操作) //export class 导出一个类 类里面可有参数 ,函数,方法(干一些具体的事情) export interface IUserState { id_token: string } @Module({ dynamic: true, store, name: 'user' }) class User extends VuexModule implements IUserState { public id_token = '' @Mutation private SET_TOKEN(token: string) { //同步存储id_token变量 this.id_token = token } @Action public async Login(params: any) { let { mobilePhone, password } = params const { data } = await login({ mobilePhone, password }) this.SET_TOKEN(`Bearer ${data.id_token}`) } } export const UserModule = getModule(User)
在login页面中调用
import { UserModule } from '@/store/modules/user'
await UserModule.Login({
...this.loginForm,
router: this.$router
})
把路由对象作为参数传过去是为了根据不同的响应状态做判断,当请求成功后,可以直接应用传过来的路由对象参数跳转页面。
router.push('/')
注意:
这一步操作其实是调用了vuex的Action操作,即原始的this. s t o r e . c o m m i t ( ′ a c t i o n ′ ) , 但 是 在 v u e x + t s 项 目 中 , 调 用 异 步 函 数 A c t i o n , 不 需 要 再 用 t h i s . store.commit('action'),但是在vuex+ts项目中,调用异步函数Action,不需要再用this. store.commit(′action′),但是在vuex+ts项目中,调用异步函数Action,不需要再用this.store.commit(‘action’)这种方法,引用模块后,直接调用里面的Action方法就好了,同样的,同步的Mutation也是这样调用。这些都要归功于vuex-module-decorators类库的封装
好了,调用Action后粗发Mutation同步操作,保存好token令牌,因为登录之后所有的请求都要把token值放在header头中发起请求
除了vuex状态管理,在项目中可能我们还会结合工具类js-cookie一起使用,管理各种变量的值,具体用法跟原始版没有什么区别,最主要的是安装类库的过程中,还得安装一个开发ts编译版
yarn add js-cookie // dependencies yarn add @types/js-cookie --dev // devDependencies(必装)
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' //公共头部组件 @Component({ components: {} }) export default class "组件名" extends Vue { private loading:bollen = false // data变量 private get pageSize() { // 计算属性 return 10 } private created() { ... } private mounted() { ... } private handleLogin() { ... } // methods方法 public destroyed() {} // 销毁声明周期 } </script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。