TypeScript 高级类型
1-类型别名
类型别名用来为类型起个新名字
type Name = string;
let nameValue: Name = "张三";
type Point = {
x: number;
y: number;
add: (x: number, y: number) => number;
};
let point: Point = {x: 100, y: 200, add:(x: number, y: number) => x + y};
观察下面代码中存在的问题
let pizzaSize: 'large' | 'medium' | 'small' = 'medium'
function selectPizzaSize (size: 'large' | 'medium' | 'small') {
pizzaSize = size
}
selectPizzaSize('small')
以上代码同一个类型被定义了多次 重复代码太多 可以使用 type 创建类型 复用类型
type Size = "large" | "medium" | "small";
let pizzaSize: Size = "medium";
function selectPizzaSize(size: Size) {
pizzaSize = size
}
selectPizzaSize("large")
2-联合类型
联合类型可以把变量设置为多种类型
// 联合类型 为一个变量设置多个类型
let a: string | number | boolean = true
// arg 参数即可以是字符串类型也可以是数值类型
function fn(arg: string | number) {}
fn("a")
fn(10)
在使用联合类型的变量时 默认只能使用联合类型中所有类型中的公共属性 因为 TypeScript 编译器并不能确定它的具体类型是什么 所以不能准确的例出它的下面有哪些属性和方法
在使用联合类型的变量时通常需要先缩小变量的类型范围 这样编辑器能给出更加精准的代码提示
观察下面代码中存在的问题
// 披萨的大小
let pizzaSize: 'large' | 'medium' | 'small' = 'medium';
function selectPizzaSize (size: 'large' | 'medium' | 'small') {
pizzaSize = size
};
selectPizzaSize("small");
以上代码同一个类型被声明了多次导致重复代码过多 可以使用 type 声明类型复用类型
type Size = 'large' | 'medium' | 'small';
let pizzaSiez: Size = 'medium';
function selectPizzaSize(size: Size) {
pizzaSize = size
};
selectPizzaSize('large');
3-交叉类型
交叉类型是指把多个类型叠加合并组成新的类型 新类型中包含了被合并类型的所有属性
//type Draggable = {
// drag:() => void;
//};
//type Resizable = {
// resize: () => void;
//};
//type UIWidget = Draggable & Resizable;
//let textBox: UIWidget = {
// drag: () => {},
// resize: () => {},
//};
interface IPerson {
id: string;
age: number;
}
interface IWoeker {
companyId: string;
}
type IStaff = IPerson & IWorker;
const staff: IStaff = {
id: 'E1066',
age: 33,
companyId: 'EFT'
};
console.dir(staff)
在上面示例中 我们首先为IPerson和IWorker 类型定义了不同的成员 然后通过 & 运算符定义了 IStaff 交叉类型 所以类型同时拥有 IPerson 和 IWorker 这两种类型的成员
4-字面量类型
字面量类型是指字面量值得类型作为变量的类型 把字面量作为变量的取值范围
type Quan = 10;
let quan: Quan = 10;
type Quan = 10 | 100;
let quan: Quan = 1;
// 不能把类型 1 分配给类型 Quan
type Quan = "cm" | "inch";
let quan: Quan = "abc";
// 不能把类型 abc 分配给 Quan
5-可空类型
在TypeScript 中 undefined 和 null 这两个值本身也可以作为类型使用
let name: string;
name = "Marius"; // ok
name = null; // ok
name = undefined; // ok
let age: number;
age = 24; // ok
age = null; // ok
age = undefined; // ok
// undefined
let nothing: undefined = undefined;
// null
let nothingMuch: null = null;
function greet(name: string | null | undefined) {
if (typeof name === "string") {
console.log(name.toLowerCase());
}else {
console.log("something went wrong");
}
}
greet(undefined)
在严格的 null 检查模式中 null 和 undefined 不在分配给每个类型 null 和 undefined 现在都有自己的类型 每个类型只有一个值
6-可选链操作符
目标:掌握可选链操作符的使用方式
分析一段代码 javaScript 代码 为讲解可选链操作符做铺垫
可选链操作符和对象一起使用
可选链操作符和数组一起使用
可选链操作符和函数一起使用
先看一段 javaScript 中的代码 分析代码中的潜在问题
// person 对象中的 name 属性是可选的 即 person 对象中可能有 name 属性也可能没有 name 属性
// 如果 name 属性存在 它是字符串类型
const person = {name: "John"};
console.log(person.name.toUpperCase()); // JOHN
const person = {}
console.log(person.name.toUpperCase());
const person = {}
if (person.name) console.log(person.name.toUpperCase());
可选链操作符(?.)允许开发者安全的链式访问一个对象上可能为 null 或 undefined 的属性
可选链操作符和对象一起使用
interface Person{
name?: string;
}
const person: Person = {name: "张三"};
person.name?.toLocaleUpperCase();
可选链操作符和数组一起使用
interface Person {
skills: string[] | null;
}
const person: Person = {
skills: ["编程", "开锁", "飞檐走壁"],
};
person.skills?.forEach((item) => console.log(item));
可选链操作符和函数一起使用
interface Person {
sayHello?: () => void;
}
const person: Person = {
sayHello: () => {
alert("Hello");
},
};
person.sayHello?.();
7-空值合并运算符
空值合并运算符(??)是一个逻辑运算符 当左侧的运算数为null 或者 undefined 时 返回其右侧运算数 否则返回左侧运算数
let speed: number | null = null;
let ride = {
// speed: speed !== null ? speed: 30,
speed: speed ?? 30,
}
8-类型断言
通过类型断言可以覆盖 TypeScript 编译器的推断 当开发者逼 TypeScript 更加清除它的类型时使用
// const phone: HTMLElement | null
const phon = document.getElementById("phone");
console.log((<HTMLInputElement>phone).value);
console.log((phone as HTMLInputElement).value);
类型断言 可以用来手动指定一个值得类型
类型断言的用途:
把一个联合类型断言为其中一个类型
把一个父类断言为更加具体的子类
把任何一个类型断言为 any
把 any 断言为一个具体的类型
类型断言只会影响 TypeScript 编译时的类型 类型断言语句在编译结果中会被删除
function toBoolean(something: any): boolean{
return something as boolean;
}
toBoolean(1);
// 返回值 1
// 在上面例子中 把 something 断言为 boolean 虽然可以通过编译 但是并没有什么用 代码在编译后会变成
function toBoolean(something) {
return something;
}
toBoolean(1)
// 返回值 1
// 所以类型断言不是类型转换 它不会真的影响到变量的类型
9-unknown类型
unknown 是严格的 any 类型 在对 unknown 类型的变量执行操作之前必须确定它的类型
let anything: unknown = "hello TypeScript";
anything = true;
anything = 3.14;
anything = function() {};
if (typeof anything === "number") {
anything.toFixed();
}else if (typeof anything === "string") {
anything.toUpperCase();
}else if (typeof anything === "function") {
anything();
}
10-never类型
问题:对应一个接口 如何定义某个属性为 number 其它不确定的属性都为 string
在解决这个问题时 never 类型就可以派上大用场
interface IType {
age: number;
[key: string]: string;
}
这里要用到几个知识点
never 是任何类型的子类型 也就是说 never 可以赋值给任何类型
下面的两行代码 都不会有TS 错误
const a: number = "" as never;
const b: object = "" as never;
类型扩张
注意:有些地方叫 类型缩减 但是从表现出来的现象来看 叫类型缩减更适合一些
两个类型的联合类型有可能会发生类型扩张 例如
type URStr = '123' | string;
// 类型是 string
‘123’ 是 string 类型的子类型 在经过联合之后 联合类型就变成 string 类型了 由于 “123” 类型范围小 string 类型的范围比较大 因此感觉叫类型扩张更合适一些
有了上面两个知识点 我们就可以写成下面的样子 在 ITypeKeyAny 类型中的 age 类型 设置为 never 后 不会和下面的 string 类型冲突 因为 never 类型是任何类型的子类型 在和 ITypeAge 类型联合也不会冲突 并且 age 类型扩张为 number 类型
interface ITypeAge {
age: number;
};
interface ITypeKeyAny{
age: never;
[key: string]: string;
}
type Itype = ITypeAge | ITypeKeyAny;
never 表示永远不会发生的类型 即永远不能有值 比如用抛出错误的函数 用执行无限循环的函数 它们的返回值就是 never 再比如 never【】 表示数组中不会有值
const throwError = (message: string): never => {
throw new Error(message);
};
// const throwError: (message: string) => void
const throwError = (message: string) => {
if(!message) throw new Error("error");
};
注意:如果一个函数永远都不会有返回值 说明函数调用位置后的代码永远都不会执行