速览常见类型

Posted 余光、

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了速览常见类型相关的知识,希望对你有一定的参考价值。

🚀【TypeScript入门手册】记录了出场率较高的Ts概念,旨在帮助大家了解并熟悉Ts
🎉 本系列会持续更新并更正,重点关照大家感兴趣的点,欢迎同学留言交流,在进阶之路上,共勉!
🙏 star本项目给作者一点鼓励吧

相信大家经常会问到为什么会有Ts,我该不该学习Ts,在知乎上这样的问题也会受到大家广泛的关注,而我的回答是,该去了解,因为了解Ts不会像我们学习英语那样“痛苦”,如果TypeScript能够解决你的问题,那就继续深入的学习吧,本系列的第一篇文章也不会有太深奥的理论,花一些时间了解一下给出自己的判断吧~

话不多说,我们先来从基本类型入手,走进Ts的大门!

一、变量上的类型注解(Type Annotations on Variables)

当你使用constvarlet 声明一个变量时,你可以选择性的添加一个类型注解,显式指定变量的类型:

let myName: string = "yuguang";

TypeScript 并不使用“在左边进行类型声明”的形式,比如 int x = 0;类型注解往往跟在要被声明类型的内容后面。

不过大部分时候,这不是必须的。因为 TypeScript 会自动推断类型。举个例子,变量的类型可以基于初始值进行推断:

// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "yuguang";

大部分时候,你不需要学习推断的规则。如果你刚开始使用,尝试尽可能少的使用类型注解。你也许会惊讶于,TypeScript 仅仅需要很少的内容就可以完全理解将要发生的事情。

二、基本类型

javascript的类型分为两种:原始数据类型和对象类型。

原始数据类型包括:

  • 布尔值
  • 数值
  • 字符串
  • null
  • undefined
  • 以及 ES6 中的新类型 Symbol 和 ES10 中的新类型 BigInt。

2.1 number、string、boolean

布尔值是最基础的数据类型,在TypeScript中,使用boolean 定义布尔值类型:
注意;在TypeScript中,boolean是JavaScript中的基本类型,而Boolean是JavaScript中的构造函数。

let visible: boolean = false;

使用number来定义数值类型:

let age: number = 100;

使用string来定义字符串类型:

const name: string = '余光';
const templateName = `hi $name!`

2.2 空值、null、undefined

  • JavaScript中没有空值Void的概念,在TypeScript中,可以用void表示没有任何返回值的函数;
  • 声明一个void类型的变量没有什么用,因为你只能将它赋值为undefinednull;
function alertName(): void 
    alert('My name is Tom');


let unusable: void = 1;
// warning 不能将类型“number”分配给类型“void”。
// Type 'number' is not assignable to type 'void'.
  • 在TypeScript中,可以使用 null 和 undefined 来定义这两个原始数据类型,但看起来没什么意义
let u: undefined = undefined;
let n: null = null;

void的区别是,undefinednull是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量:

// 严格模式下,会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;

注意:

  1. 声明变量的数据类型为 void 时,非严格模式下,变量的值可以为 undefined 或 null。而严格模式下,变量的值只能为 undefined。
  2. 严格模式下:let num: number = undefined;会报错

三、任意值

如果你没有指定一个类型,TypeScript 也不能从上下文推断出它的类型,编译器就会默认设置为 any 类型。

TypeScript有一个特殊的类型any,当你不希望一个值导致类型检查错误的时候,就可以设置为any,先来看下面的代码:

let age:number = 100;

age = '余光'
// 不能将类型“string”分配给类型“number”
// Type 'string' is not assignable to type 'number'.

类型检测会提前帮我们规避一些不必要的风险,大家在平时也一定会遇到类型不匹配这样的错误,如果是any类型,则允许被赋值为任意类型。但写多了any和Js就没“区别”了不是吗?

let age:any = 100;

age = '余光';

3.1 任意值的属性和方法

  • 在任意值上访问任何属性都是允许的;
  • 调用任意方法也是允许的;
let anyThing: any = 'todo';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

anyThing.toString();

可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

3.2 未声明类型的变量在未指定其类型时会被识别为any

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:

let something;
something = 'str';
something = 7;
something.name = '余光';

// 等价于
let something: any;
something = 'str';
something = 7;
something.name = '余光';

注意:

经过前面两点(尤其是第二点)的描述,你会发现如果给变量添加了any的声明,那么Typescript就变成了AnyScript了,那岂不是一夜回到了解放前?理论上我们尽量不使用any,就好像才出现了const和let之后,我们进来避免试用var一样。

四、数组

在TypeScript中,数组类型有多种定义方式声明一个类似于 [1, 2, 3] 的数组类型,你可以用number[]。这个语法可以适用于任何类型(举个例子,string[] 表示一个字符串数组)。你也可能看到这种写法 Array<number>,是一样的。未来了解泛型时我们再来深入的研究。先来了解一下简单方法吧~

