1. 概述
- Vue3使用Typescript进行了重写,优化了渲染机制,重写实现了数据响应式原理,同时也支持了组合式API的写法,还引入了诸多新特性和带来更好的性能。
- TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。
本文首先介绍了Vue3和Typescipt 常用的一些基本语法,然后用上手demo来实践,更好的体验Vue3 + Typescipt进行开发。
2. Vue3 新特性
2.1 createApp
const app = Vue.createApp({})
返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。可以在 createApp 之后链式调用其它方法。
相比Vue2 new Vue()
创建Vue实例,从同一个Vue构造函数创建的每个根实例共享相同的全局配置:
- 在测试阶段,全局配置很容易意外地污染其他测试用例
- 全局配置使得在同一页面上的多个“app”之间共享同一个 Vue 副本非常困难,但全局配置不同
2.2 组合式 API
用一张图看变化:
为什么不用之前的组件的option
?
data、computed、methods、watch 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。而通过setup可以将该部分抽离成函数,让其他开发者就不用关心该部分逻辑了.
setup() {
// ref 是一个函数,它接受一个参数,返回的就是一个神奇的 响应式对象 。
我们初始化的这个 0 作为参数包裹到这个对象中去,在未来可以检测到改变并作出对应的相应。
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increase = () => {
count.value++
}
return {
count,
increase,
double
}
}
setup
作为一个组件选项,在创建组件之前执行并作为组合式 API 的入口点- 下面为setup的类型声明: ```typescript interface Data {
}
interface SetupContext { attrs: Data slots: Slots emit: (event: string, …args: unknown[]) => void }
function setup(props: Data, context: SetupContext): Data
> 从这里可以看出类型声明和 TS 类型的好处,可以看查看参数的类型。
<a name="vksQP"></a>
## 2.3 响应式
要为 JavaScript 对象创建响应式状态,可以使用 `reactive `方法:
```typescript
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
count: 0
})
当为一些基本类型时,可以使用ref
:
import { ref } from 'vue'
const count = ref(0)
2.4 生命周期
选项式 API | setup |
---|---|
beforeCreate | 无 |
created | 无 |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
2.5 computed 与 watch
- computed ```typescript const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } })
plusOne.value = 1 console.log(count.value) //
- **watchEffect**
在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
```typescript
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
- watch
const count = ref(0) watch(count, (count, prevCount) => { /* ... */ })
2.6 v-model
默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和update:modelValue
作为事件。
可以通过向 v-model 传递参数来修改默认名称:
子组件就可以通过<my-component v-model:title="bookTitle"></my-component>
$emit('update:title', $event.target.value)
进行值的传递。
类似于Vue2中的.sync
写法3. TypeScript入门
TypeScript是JavaScript的超集,对于开发人员还是比较好上手的,主要是要习惯Type优先的写法,本章总体介绍一下TypeScript:
Typescript 官网地址: https://www.typescriptlang.org/zh/
3.1 安装
安装 Typescript:
npm install -g typescript
使用 tsc 全局命令:
// 查看 tsc 版本
tsc -v
// 编译 ts 文件
tsc fileName.ts
使用ts-node:
//安装
npm install -g ts-node
//使用ts-node即可
ts-node fileName.ts
3.2 基本数据类型
- Boolean
- Number
- String
- Null
Undefined
boolean/number/string类型,没啥好说的,最常用的三个类型,定义方式为: ```typescript let isDone: boolean = false;
let num: number = 6;
let name: string = “bob”;
- **undefined/null**
- 默认情况下`null`和`undefined`是所有类型的子类型。 就是说你可以把 `null`和`undefined`赋值给`number`类型的变量。
- 当编译时指定了`--strictNullChecks`选项,`null`和`undefined`只能赋值给`void`和它们各自
- **any **: 相当于不进行类型检查,尽量不要用
```typescript
let notSure: any = 4
notSure = 'maybe it is a string'
// 在任意值上访问任何属性都是允许的:
notSure.myName
// 也允许调用任何方法:
notSure.getName()
- void
- 声明一个
void
类型的变量没有什么大用,因为你只能为它赋予undefined
和null
- 当一个函数没有返回值时,你通常会见到其返回值类型是
void
```typescript let unusable: void = undefined;
- 声明一个
function warnUser(): void { console.log(“This is my warning message”); }
- **联合类型 **
```typescript
let numberOrString: number | string
- 类型别名 ```typescript type a = string
type ab = string || number
<a name="jLHcv"></a>
## 3.3 数组和元祖
- **Array**
```typescript
/** 子元素是数字类型的数组 */
let arr1: number[] = [1, 2, 3];
/** 子元素是字符串类型的数组 */
let arr2: string[] = ['x', 'y', 'z'];
//使用 Array 泛型
/** 子元素是数字类型的数组 */
let arr3: Array<number> = [1, 2, 3];
/** 子元素是字符串类型的数组 */
let arr4: Array<string> = ['x', 'y', 'z'];
推荐使用 [] 这种形式来定义数组。一方面可以避免与 JSX 的语法冲突,另一方面可以减少不少代码量。
- Tuple
元组类型允许表示一个已知元素数量和类型的数组
// Declare a tuple type
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
3.4 接口
在声明一个对象、函数或者类时,先定义接口,确保其数据结构的一致性。
- 接口描述一个对象: ```typescript // 我们定义了一个接口 Person interface Person { name: string; age: number; }
// 接着定义了一个变量 tom,它的类型是 Person。这样, //我们就约束了 tom 的形式必须和接口 Person 一致。 let tom: Person ={ name: ‘tom’, age: 20 }
//也可以用可选属性: interface Person { name: string; age?: number; } let tom: Person = { name: ‘tom’ }
//还有只读属性, 那么可以用 readonly 定义只读属性 interface Person { readonly id: number; name: string; age?: number; }
- 接口描述一个类:
```typescript
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
- 接口也可以描述函数类型: ```typescript interface SearchFunc { (source: string, subString: string): boolean; }
let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { let result = source.search(subString); return result > -1; }
<a name="xHLQv"></a>
## 3.5 类型断言
- 有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息
- 通过_类型断言_这种方式可以告诉编译器,“相信我,我知道自己在干什么”
- 一般使用as语法,还可以使用`<类型>变量`
```typescript
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
3.6 Class
class Animal {
name: string;
constructor(name: string) {
this.name = name
}
run() {
return `${this.name} is running`
}
}
const snake = new Animal('sss')
// 继承的特性
class Dog extends Animal {
bark() {
return `${this.name} is barking`
}
}
const hhh = new Dog('hhh')
console.log(hhh.run())
console.log(hhh.bark())
类的成员可以用访问修饰符修饰:
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
3.7 泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
//使返回值的类型与传入参数的类型是相同的
function identity<T>(arg: T): T {
return arg;
}
//可以指定T
let output = identity<string>("myString"); // type of output will be 'string'
//也可以让编译器进行类型推论
let output = identity("myString"); // type of output will be 'string'
- 泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
interface IWithLength {
length: number;
}
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
4. 开发上手
4.1 初始化
使用vue-cli,进行初始化
vue create vue3-demo
选择Vue 3.x
和TypesScipt
初始化工程
初始化后目录结构:
─public
└─src
├─assets
├─components
├─App.vue
├─main.ts
├─shims-vue.ts
└─tsconfig.json
- shims-vue.d.ts ```typescript / eslint-disable / declare module ‘*.vue’ { import type { DefineComponent } from ‘vue’ const component: DefineComponent<{}, {}, any> export default component }
> shims-vue.d.ts是为了typescript做的适配定义文件,因为.vue文件不是一个常规的文件类型,TypeScript是不能理解vue文件是干嘛的,加这一段是是告诉 TypeScript,vue文件是这种类型的
- **tsconfig.json**
> [https://www.typescriptlang.org/docs/handbook/compiler-options.html](https://www.typescriptlang.org/docs/handbook/compiler-options.html)
typescript 的编译配置文件,里面含有很多的编译选项:
```typescript
{
"compilerOptions": {
"noImplicitAny": false, // 不允许隐式的any类型
"target": "es5", // 目标语言的版本
"module": "esnext",// 生成代码的模板标准
"strict": true, // 开启所有严格的类型检查
"jsx": "preserve", // JSX相关
"importHelpers": true, // 通过tslib引入helper函数
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导
"skipLibCheck": true,
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowSyntheticDefaultImports": true,
"sourceMap": true, // 生成目标文件的sourceMap文件
"baseUrl": ".", // 解析非相对模块的基地址
"types": [ // 加载的声明文件包
"webpack-env"
],
"paths": { // 路径映射
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [ // 指定编译需要编译的文件或目录。
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [ // 指定编译器需要排除的文件或文件夹
"node_modules"
]
}
- App.vue
```vue
`export default defineComponent({ .. })`相比`export default { .. }` 能更好的获得提示和类型推导
<a name="rRPab"></a>
##
<a name="ohSqW"></a>
## 4.2 实现一个Rate组件
使用 Vue3 和 TS实现一个简单的评分组件,如图为el-rate的想过<br />
```vue
<el-rate v-model="value1"></el-rate>
css样式直接引入element已有的样式,这里以实现简单功能为主。
编写Rate.vue
文件:
<template>
<div class="el-rate">
<span
v-for="(item, key) in max"
:key="key"
class="el-rate__item"
style="cursor: pointer"
@mousemove="setCurrentValue(item)"
@mouseleave="resetCurrentValue"
@click="selectValue(item)"
>
<i
class="el-rate__icon"
:class="'el-icon-star-' + (currentValue >= item ? 'on' : 'off')"
style="color: #f7ba2a"
></i>
</span>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "Rate",
props: {
modelValue: {
type: Number,
default: 0,
},
},
//传入props和context
setup(props, context) {
//定义为响应式
let currentValue = ref(props.modelValue);
//设置
const setCurrentValue = (val) => {
currentValue.value = val;
};
//重置
const resetCurrentValue = () => {
currentValue.value = props.modelValue;
};
const selectValue = (val) => {
// 向父级传值,//modelValue为默认的,可查看vue3 v-model的变化
context.emit("update:modelValue", val);
};
//模板中用的需要进行retuan
return {
currentValue,
setCurrentValue,
resetCurrentValue,
selectValue,
};
},
};
</script>
App.vue
中引入使用,进行测试:
<template>
<div id="app">
<h1>RateValue: {{data.rateValue}}</h1>
<rate v-model="data.rateValue"></rate>
</div>
</template>
<script>
import {reactive} from 'vue'
import Rate from '@/components/Rate.vue'
import Calendar from '@/components/Calendar'
export default {
name: 'App',
components: {
Rate
},
setup() {
let data = reactive({
rateValue: 3
})
return {
data
}
}
}
</script>
实现了简单的评分组件:
4.3 PropType类型验证
假如一个组件接收的prop参数为对象类型,可以使用TS的interface定义接口进行类型验证,也能带来友好的编辑器提示。
实现一个简单card展示组件:
<template>
<ul>
<li v-for="column in list" :key="column.id">
<img :src="column.img" >
<div>{{column.title}}</div>
<div>{{column.description}}</div>
</li>
</ul>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export interface CardProps {
id: number;
title: string;
img: string;
description: string;
}
export default defineComponent({
name: 'Card',
props: {
list: {
//
type: Object as PropType<CardProps>,
required: true
}
}
})
</script>
Vue 对定义了 type 的 prop 执行运行时验证。要将这些类型提供给 TypeScript,我们需要使用 PropType 强制转换构造函数
5. 兼容性问题
Vue3是不支持IE11 ,Vue2 的2.7版本将会支持 Vue3 的写法, 以兼容IE11
同时 Vue3在今年的Q2将成为默认版本,npm 和文档都会指向 V3 版本
6.总结
本文简单的介绍了Vue3+TS 的上手实战,关于Vue3的新特性还有很多没有介绍,可以查看官网的文档进行学习和实践。
TypeScript也在慢慢普及,可以给开发者带来更好的体验(类型提示,智能补全等),但是在习惯上还需要一段时间去学习和应试。