工作记录:TypeScript从入门到项目实战(项目篇)

Vue项目中使用

前面两篇介绍过TypeScript基础和较深入的东西,本章介绍如何在Vue项目中使用。

项目创建

创建项目直接使用Vue-cli创建

下面是步骤:

1.运行vuecli,

2.选择合适的目录创建项目

3.输入项目名并,选择包管理器,输入git仓库初始化内容

4.设置预设,如果你之前有合适的预设,可以设置该预设,这里选择手动

5.选择功能,其中TypeScript和babel必选,其他功能视项目而定:

6.设置配置,开启类样式组件语法(第一项),选择eslint配置为ESLint+Standard(第五项),开启保存时检查和修复代码

7.创建项目

8.安装一些插件

编辑器文件支持设置

这里以我们以后统一使用的Webstrom为例:

找到Editor>File and COde Templates

 

新建一个代码模板,输入名称,扩展名为vue,选择根据样式重新设置格式、选择启用该模板

 

内容区域输入:

<template lang="pug">
#[[$END$]]#
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

@Component
export default class ${COMPONENT_NAME} extends Vue {
  
}
</script>

<style  lang="stylus" rel="stylesheet/stylus" module>

</style>

点击应用之后,新建文件时选择刚建立的模板:

因为选择的是类样式语法,所以需要填入类名:

 

修改eslint、编辑器配置

修改eslint配置文件以支持检查TypeScript文件 

.eslintrc.js文件的parserOptions选项中新增如下代码:

parser: '@typescript-eslint/parser'

修改编辑器配置以支持保存时自动修复代码

webstrom强大之处是对各种技术的支持,比如eslint检查代码。正常情况下,我们需要通过命令行检查代码,这非常麻烦,不过webstrom能够配置eslint,编辑器内检查不合eslint配置的代码,并且支持保存时修复。配置如下:

 

修改声明文件

对于我们自定义的插件、全局方法、全局变量等,TypeScript并不知道他们,为了让TypeScript认识他们,我们可以通过声明文件告诉TypeScript,如果使用model样式时的$style,我们修改shims-tsx.d.ts文件,在行末添加下面代码:

declare module 'vue/types/vue' {
  interface Vue {
    $style: {
      [key: string]: string;
    };
  }

  interface VueConstructor {
    $style: {
      [key: string]: string;
    };
  }
}

对于其他内容和上面方法类似

组件中使用

基本用法

因为使用类样式写组件中的js,所以写法上会略有不同,但是本质上还是不变的。

组件引用

组件引用通过传递参数components给装饰符@Component

@Component({
  components: {
    MapLegend: () => import('@/components/legend/index.vue')
  }
})

过滤器

过滤器和组件类似,通过传递filters参数,filters对象内定义局部过滤器:

@Component({
  filters: {
    dateFormat (date:Date) {
      return dateFormat(date)
    }
  }
})

指令

局部指令和过滤器类似,通过@Component装饰器传递参数directives

@Component({
  directives:{
    focus: {
      inserted: function (el) {
        el.focus()
      }
    }
  }
})

props

props不再通过对象属性形式定义,而是通过@Prop装饰器定义,其配置内容通过参数形式传入装饰器:

@Component
export default class Test extends Vue {
  @Prop({
    type:String,
    default:'no name',
    required:false
  }) name!:string

  mounted(){
    console.log(name)
  }
}

props同步版

通过@PropSync可以创建一个props属性的同步版本(即:变量改变,所对应的props属性也会随之改变):

@Component
export default class Test extends Vue {
  @PropSync('name',{
    type:String,
    default:'no name',
    required:false
  }) name!:string

  mounted(){
    this.name='nichols'
  }
}

侦听器(watch)

类似的,侦听器通过@Watch装饰器定义,装饰器接收两个参数,第一个监视哪个变量,第二个其他配置:

@Component
export default class Test extends Vue {
  isShow=false
  @Watch('isShow',{
    deep:true,
    immediate:true
  })
  onIsShowChange(val:boolean,newVal:boolean){

  }
}

侦听器同样可以被当做方法调用,只是执行其内部逻辑:

  mounted(){
    this.onIsShowChange(true,false)
  }

emit

在组件中触发事件让父组件侦听到是一个非常常用的操作,通过@Emit装饰符定义,所定义的函数可以被当做普通函数调用:

@Component
export default class Test extends Vue {
  name = ''

  @Emit('change')
  onChange (name: string) {
    this.name = name
  }

  mounted () {
    this.onChange('nichols')
  }
}