4.1 类型 + 方括号

let fibonacci: number[] = [1, 1, 2, 3, 5]; // 此时数组内每个元素都会进行类型检测
fibonacci.push('8'); // 类型“string”的参数不能赋给类型“number”的参数。

4.2 数组泛型

具体详细内容我们之后再展开~

我们可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:

let fibonacci: Array<number> = [1, 1, 2, 3, 5];

4.3 any在数组中的应用

一个比较常见的做法是,用 any 表示数组中允许出现任意类型:

let list: any[] = ['xcatliu', 25,  website: 'http://xcatliu.com' ];

对于any约束的数字,其实就是主动放弃了类型检测~

五、对象

除了原始类型,最常见的类型就是对象类型了。定义一个对象类型,我们只需要简单的列出它的属性和对应的类型。

5.1 对象属性注释

来看下面一段代码:

**注意:**此时参数的数量不一致,会导致报错,其实这是因为它们的形状不同,之后我们会展开来说~

function getName(obj:  name: string; age: number ): void 
  console.log(obj.name);
  console.log(obj.age);


const res = getName( name: "余光", age: 100 );
// ✅ 
const res2 = getName( name: "余光", age: 100 );
// ❌ 类型 " name: string; " 中缺少属性 "age",但类型 " name: string; age: number; " 中需要该属性。

不难理解,我们将参数 obj 内部的属性进行了类型注释,并在之后检查对应的类型。

5.2 可选属性

对象类型可以指定一些甚至所有的属性为可选的,你只需要在属性名后添加一个 ?:

function getName(obj:  name: string; age?: number ): void 
    console.log(obj.name);
    console.log(obj.age);


getName( name: "余光" );

注意可选属性可以不存在,但仍然不允许添加未定义的属性。

在JavaScript中,如果你获取一个不存在的属性,你会得到一个undefined而不是一个运行时错误。因此,当你获取一个可选属性时,你需要在使用它前,先检查一下是否是undefined

function getName(obj:  name: string; age?: number ): void 
    console.log(obj.name);
    console.log(obj.age);


getName( name: "余光" );
// 余光
// undefined

六、函数

在函数是JavaScript中是一等公民,在Ts中,它允许我们指定输入类型和输出类型。

6.1 参数类型注解(Parameter Type Annotations)

当你声明一个函数的时候,你可以在每个参数后面添加一个类型注解,声明函数可以接受什么类型的参数。参数类型注解跟在参数名字后面:

函数声明:

function sum(x: number, y: number): number 
  return x + y;


// 输入多余的(或者少于要求的)参数,是不被允许的:

sum(1);
// 应有 2 个参数,但获得 1 个。ts(2554)

sum(1, 2, 3); 
// 应有 2 个参数,但获得 3 个

6.2 可选参数和参数默认值

前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?与接口中的可选属性类似,我们用?表示可选的参数:

注意:可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了。

function hello(name1: string, name2?: string): void 
  console.log(`hello! $name1 $name2 ? "and" + " " + name2 : ""`);


hello("余光1", "余光2"); // hello! 余光1 and 余光2
hello("余光"); // hello! 余光

同样添加默认值也是可以的,TypeScript 会将添加了默认值的参数识别为可选参数:

function hello(name1: string = "余光", name2: string = "yuguang"): void 
  console.log(`hello! $name1 $name2 ? "and" + " " + name2 : ""`);


hello(); // hello! 余光 and yuguang
hello("小明"); // hello! 小明 and yuguang

6.3 匿名函数

匿名函数有一点不同于函数声明,当 TypeScript 知道一个匿名函数将被怎样调用的时候,匿名函数的参数会被自动的指定类型。

这个例子来自“冴羽大佬”的示例👍

const arr = [1, 2, 3, 4];

arr.forEach((val) => 
    val.toFixed(1);
);
// ✅

arr.forEach((val) => 
    val.split("-");
);
// ❌ 类型“number”上不存在属性“split”。

尽管参数val并没有添加类型注解,但TypeScript根据forEach函数的类型,以及传入的数据的类型,最后推断出了val的类型。

这个过程被称为上下文推断(contextual typing),因为正是从函数出现的上下文中推断出了它应该有的类型。

七、联合类型(Union Types)

联合类型(Union Types)表示取值可以为多种类型中的一种。

7.1 定义一个联合类型

前面我们提到了基本类型是怎么指定的,如果你希望一个变量的类型是可选的可以使用any,但还有更好的方式——是明确指定几个类型——联合类型

let category: string | number;
category = '余光';
category = 7;
category = true; // ❌ 不能将类型“boolean”分配给类型“string | number”。

联合类型使用 | 分隔每个类型。表示允许设置的类型,这其中每个类型都是联合类型的成员(members)。

