Vue3.0 前的 TypeScript 最佳入门实践

前言

其实Vue官方从2.6.X版本开始就部分使用Ts重写了。

我个人对更严格类型限制没有积极的看法,毕竟各类转类型的骚写法写习惯了。

然鹅最近的一个项目中,是TypeScript+ Vue,毛计喇,学之...…真香!

注意此篇标题的“前”,本文旨在讲Ts混入框架的使用,不讲Class API




vue tyescript提示失效_vue tyescript提示失效


1. 使用官方脚手架构建

npm install -g @vue/cli# ORyarn global add @vue/cli复制代码

新的Vue CLI工具允许开发者 使用 TypeScript 集成环境 创建新项目。

只需运行vue create my-app。

然后,命令行会要求选择预设。使用箭头键选择Manually select features。

接下来,只需确保选择了TypeScript和Babel选项,如下图:


vue tyescript提示失效_用typescript完成倒计时_02


完成此操作后,它会询问你是否要使用class-style component syntax。

然后配置其余设置,使其看起来如下图所示。


vue tyescript提示失效_User_03


Vue CLI工具现在将安装所有依赖项并设置项目。


vue tyescript提示失效_User_04


接下来就跑项目喇。


vue tyescript提示失效_vue tyescript提示失效_05


总之,先跑起来再说。

2. 项目目录解析

通过tree指令查看目录结构后可发现其结构和正常构建的大有不同。


vue tyescript提示失效_Vue_06


这里主要关注shims-tsx.d.ts和 shims-vue.d.ts两个文件

两句话概括:

  • shims-tsx.d.ts,允许你以.tsx结尾的文件,在Vue项目中编写jsx代码
  • shims-vue.d.ts 主要用于 TypeScript 识别.vue 文件,Ts默认并不支持导入 vue 文件,这个文件告诉ts 导入.vue 文件都按VueConstructor处理。

此时我们打开亲切的src/components/HelloWorld.vue,将会发现写法已大有不同


{{ msg }}

复制代码

至此,准备开启新的篇章 TypeScript极速入门 和 vue-property-decorator

3. TypeScript极速入门

3.1 基本类型和扩展类型


vue tyescript提示失效_vue tyescript提示失效_07


Typescript与Javascript共享相同的基本类型,但有一些额外的类型。

  • 元组 Tuple
  • 枚举 enum
  • Any 与Void

1. 基本类型合集

// 数字,二、八、十六进制都支持let decLiteral: number = 6;let hexLiteral: number = 0xf00d;// 字符串,单双引都行let name: string = "bob";let sentence: string = `Hello, my name is ${ name }.// 数组,第二种方式是使用数组泛型,Array:let list: number[] = [1, 2, 3];let list: Array = [1, 2, 3];let u: undefined = undefined;let n: null = null;复制代码

2. 特殊类型

1. 元组 Tuple


vue tyescript提示失效_Vue_08


想象 元组 作为有组织的数组,你需要以正确的顺序预定义数据类型。

const messyArray = [' something', 2, true, undefined, null];const tuple: [number, string, string] = [24, "Indrek" , "Lasn"]复制代码

如果不遵循 为元组 预设排序的索引规则,那么Typescript会警告。


vue tyescript提示失效_User_09


(tuple第一项应为number类型)

2. 枚举 enum


vue tyescript提示失效_User_10


enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。

// 默认情况从0开始为元素编号,也可手动为1开始enum Color {Red = 1, Green = 2, Blue = 4}let c: Color = Color.Green;let colorName: string = Color[2];console.log(colorName); // 输出'Green'因为上面代码里它的值是2复制代码

另一个很好的例子是使用枚举来存储应用程序状态。


vue tyescript提示失效_数据类型_11


3. Void


vue tyescript提示失效_用typescript完成倒计时_12


在Typescript中,你必须在函数中定义返回类型。像这样:


vue tyescript提示失效_vue tyescript提示失效_13


若没有返回值,则会报错:


vue tyescript提示失效_User_14


我们可以将其返回值定义为void:


