一、基本类型
// 1、布尔值
let isFlag: boolean;
// 2、数字
let sum: number;
// 3、字符串
let name: string;
// 4、symbol
let symbol1: symbol;
// 5、Null 和 Undefined
let n: ``null``;
// 6、数组
let colorArr: string[];
/* 基本类型有初始值的话,无需声明类型,ts在分配属性时会推断基础类型 */
二、特殊类型
- any
在不确定类型的时候使用any,但是any会绕过ts的类型检查,如果有问题,编译时不会报错,运行时才会报错
let notSure: any = 4 // ts类型检查会建议你替换为unknown
2.unknown
被称为顶端类型,是一些超类的集合,任何类型都可以赋值为unknown
// 使用any
let unSure: any = 22;
unSure.push(33); // 编译不会报错,运行报错
console.log(unSure);
// 使用unknown
let unSure: unknown = 22;
unSure.push(33); // 编译器认为这个类型没有push方法,就会报错
console.log(unSure);
// 使用unknown加类型判断
let unSure: unknown = 22;
if (unSure instanceof Array) {
unSure.push(33);
}
console.log(unSure);
3.never
表示永不存在的值,任何值都不能冠以类型never,被称为底端类型
// 函数永远不会有返回值时
function fn(): never {
throw new Error("error");
}
fn();
// 可以用作收窄类型
type Foo = string | number;
// type Foo = string | number | boolean; // 如果添加boolean类型
function checkType(foo: Foo): void {
if (typeof foo === "string") {
} else if (typeof foo === "number") {
} else {
// foo 在这里是 never
const check: never = foo; // 修改Foo类型会报错: Type 'boolean' is not assignable to type 'never'.
console.log(check);
}
}
/* 使用 never 可以在新增了类型之后,编译时检查出没有对应的类型判断,其目的就是写出类型绝对安全的代码。*/
如何在 never、unknown、any 之间作出选择:
- 永远不能取得任何值的时候使用 never
- 定义或者获取到得的任意值,但不确定类型的时候,使用unknown
- 除非有意忽略类型检查,否则一般不要使用any,不安全 4.void
表示没有任何类型,一般表示函数没有返回值
function setTitle(title: string): void {
console.log(title)
}
void和never的区别:
-
没有设置返回值的函数,是会隐式返回
undefined。尽管我们通常说这样的函数 “什么也不返回”,但实际上它是会返回的。在这些情况下,我们通常忽略返回值。在 TypeScript 中这些函数的返回类型被推断为void。 -
具有
never返回类型的函数永不返回。它也不返回undefined。而且函数没有不会正常完成,也就是说它可能会抛出异常或根本无法退出执行。
5.enum
// 数字类型枚举
enum VipCard {
year,
month,
week,
}
const year = VipCard.year;
console.log(year, "year"); // 0
// 修改数字关联枚举值
enum VipCard {
year=4,
month,
week,
}
const year = VipCard.year;
console.log(year, "year"); // 4
console.log(VipCard.month, "month"); // 5
console.log(VipCard.week, "week"); // 6
// 字符串类型枚举
enum VipCard {
year,
month,
week,
}
const year = VipCard[0];
console.log(year, "year"); // 'year'
// 字符串枚举关联值定义
enum CONSTANT {
PEOPLETYPE = 'part',
}
const year = CONSTANT.PEOPLETYPE;
console.log(year, "year"); // 'part'
6.tuple
表示一个已知元素数量和类型的数组
const peple: [number, string] = [18, "xiaoming"];
const age = peple[0];
const useName = peple[1];
peple[2] = "nicName"; // 报错 Type '"nicName"' is not assignable to type 'undefined'.
console.log(age, useName);
7.type
自定义类型,自己组合定义类型
// 自定义类型
type Product = string | undefined;
const math: Product = "math";
type People = {
readonly name: string; // 只读属性
age: number;
nickname?: string; // 可选属性
[propName: string]: unknown; // 额外属性
};
const xiaoMing: People = {
name: "xiaoMing",
age: 12,
uid: "98989e3e3",
};
// type也可以定义像接口一样的对象,也可以定义基础类型,interface不可以,在声明合并方面,interface可以,type不行
三、接口
// 接口基础使用方式
interface People {
readonly name: string; // 只读属性
age: number;
nickname?: string; // 可选属性
[propName: string]: unknown; // 额外属性
}
const xiaoMing: People = {
name: "xiaoMing",
age: 12,
uid: "98989e3e3",
};
xiaoMing.name = "Lili"; // 报错 Cannot assign to 'name' because it is a read-only property.
// 函数接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function (source, subString) {
const result = source.search(subString);
return result > -1;
};
// 类接口
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light { // 实现多个接口
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
// 接口继承接口
interface Alarm {
alert(): void;
}
interface LightableAlarm extends Alarm {
lightOn(): void;
lightOff(): void;
}
class Car implements LightableAlarm {
lightOn(): void {
throw new Error("Method not implemented.");
}
lightOff(): void {
throw new Error("Method not implemented.");
}
alert(): void {
throw new Error("Method not implemented.");
}
}
四、函数
// 函数类型定义 可选参数放在最后
function buildName(lastName = "Smith", firstName?: string): string {
return lastName + " " + firstName;
}
console.log(buildName("a", "b")); // a b
// 剩余参数 个数不限的参数
function buildName2(
lastName = "Smith",
firstName?: string,
...restOfName: string[]
): string {
return firstName + " " + lastName + restOfName.join(" ");
}
console.log(buildName2("a", "b")); // a bc
// 重载 函数名称相同 但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力
// Ts为同一个函数提供多个函数类型定义来进行函数重载,目的是重载的函数在调用的时候会进行正确的类型检查,让你清晰的知道传入不同的参数得到不同的结果
function add(x: string, y: string): string;
function add(x: number, y: number): number;
function add(x: string | number, y: number | string): number | string {
if (typeof x === "string" && typeof y === "string") {
return x + "," + y;
} else if (typeof x === "number" && typeof y === "number") {
return x + y;
} else {
return "";
}
}
const x = add("1", "2"); // string
const y = add(2, 3); // number
console.log(x, y);
/* 需要遵循的原则是:越具体的类型,应该定义在越前面。这样有利于 TypeScript 编译器推断出更准确的类型 */
五、类
// 继承
class Animal { // 基类
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal { // extends关键字实现派生类
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) { // 方法重写
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal { // 派生类
constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) { // 方法重写
console.log("Galloping...");
super.move(distanceInMeters);
}
}
const sam = new Snake("Sammy the Python");
const tom = new Horse("Tommy the Palomino");
sam.move(); // Slithering... Sammy the Python moved 5m.
tom.move(34); // Galloping... Tommy the Palomino moved 34m.
/*派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问this的属性之前,一定要调用 super()。 这是TypeScript强制执行的一条重要规则。*/
// 修饰符
class Animal {
private name: string; // 私有属性,不能直接访问
protected nickName: string; // 保护变量,不能直接访问, 在派生类中可以访问
readonly numberOfLegs: number = 8; // 只读属性不能更改
age: number | undefined; // 没有声明修饰符,默认都是public
public constructor(theName: string, nickName: string) {
this.name = theName;
this.nickName = nickName;
}
public getName(): string {
return this.name;
}
}
class Horse extends Animal {
private subAge: string;
constructor(theName: string, nickName: string) {
super(theName, nickName);
this.subAge = nickName;
}
public getNickName(): string {
// return this.theName; // Error Property 'name' is private and only accessible within class 'Animal'.
return this.nickName;
}
}
new Animal("Cat", "1").numberOfLegs = 10; // 错误: Cannot assign to 'numberOfLegs' because it is a read-only property.
new Animal("Cat", "2").nickName; // 错误: Property 'nickName' is protected and only accessible within class 'Animal' and its subclasses
new Animal("Cat", "3").name; // 错误: Property 'name' is private and only accessible within class 'Animal'.
const howard = new Horse("Cat", "4");
console.log(howard.getNickName(), "nickName");
// 抽象类 指不具体的类,通常作为基类使用,不能直接实例化,关键字abstract声明 规定所有继承的子类必须实现他规定的功能和相关的操作
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract eat(): void;
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat() {
console.log(this.name + "吃肉");
}
}
const dog = new Dog("狗");
dog.eat();
六、联合类型和交叉类型
// 联合类型表示一个值可以是几种类型之一, 用竖线|分隔每个类型
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function getSmallPet(): Fish | Bird {
return {
layEggs: () => {
console.log("ss");
},
swim: () => {
console.log("ss");
},
fly: () => {
console.log("dee");
},
};
}
const pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // Property 'swim' does not exist on type 'Bird | Fish'.
pet.fly(); // Property 'fly' does not exist on type 'Bird | Fish'
// 交叉类型 使用符号&将多个类型合并为一个类型
interface IPerson {
id: string;
age: number;
}
interface IWorker {
companyId: string;
}
type IStaff = IPerson & IWorker; // 可以用type去自定义交叉类型
const staff: IStaff = {
id: "E1006",
age: 33,
companyId: "EXE",
};
console.dir(staff); // {age: 33companyId: "EXE"id: "E1006"}
七、类型断言和类型保护
// 类型断言 保证和检测数据是否符合我们的要求
interface Foo {
bar: number;
bas: string;
}
/* 需要给对象初始化一个值是空对象,得给每一个属性设置初始值,但有的属性初始值不确定 */
const foo: Foo = {
bar: 0,
bas: "",
};
const foo = {} as Foo; // 用 as 告诉编译器你知道你在做什么
// 类型保护
/* 在js中,有时候我们去访问对象的某个属性,但是可能这个属性不存在,那么就会为undefind,有时候会不符合我们的预期,
因此为了保证类型的安全,判断未知数据是不是所需类型,ts添加了类型保护,指定数据类型,通过 typeof、instanceof、in以及自定义类型保护的类型谓词 */
function isNumber(x: any): x is number {
return typeof x === "number";
}
八、模块
// 定义外部模块类型,创建文件.d.ts
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
declare module 'clipboard';
九、命名空间
// 区分不同的模块,比如说同样的音频类,含有同名方法、变量,可以做命名空间上的区分
/* eslint-disable @typescript-eslint/no-namespace */
export namespace Audio {
export class Audio1 {
/* ... */
}
export class Audio2 {
/* ... */
}
}
十、泛型
// 举例,约束的函数的参数和返回值
function identity (value: Number) : Number {
return value;
}
console.log(identity(1)) // 1
// 扩展成为通用类型,就可以使用泛型,设计泛型的关键目的是在成员之间提供有意义的约束
function identity<T>(value: T): T {
return value;
}
console.log(identity<string>('name')); // 'name'
任何有效的字母有可以做泛型符号,常见的泛型通用符号
- T (Type),在定义泛型时通常用作第一个类型变量名称
- K(Key):表示对象中的键类型;
- V(Value):表示对象中的值类型;
- E(Element):表示元素类型。
// 泛型可定义任何数量的类型变量
function test<T, U>(value: T, message: U): T {
console.log(message);
return value;
}
console.log(test<number, string>(68, "测试")); //测试 68
/* 可以不用指定number和string的类型,编译器自己会识别 */
console.log(test(68, "测试")); //测试 68
十一、声明合并
// 无函数成员合并接口
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
width: string; // 声明同一个属性的不同类型会报错
}
const box: Box = { height: 5, width: 6, scale: 10 };
// 有函数成员合并接口 同名函数会被视为重载
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
// 合并命名空间
namespace Animals {
export class Zebra { }
}
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
十二、Ts的优缺点
-
优点
1、代码的可读性和可维护性:定义后端接口返回值,编码时,编辑器会提醒接口返回值的类型
2、在编译阶段就发现大部分错误,避免了很多线上bug
-
缺点
1、有一定的学习成本,需要理解很多强类型语言有的概念
2、增加开发成本,项目工期紧张,定义类型会带来很多代码开发量
3、兼容没有类型的js库,需要增加很多声明文件
图片来源于网络,文章内容参考网上资料,也有自己的测试案例,如有任何问题,请联系更正或者删除