其中如果有返回值,则返回值会作为触发的参数放在前面,而传入参数会放到返回值后面

ref

定义ref使用@Ref装饰器定义:

@Component
export default class Test extends Vue {
  name = ''

  @Ref('form') form!:HTMLElement

  mounted(){
    console.log(this.form.offsetHeight)
  }
}

data

对于组件内的数据,我们可以直接使用类属性定义组件的数据:

@Component
export default class Test extends Vue {
  isShow = false
  form: {
    username: string;
    password: string;
  } = {
    username: '',
    password: ''
  }

  mounted () {
    this.isShow = true
  }
}

函数(methods)

函数与data定义类似,为类添加一个方法即可:

@Component
export default class Test extends Vue {
  log(){
    // ....
  }
}

计算属性

而计算属性,则是类的存取器的写法(gettersetter,对应Vue的gettersetter):

@Component
export default class Test extends Vue {
  lastName = '尼古拉斯'
  firstName = '赵四'

  get name (): string {
    return `${this.firstName}·${this.lastName}`
  }

  set name (name: string) {
    const names = name.split('·')
    this.firstName = names[0]
    this.lastName = names[0]
  }
}

生命周期

可以直接定义所对应的钩子名称,或者借助vue-class-component/hooks.d.ts完成:

@Component
export default class Test extends Vue {
  mounted () {
  }

  created () {
  }

  updated () {
  }

  beforeDestroy () {
  }

  destroyed () {
  }
}

更加详细的内容

更详细参考vue-property-decorator文档

类型声明

src目录下types目录下,创建index.d.ts(或者更详细的文件名),然后定义类型,这里以扩展Event为例

interface Event{
  DataTransfer:{
    setData():void
  }
}

为了避免全局变量混乱,可以使用export导出我们想要被外部访问的声明:

export interface User{
  id:string;
  name:string;
  realName:string;
}

需要使用时,再在需要使用的地方导入即可:

import { User } from '@/types/user'

@Component
export default class Test extends Vue {
  user:User={
    id: '',
    name: '',
    realName: ''
  }
}

旧项目的迁移

安装插件

1.启动vue ui(一把梭,就是干!),在插件项中点击添加插件,

2.搜索TypeScript,选择@vue/cli-pluging-typescript,点击安装即可

修改组件

1.script标签添加属性lang="ts"

2.组件引入添加.vue后缀名

3.修改默认导出为类样式:

export default {
  name:'Component1'
}

修改为:

@Component
export default class Component1 extends Vue{
}

4.按照基本用法,将对应的数据更改为类样式

5.按照编辑器报错提示添加或者修改类型注释

修改js文件

1.js文件后缀改为.ts

2.添加类型约束

vuex的使用

vuex和vue组件使用方式类似,使用类样式+装饰器的形式定义,使用的依赖是vuex-module-decoratorsvuex-class

安装

yarn add vuex-module-decorators vuex-class
npm i vuex-module-decorators vuex-class

创建Store

Store的创建和常规创建方式一致,只是Vuex.Store参数中无需传入任何参数:

import Vue from 'vue'
import Vuex from 'vuex'
import User from '@/store/modules/user'
import getters from '@/store/getters'

Vue.use(Vuex)

export default new Vuex.Store({
})

定义模块

@Module

使用@Module定义一个vuex模块,接收如下参数:

 

属性

数据类型

描述

name

string

模块的名称(如果有名称空间)

namespaced

boolean

模块是否具有名称空间

stateFactory

boolean

是否开启状态工厂(方便模块复用)

dynamic

true

如果这是一个动态模块(在创建存储后添加到存储)

 

store

Store<any>

将注入此模块的存储区(用于动态模块)

preserveState

boolean

如果启用此选项,则加载模块时将保留状态

创建模块语法如下:

import { VuexModule } from 'vuex-module-decorators'
@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
}

state

state的定义和组件中的data定义类似:

@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  token = getToken()
}

上面代码和下面代码效果一样:

export default {
	state:{
  	token: getToken()
  },
  namespaced:true
}

@Mutation

mutation的定义使用@Mutation装饰器定义:

@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  token = getToken()

  @Mutation
  setToken (token: string) {
    this.token = token
    token ? setToken(token) : deleteToken()
  }
}

@Action

使用@Action装饰器定义action,该装饰器接收三个参数:

 

参数名

类型

描述

commit

string

所提交的荷载

rawError

boolean

是否打印原始错误类型(默认会对报错信息进行包装)

root

boolean

是否允许提交根荷载

如果不传入参数,需要手动提交荷载:

