原文地址: How to write a Vue.js app completely in TypeScript[1]
原文作者:Preetish HS[2]
译者:Gopal
译者推荐:Typescript
和 Vue
都是现在前端必备的知识,本文基本覆盖了目前 Vue 2.x
的一些基础用法的 Typescript
版本实现,感兴趣的了解一下,更好的迎接 Vue 3.0
译者根据文章中提到的知识点,自己写了一个 demo
,已放到 Github
地址[3] ,建议大家可以动手实战一下,如果遇到什么问题,可以留言评论或者提 issue
Vue
是一个惊人的,轻量的渐进式前端框架。因为 Vue
是灵活的,所以用户不需要使用 Typescript
。但是不像 Angular
,老版本的 Vue
并没有很好的支持 Typescript
。因为这点,大多数 Vue
应用程序都是直接使用 JavaScript
写的。
现在随着官方对 Typescript
的支持,使用 Vue CLI
可以从头开始创建 Typescript
项目。但是我们仍然需要一些带有自定义装饰器和功能的第三方包来创建一个真正的、完整的 Typescript
应用程序,而官方文档并不包含入门所需要的所有信息。
为了帮助大家全面地了解它,我们将演示如何使用 Vue CLI
构建一个新的Vue TypeScript
应用程序。
开始
用下面这句代码开始
代码语言:javascript复制vue create typescript-app
选择手动选择功能并进行配置,如下所示
项目搭建好之后,我们将项目跑起来
代码语言:javascript复制cd typescript-app
npm run serve
会自动打开 localhost:8080
网页(或者会在你启动项目后打印出这个链接),这代表我们项目启动成功
我们通过这个教程,我们会回顾以下功能,并展示如何使用 Typescript
去实现
1.基于类的组件 2.Data
, props
, computed
属性, methods
, watchers
, and emit
3.生命周期 4.Mixins
5.Vuex
在 components
目录中打开 HelloWorld.vue
,你会看到如下结构
注意:对于每个实例,我将同时显示 TypeScript
和 Javascript
等价代码,这样您就可以轻松地比较两者。让我们开始吧
1.基于类的组件
代码语言:javascript复制//Typescript code
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class HelloWorld extends Vue {
}
</script>
Javascript
等价代码如下:
<script>
export default {
name: 'HelloWorld'
}
</script>
为了使用 Typescript
,我们首先需要设置 <script>
的 lang
属性为 ts
是一个第三方包,它使用官方的 vue-class
组件包,并在此基础上添加了更多装饰器。
vue-property-decorator
是一个第三方包,它使用了 Vue
类组件包,并在此基础上添加了更多的装饰器。我们也可以显式地使用 name
属性来命名组件,但是使用它作为类名就足够了。
@component({
name: 'HelloWorld'
})
引入一个组件
在组件中注册其他组件的代码是在 @Component
装饰器中编写的,如下所示。
<template>
<div class="main">
<project />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Project from '@/components/Project.vue'
@Component({
components: {
project
}
})
export default class HelloWorld extends Vue {
}
</script>
Javascript
等价代码如下:
<template>
<div class="main">
<project />
</div>
</template>
<script>
import Project from '@/components/Project.vue'
export default {
name: 'HelloWorld',
components: {
project
}
})
</script>
2. Data, props, computed 属性, methods, watchers, and emit
使用 data
要使用 data
属性,我们可以简单地将它们声明为类变量。
@Component
export default class HelloWorld extends Vue {
private msg: string = "welcome to my app"
private list: Array<object> = [
{
name: 'Preetish',
age: '26'
},
{
name: 'John',
age: '30'
}
]
}
Javascript
等价代码如下:
export default {
data() {
return {
msg: "welcome to my app",
list: [
{
name: 'Preetish',
age: '26'
},
{
name: 'John',
age: '30'
}
]
}
}
使用 props
我们可以使用 @Prop
装饰器在 Vue
组件中使用 props
。在 Vue
中,我们可以给额外的配置给 props
,比如 required
、default
和 type
。首先我们可以像下面一样从 vue-property-decorator
引入 Prop
装饰器。我们还可以使用 readonly
去避免操作改变 props
。
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class HelloWorld extends Vue {
@Prop() readonly msg!: string
@Prop({default: 'John doe'}) readonly name: string
@Prop({required: true}) readonly age: number
@Prop(String) readonly address: string
@Prop({required: false, type: String, default: 'Developer'}) readonly job: string
}
</script>
Javascript
等价代码如下:
props: {
msg,
name: {
default: 'John doe'
},
age: {
required: true,
},
address: {
type: String
},
job: {
required: false,
type: string,
default: 'Developer'
}
}
}
Computed 属性
计算属性用于编写简单的模板逻辑,例如操作、添加或连接数据。在 TypeScript
中,一个普通的计算属性也以 get
关键字作为前缀。
export default class HelloWorld extends Vue {
get fullName(): string {
return this.first ' ' this.last
}
}
Javascript
等价代码如下:
export default {
fullName() {
return this.first ' ' this.last
}
}
在 Typescript
中,我们可以写一些复杂的包括 getter
和 setter
的计算属性,如下所示
export default class HelloWorld extends Vue {
get fullName(): string {
return this.first ' ' this.last
}
set fullName(newValue: string) {
let names = newValue.split(' ')
this.first = names[0]
this.last = names[names.length - 1]
}
}
Javascript
等价代码如下:
fullName: {
get: function () {
return this.first ' ' this.last
},
set: function (newValue) {
let names = newValue.split(' ')
this.first = names[0]
this.last = names[names.length - 1]
}
}
Methods
与普通类方法一样,TypeScript
中的方法也有一个可选的访问修饰符。
export default class HelloWorld extends Vue {
public clickMe(): void {
console.log('clicked')
console.log(this.addNum(4, 2))
}
public addNum(num1: number, num2: number): number {
return num1 num2
}
}
Javascript
等价代码如下:
export default {
methods: {
clickMe() {
console.log('clicked')
console.log(this.addNum(4, 2))
}
addNum(num1, num2) {
return num1 num2
}
}
}
Watchers
Watcher
的写法跟我们平时书写 JavaScript
的方式不同,我们最常用的 JavaScript
书写 watcher
的语法如下:
watch: {
name: function(newval) {
//do something
}
}
我们不经常使用 handler
语法
watch: {
name: {
handler: 'nameChanged'
}
}
methods: {
nameChanged (newVal) {
// do something
}
}
但是,Typescript
跟第二种方式相似。在 TypeScript
中,我们使用 @Watch
装饰器并传递需要监视的变量的名称。
@Watch('name')
nameChanged(newVal: string) {
this.name = newVal
}
我们也可以使用 immediate
和 deep
@Watch('project', {
immediate: true, deep: true
})
projectChanged(newVal: Person, oldVal: Person) {
// do something
}
Javascript
等价代码如下:
watch: {
person: {
handler: 'projectChanged',
immediate: true,
deep: true
}
}
methods: {
projectChanged(newVal, oldVal) {
// do something
}
}
Emit
要从一个子组件 emit
一个方法到父组件,在 Typescript
中,我们将使用 @Emit
装饰器。
@Emit()
addToCount(n: number) {
this.count = n
}
@Emit('resetData')
resetCount() {
this.count = 0
}
在第一个示例中,函数名 addToCount
被转换为短横线分隔 (kebab-case
),这与 Vue emit
的工作方式非常类似。
在第二个示例中,我们传递方法的显式名称 resetData
,并使用该名称。因为addData
是驼峰式的,所以它再次被转换为短横线分隔 (kebab-case
)。
<some-component add-to-count="someMethod" />
<some-component reset-data="someMethod" />
代码语言:javascript复制//Javascript Equivalent
methods: {
addToCount(n) {
this.count = n
this.$emit('add-to-count', n)
},
resetCount() {
this.count = 0
this.$emit('resetData')
}
}
3.生命周期钩子
一个 Vue
组件有八个生命周期,包括 created
、mounted
等等,每个钩子都使用相同的 Typescript
语法。这些被声明为普通类方法。因为生命周期钩子是自动调用的,所以它们既不接受参数也不返回任何数据。因此,我们不需要访问修饰符、输入参数或返回类型。
export default class HelloWorld extends Vue {
mounted() {
//do something
}
beforeUpdate() {
// do something
}
}
Javascript
等价代码如下:
export default {
mounted() {
//do something
}
beforeUpdate() {
// do something
}
}
4. Mixins
为了在 Typescript
中创建 mixins
,我们必须首先创建 mixin
文件,其中包含我们与其他组件共享的数据。
创建一个名为 ProjectMixin.ts
的文件。在 mixin
目录中添加下面的 mixin
,它共享 projName
和更新 projName
的方法。
import { Component, Vue } from 'vue-property-decorator'
@Component
class ProjectMixin extends Vue {
public projName: string = 'My project'
public setProjectName(newVal: string): void {
this.projName = newVal
}
}
export default ProjectMixin
Javascript
等价代码如下:
export default {
data() {
return {
projName: 'My project'
}
},
methods: {
setProjectName(newVal) {
this.projName = newVal
}
}
}
在我们的 Vue
组件中使用上面的 mixin
,我们需要从 vue-property-decorator
引入 Mixins
和引入 mixin
文件,如下所示
//Projects.vue
<template>
<div class="project-detail">
{{ projectDetail }}
</div>
</template>
<script lang="ts">
import { Component, Vue, Mixins } from 'vue-property-decorator'
import ProjectMixin from '@/mixins/ProjectMixin'
@Component
export default class Project extends Mixins(ProjectMixin) {
get projectDetail(): string {
return this.projName ' ' 'Preetish HS'
}
}
</script>
Javascript 等价代码如下:
代码语言:javascript复制<template>
<div class="project-detail">
{{ projectDetail }}
</div>
</template>
<script>
import ProjectMixin from '@/mixins/ProjectMixin'
export default {
mixins: [ ProjectMixin ],
computed: {
projectDetail() {
return this.projName ' ' 'Preetish HS'
}
}
}
</script>
5. Vuex
Vuex
是大多数 Vue.js
应用程序中使用的官方状态管理库。将 store
划分为命名空间模块是一个很好的实践。我们将演示如何在 TypeScript
中编写它。
首先,我们需要安装两个流行的第三方库:
代码语言:javascript复制npm install vuex-module-decorators -D
npm install vuex-class -D
在 store文件夹中,让我们创建一个 module
文件夹来放置我们的命名空间存储模块。
创建一个名为 user
的文件。ts
拥有 user
的状态。
// store/modules/user.ts
import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators'
@Module({ namespaced: true, name: 'test' })
class User extends VuexModule {
public name: string = ''
@Mutation
public setName(newName: string): void {
this.name = newName
}
@Action
public updateName(newName: string): void {
this.context.commit('setName', newName)
}
}
export default User
vuex-module-decorators
库为 Module
, Mutation
和 Action
提供了装饰器。状态变量是直接声明的,就像类变量一样。这是一个简单的模块,它存储用户名,并通过一个 mutation
和一个 action
去更新用户名的操作。
我们不需要将 state
作为Mutations
和 Actions
中的第一个参数,这个库已经考虑到这一点。它已经被注入到那些方法中。
Javascript
等价代码如下:
export default {
namespaced: true,
state: {
name: ''
},
mutations: {
setName(state, newName) {
state.name = newName
}
},
actions: {
updateName(context, newName) {
context.commit('setName', newName)
}
}
}
在 store
文件夹中,我们需要创建一个 index.ts
去初始化 Vuex
并注册此模块
import Vue from 'vue'
import Vuex from 'vuex'
import User from '@/store/modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
User
}
})
export default store
在组件中使用 Vuex
要使用 Vuex
,我们可以利用一个名为 Vuex -class
的库。这个库提供装饰器来绑定 Vue
组件中的 State
, Getter
, Mutation
和 Action
。
因为我们使用的是带有命名空间的Vuex
模块,所以我们首先从 Vuex
类导入命名空间,然后传递模块的名称来访问该模块。
<template>
<div class="details">
<div class="username">User: {{ nameUpperCase }}</div>
<input :value="name" @keydown="updateName($event.target.value)" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { namespace } from 'vuex-class'
const user = namespace('user')
@Component
export default class User extends Vue {
@user.State
public name!: string
@user.Getter
public nameUpperCase!: string
@user.Action
public updateName!: (newName: string) => void
}
</script>
Javascript
等价代码如下:
<template>
<div class="details">
<div class="username">User: {{ nameUpperCase }}</div>
<input :value="name" @keydown="updateName($event.target.value)" />
</div>
</template>
<script>
import { mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState('user', ['name']),
...mapGetters('user', ['nameUpperCase'])
}
methods: {
...mapActions('user', ['updateName'])
}
}
</script>
总结
现在,你已经掌握了在 TypeScript
中完全创建 Vue.js
应用程序所需的所有基本信息,可以使用一些官方和第三方库来充分利用类型化和自定义装饰器特性。Vue 3.0
将对 TypeScript
提供更好的支持,并且整个 Vue.js
代码都在 TypeScript
中重写,以提高可维护性。
一开始,使用 TypeScript
似乎有点让人不知所措,但是当你习惯了之后,你的代码中就会有更少的 bug
,并且在相同代码基础上的其他开发人员之间的代码协作也会更顺畅。
参考资料
[1]
How to write a Vue.js app completely in TypeScript: https://blog.logrocket.com/how-to-write-a-vue-js-app-completely-in-typescript/
[2]
Preetish HS: https://blog.logrocket.com/author/preetishhs/
[3]
地址: https://github.com/GpingFeng/learn-ts-vue-demo