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也在慢慢普及,可以给开发者带来更好的体验(类型提示,智能补全等),但是在习惯上还需要一段时间去学习和应试。