@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  token = getToken()

  @Mutation
  setToken (token: string) {
    this.token = token
    token ? setToken(token) : deleteToken()
  }

  @Action
  async login () {
    this.context.commit('setToken', 'token')
    router.replace('/')
  }
}

如果指定提交的荷载名,可通过函数的返回值设定荷载值:

@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  token = getToken()

  @Mutation
  setToken (token: string) {
    this.token = token
    token ? setToken(token) : deleteToken()
  }

  @Action({commit:'setToken'})
  async login ():string {
    router.replace('/')
    return 'token'
  }
}

@MutationAction  

有时候简单地数据操作,mutation会显得有点多余,这时候,我们可以使用@MutationAction装饰器将mutatioin和action合二为一,用此装饰器定义的action会同时定义并提交荷载:

@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  testStr = ''

  @MutationAction({ mutate: ['testStr'] })
  async setStr () {
    return new Promise<{ testStr: string }>(resolve => {
      resolve({
        testStr: 'test'
      })
    })
  }
}

需要注意的是:返回对象的数据结构必须和指定的参数名一致

getter

getter的定义和Vue组件中的计算属性定义类似,使用get前置于方法名:

@Module
class MyModule extends VuexModule {
  wheels = 2

  get axles() {
    return this.wheels / 2
  }
}

完整示例

import {
  deleteRefreshToken,
  deleteToken,
  deleteUserInfo,
  getRefreshToken,
  getToken,
  getUserInfo,
  setRefreshToken,
  setToken,
  setUserInfo
} from '@/utils/auth'
import { UserInfo } from '@/types/user'
import router from '@/router'
import { Action, Module, Mutation, MutationAction, VuexModule } from 'vuex-module-decorators'

@Module({
  dynamic: true,//启用动态模块,模块将在调用getModule时注册
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  token = getToken()
  refreshToken = getRefreshToken()
  userInfo: UserInfo | null = getUserInfo()
  testStr = ''

  @Mutation
  setToken (token: string) {
    this.token = token
    token ? setToken(token) : deleteToken()
  }

  @Mutation
  setRefreshToken (token: string) {
    this.refreshToken = token
    token ? setRefreshToken(token) : deleteRefreshToken()
  }

  @Mutation
  setUserInfo (user: UserInfo | null) {
    this.userInfo = user
    user ? setUserInfo(user) : deleteUserInfo()
  }

  @MutationAction({ mutate: ['testStr'] })
  async setStr () {
    return new Promise<{ testStr: string }>(resolve => {
      resolve({
        testStr: 'test'
      })
    })
  }

  @Action
  async login () {
    this.context.commit('setToken', 'token')
    this.context.commit('setRefreshToken', 'refreshToken')
    this.context.commit('setUserInfo', {})
    router.replace('/')
  }

  @Action
  async loginOut () {
    this.context.commit('setToken', '')
    this.context.commit('setRefreshToken', '')
    this.context.commit('setUserInfo', null)
    router.replace('/login')
  }
}

组件中使用

组件中通过getModule()方法进行获取到模块,可以通过定义计算属性以使用state

import { Component, Vue } from 'vue-property-decorator'
import User from '@/store/modules/user'
import { getModule } from 'vuex-module-decorators'
import { namespace } from 'vuex-class'

let user:User = getModule(User)

@Component
export default class test extends Vue {
  //动态数据,使用user.userInfo获取的并不是响应式数据
  @namespace('User').State('userInfo') userInfo: UserInfo
  
  login ():void {
    console.log(user.testStr)
    user.login()
  }
  
  get token ():string {
    return user.token
  }

  mounted ():void {
    
  }
}

小程序中使用

小程序中使用TypeScript比较简单,在创建项目时选择语言为TypeScript,其他的和Vue类似项目类似

不同的是,Vue项目会自动编译TypeScript,而小程序需要手动编译ts文件,这显得有点麻烦,所以我们可以使用webstrom开发小程序:

安装小程序插件,让webstrom支持小程序语法:File>Setting>Plugins,搜索Wechat mini programs upport,完成之后重启webstrom,这时候我们可以看到在右键菜单New那一项里面多了两个小程序选项:

配置ts文件自动编译:File>Setting>Languages & Frameworks>TypeScript,选中改变时重新编译:

 

扩展

本系列文章全面结合项目编写,如果你还想深入学习TypeScript的话可以看下面的网站:

TypeScript中文网

TypeScript 入门教程 

TypeScript 学习资源合集

 

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

 

posted @ 2021-10-02 16:42  林恒  阅读(594)  评论(0编辑  收藏  举报