注意

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

function getLength(something: string | number[]): number 
    return something.length;

// ✅
function getLength(something: string | number): number 
    return something.length;

// ❌ 类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”。

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型,所以也会遇到上面问题

let val: string | number;
val = 'seven';
console.log(val.length); // 5
val = 7;
console.log(val.length); // ❌ 类型“number”上不存在属性“length”

八、类型别名(type)

类型别名用来给一个类型起个“新”名字。例如:

type hasLen = string | number[]; // isNumber就是新名字,他可能更语义化一些
const arr: hasLen = [1];

又或者,常用语给一个联合类型定义自己的类型别名

type Name = string; // 字符串
type NameResolver = () => string; // 函数
type NameOrResolver = Name | NameResolver; // 联合类型

function getName(n: NameOrResolver): Name 
  if (typeof n === "string") 
    return n;
   else 
    return n();
  

九、接口(interface)

接口声明(interface declaration)是命名对象类型的另一种方式:

9.1 使用接口

来看下面一段代码:

function getName(obj:  name: string; age: number ): void 
  console.log(obj.name);
  console.log(obj.age);


const res = getName( name: "余光", age: 100 );

不难理解,我们将参数 obj 内部的属性进行了类型注释,并在之后检查对应的类型,再来看看使用了接口后的代码:

interface Person 
  name: string;
  age: number;


function getName(person: Person): void 
  console.log(person.name);
  console.log(person.age);

const person1 =  name: "余光1", age: 100 ;
const person2 =  name: "余光2", age: 200 ;

getName(person1); // 余光1 100
getName(person2); // 余光2 200

类似我们在上节使用的类型别名,这个例子也同样可以运行,就跟我们使用了一个匿名对象类型一样。TypeScript 只关心传递给 printCoord 的值的结构(structure)——关心值是否有期望的属性。

**注意:**类型别名和接口非常相似,大部分时候,你可以任意选择使用。接口的几乎所有特性都可以在 type 中使用,两者最关键的差别在于类型别名本身无法添加新的属性,而接口是可以扩展的。

十、类型断言(Type Assertion)

断言是编程术语,表示为一些布尔表达。—— 百度百科
类型断言(Type Assertion)可以用来手动指定一个值的类型。就是告诉编译器, 你不要帮我们检查了, 相信我,它就是这个类型。

断言就好比解释型强制类型转换,他会告诉你更加具体或者更不具体的类型!

as 类型;

有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,就像这样:

interface fruits 
  name: string;
  getColor(): void;

interface person 
  name: string;
  getAge(): void;


type fruitsOrPerson = fruits | person;

function getColor(intance: fruitsOrPerson) 
  return intance.getColor();

// 类型“fruitsOrPerson”上不存在属性“getColor”。
// 类型“person”上不存在属性“getColor”

上面的例子中,执行intance.getColor 的时候会报错。这是因为在它类型不确定时,我们使用了非共有属性或方法。

此时可以使用类型断言,将实例断言成fruits

interface fruits 
  name: string;
  getColor(): void;

interface person 
  name: string;
  getAge(): void;


type fruitsOrPerson = fruits | person;

function getColor(intance: fruitsOrPerson) 
  return (intance as fruits).getColor(); // (method) fruits.getColor(): void

需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,不合理的使用会导致不可掌控的错误,例如:

interface fruits 
  name: string;
  getColor(): void;

interface person 
  name: string;
  getAge(): void;


type fruitsOrPerson = fruits | person;

// 通过
function getColor(intance: fruitsOrPerson) 
  return (intance as fruits).getColor();


// 执行错误 intance.getColor is not a function
getColor(
  name: "余光",
  getAge: () => 
    return 100;
  ,
);

上面的例子编译时不会报错,但在运行时会报错:

使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性(就像上面的例子),以减少不必要的运行时错误。

十一、字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个。

type EventNames = "click" | "scroll" | "mousemove";
function handleEvent(ele: Element, event: EventNames) 
  // do something


handleEvent(document.getElementById("hello"), "scroll"); // 没问题
handleEvent(document.getElementById("world"), "onmouseout"); // 报错,event 不能为 'onmouseout'

上例中,我们使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。

写在最后

本篇文章是《Typescript基础入门》第一篇,到这里就结束了,主要带大家了解一下常见类型在Ts中是什么样子的,大部分代码我们都认识,但组装起来却会有些别扭,一起共勉吧!

关于我:

  • 花名:余光
  • 邮箱:webbj97@163.com
  • csdn:传送门

其他沉淀:

以上是关于速览常见类型的主要内容,如果未能解决你的问题,请参考以下文章

速览常见类型

速览常见类型

速览常见类型

速览常见类型

使用联合类型进行类型推断 - 不存在最佳通用类型

为啥 Spark 在创建 DataFrame 时会推断二进制而不是 Array[Byte]?