vue tyescript提示失效_用typescript完成倒计时_15


此时将无法 return

4. Any


vue tyescript提示失效_User_16


Emmm...就是什么类型都行,当你无法确认在处理什么类型时可以用这个。

但要慎重使用,用多了就失去使用Ts的意义。

let person: any = "前端劝退师"person = 25person = true复制代码

主要应用场景有:

  1. 接入第三方库
  2. Ts菜逼前期都用

5. Never


vue tyescript提示失效_数据类型_17


用很粗浅的话来描述就是:"Never是你永远得不到的爸爸。"

具体的行为是:

  • throw new Error(message)
  • return error("Something failed")
  • while (true) {} // 存在无法达到的终点


vue tyescript提示失效_数据类型_18


3. 类型断言


vue tyescript提示失效_User_19


简略的定义是:可以用来手动指定一个值的类型。

有两种写法,尖括号和as:

let someValue: any = "this is a string";let strLength: number = (someValue).length;let strLength: number = (someValue as string).length;复制代码

使用例子有:

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

function getLength(something: string | number): number { return something.length;}// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.复制代码

如果你访问长度将会报错,而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型的属性或方法,此时需要断言才不会报错:

function getLength(something: string | number): number { if ((something).length) { return (something).length; } else { return something.toString().length; }}复制代码

3.2 泛型:Generics

软件工程的一个主要部分就是构建组件,构建的组件不仅需要具有明确的定义和统一的接口,同时也需要组件可复用。支持现有的数据类型和将来添加的数据类型的组件为大型软件系统的开发过程提供很好的灵活性。

在C#和Java中,可以使用"泛型"来创建可复用的组件,并且组件可支持多种数据类型。这样便可以让用户根据自己的数据类型来使用组件。

1. 泛型方法

在TypeScript里,声明泛型方法有以下两种方式:

function gen_func1(arg: T): T { return arg;}// 或者let gen_func2: (arg: T) => T = function (arg) { return arg;}复制代码

调用方式也有两种:

gen_func1('Hello world');gen_func2('Hello world'); // 第二种调用方式可省略类型参数,因为编译器会根据传入参数来自动识别对应的类型。复制代码

2. 泛型与Any

Ts 的特殊类型 Any 在具体使用时,可以代替任意类型,咋一看两者好像没啥区别,其实不然:

// 方法一:带有any参数的方法function any_func(arg: any): any { console.log(arg.length);return arg;}// 方法二:Array泛型方法function array_func(arg: Array): Array { console.log(arg.length);return arg;}复制代码
  • 方法一,打印了arg参数的length属性。因为any可以代替任意类型,所以该方法在传入参数不是数组或者带有length属性对象时,会抛出异常。
  • 方法二,定义了参数类型是Array的泛型类型,肯定会有length属性,所以不会抛出异常。

3. 泛型类型

泛型接口:

interface Generics_interface { (arg: T): T;} function func_demo(arg: T): T { return arg;}let func1: Generics_interface = func_demo;func1(123); // 正确类型的实际参数func1('123'); // 错误类型的实际参数复制代码

3.3 自定义类型:Interface vs Type alias

Interface,国内翻译成接口。

Type alias,类型别名。


vue tyescript提示失效_数据类型_20


以下内容来自:

Typescript 中的 interface 和 type 到底有什么区别

1. 相同点

都可以用来描述一个对象或函数:

interface User { name: string age: number}type User = { name: string age: number};interface SetUser { (name: string, age: number): void;}type SetUser = (name: string, age: number): void;复制代码

都允许拓展(extends):

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说interface可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同

interface extends interface

interface Name {  name: string; }interface User extends Name {  age: number; }复制代码

type extends type

type Name = {  name: string; }type User = Name & { age: number };复制代码

interface extends type

type Name = {  name: string; }interface User extends Name {  age: number; }复制代码

type extends interface

interface Name {  name: string; }type User = Name & {  age: number; }复制代码

2. 不同点

