工作记录: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,选择根据样式重新设置格式、选择启用该模板

 

内容区域输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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选项中新增如下代码:

1
parser: '@typescript-eslint/parser'

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

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

 

修改声明文件

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

1
2
3
4
5
6
7
8
9
10
11
12
13
declare module 'vue/types/vue' {
  interface Vue {
    $style: {
      [key: string]: string;
    };
  }
 
  interface VueConstructor {
    $style: {
      [key: string]: string;
    };
  }
}

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

组件中使用

基本用法

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

组件引用

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

1
2
3
4
5
@Component({
  components: {
    MapLegend: () => import('@/components/legend/index.vue')
  }
})

过滤器

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

1
2
3
4
5
6
7
@Component({
  filters: {
    dateFormat (date:Date) {
      return dateFormat(date)
    }
  }
})

指令

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

1
2
3
4
5
6
7
8
9
@Component({
  directives:{
    focus: {
      inserted: function (el) {
        el.focus()
      }
    }
  }
})

props

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

1
2
3
4
5
6
7
8
9
10
11
12
@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属性也会随之改变):

1
2
3
4
5
6
7
8
9
10
11
12
@Component
export default class Test extends Vue {
  @PropSync('name',{
    type:String,
    default:'no name',
    required:false
  }) name!:string
 
  mounted(){
    this.name='nichols'
  }
}

侦听器(watch)

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

1
2
3
4
5
6
7
8
9
10
11
@Component
export default class Test extends Vue {
  isShow=false
  @Watch('isShow',{
    deep:true,
    immediate:true
  })
  onIsShowChange(val:boolean,newVal:boolean){
 
  }
}

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

1
2
3
mounted(){
  this.onIsShowChange(true,false)
}

emit

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

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
export default class Test extends Vue {
  name = ''
 
  @Emit('change')
  onChange (name: string) {
    this.name = name
  }
 
  mounted () {
    this.onChange('nichols')
  }
}

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

ref

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

1
2
3
4
5
6
7
8
9
10
@Component
export default class Test extends Vue {
  name = ''
 
  @Ref('form') form!:HTMLElement
 
  mounted(){
    console.log(this.form.offsetHeight)
  }
}

data

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
export default class Test extends Vue {
  isShow = false
  form: {
    username: string;
    password: string;
  } = {
    username: '',
    password: ''
  }
 
  mounted () {
    this.isShow = true
  }
}

函数(methods)

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

1
2
3
4
5
6
@Component
export default class Test extends Vue {
  log(){
    // ....
  }
}

计算属性

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
export default class Test extends Vue {
  mounted () {
  }
 
  created () {
  }
 
  updated () {
  }
 
  beforeDestroy () {
  }
 
  destroyed () {
  }
}

更加详细的内容

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

类型声明

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

1
2
3
4
5
interface Event{
  DataTransfer:{
    setData():void
  }
}

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

1
2
3
4
5
export interface User{
  id:string;
  name:string;
  realName:string;
}

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

1
2
3
4
5
6
7
8
9
10
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.修改默认导出为类样式:

1
2
3
export default {
  name:'Component1'
}

修改为:

1
2
3
@Component
export default class Component1 extends Vue{
}

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

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

修改js文件

1.js文件后缀改为.ts

2.添加类型约束

vuex的使用

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

安装

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

创建Store

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

1
2
3
4
5
6
7
8
9
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

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

创建模块语法如下:

1
2
3
4
5
6
7
8
9
10
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定义类似:

1
2
3
4
5
6
7
8
9
10
@Module({
  dynamic: true,//启用动态模块
  name: 'User',
  store,//注入store
  namespaced: true,
  stateFactory: true//开启工厂模式
})
export default class User extends VuexModule {
  token = getToken()
}

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

1
2
3
4
5
6
export default {
    state:{
    token: getToken()
  },
  namespaced:true
}

@Mutation

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

是否允许提交根荷载

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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('/')
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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会同时定义并提交荷载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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前置于方法名:

1
2
3
4
5
6
7
8
@Module
class MyModule extends VuexModule {
  wheels = 2
 
  get axles() {
    return this.wheels / 2
  }
}

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 @   林恒  阅读(636)  评论(0编辑  收藏  举报
编辑推荐:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
欢迎阅读『工作记录:TypeScript从入门到项目实战(项目篇)』
点击右上角即可分享
微信分享提示