工作记录: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(){ // .... } } |
计算属性
而计算属性,则是类的存取器的写法(getter
、setter
,对应Vue的getter
和setter
):
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 () { } } |
更加详细的内容
类型声明
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-decorators
和vuex-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的话可以看下面的网站:
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体