type 可以而 interface 不行

  • type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名type Name = string// 联合类型interface Dog { wong();}interface Cat { miao();}type Pet = Dog | Cat// 具体定义数组每个位置的类型type PetList = [Dog, Pet]复制代码
  • type 语句中还可以使用 typeof获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeoflet div = document.createElement('div');type B = typeof div复制代码
  • 其他骚操作
type StringOrNumber = string | number; type Text = string | { text: string }; type NameLookup = Dictionary; type Callback = (data: T) => void; type Pair = [T, T]; type Coordinates = Pair; type Tree = T | { left: Tree, right: Tree };复制代码

interface可以而 type不行

interface 能够声明合并

interface User { name: string age: number}interface User { sex: string}/*User 接口为 { name: string age: number sex: string }*/复制代码

interface 有可选属性和只读属性

  • 可选属性
  • 接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 例如给函数传入的参数对象中只有部分属性赋值了。带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号。如下所示
interface Person { name: string; age?: number; gender?: number;}复制代码
  • 只读属性
  • 顾名思义就是这个属性是不可写的,对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性,如下所示:
interface User { readonly loginName: string; password: string;}复制代码

上面的例子说明,当完成User对象的初始化后loginName就不可以修改了。

3.4 实现与继承:implementsvsextends

extends很明显就是ES6里面的类继承,那么implement又是做什么的呢?它和extends有什么不同?

implement,实现。与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约

implement基本用法

interface IDeveloper { name: string; age?: number;}// OKclass dev implements IDeveloper { name = 'Alex'; age = 20;}// OKclass dev2 implements IDeveloper { name = 'Alex';}// Errorclass dev3 implements IDeveloper { name = 'Alex'; age = '9';}复制代码

而extends是继承父类,两者其实可以混着用:

class A extends B implements C,D,E复制代码

搭配 interface和type的用法有:


vue tyescript提示失效_Vue_21


3.5 声明文件与命名空间:declare 和 namespace

前面我们讲到Vue项目中的shims-tsx.d.ts和shims-vue.d.ts,其初始内容是这样的:

// shims-tsx.d.tsimport Vue, { VNode } from 'vue';declare global { namespace JSX { // tslint:disable no-empty-interface interface Element extends VNode {} // tslint:disable no-empty-interface interface ElementClass extends Vue {} interface IntrinsicElements { [elem: string]: any; } }}// shims-vue.d.tsdeclare module '*.vue' { import Vue from 'vue'; export default Vue;}复制代码

declare:当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

这里列举出几个常用的:

declare var 声明全局变量declare function 声明全局方法declare class 声明全局类declare enum 声明全局枚举类型declare global 扩展全局变量declare module 扩展模块复制代码

namespace:“内部模块”现在称做“命名空间”

module X { 相当于现在推荐的写法 namespace X {)

跟其他 JS 库协同

类似模块,同样也可以通过为其他 JS 库使用了命名空间的库创建 .d.ts 文件的声明文件,如为 D3 JS 库,可以创建这样的声明文件:

declare namespace D3{ export interface Selectors { ... }}declare var d3: D3.Base;复制代码

所以上述两个文件:

  • shims-tsx.d.ts, 在全局变量 global中批量命名了数个内部模块。
  • shims-vue.d.ts,意思是告诉 TypeScript *.vue 后缀的文件可以交给 vue 模块来处理。

3.6 访问修饰符:private、public、protected

其实很好理解:

  1. 默认为public
  2. 当成员被标记为private时,它就不能在声明它的类的外部访问,比如:
class Animal { private name: string; constructor(theName: string) { this.name = theName; }}let a = new Animal('Cat').name; //错误,‘name’是私有的复制代码
  1. protected和private类似,但是,protected成员在派生类中可以访问
class Animal { protected name: string; constructor(theName: string) { this.name = theName; }}class Rhino extends Animal { constructor() { super('Rhino'); }  getName() { console.log(this.name) //此处的name就是Animal类中的name }} 复制代码

3.7 可选参数 ( ?: )和非空断言操作符(!.)

可选参数

function buildName(firstName: string, lastName?: string) { return firstName + ' ' + lastName}// 错误演示buildName("firstName