光看不练假把式:ts练习题
类型
1、8种内置类型(基本类型)
let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
//JavaScript 中的常规数字以 64 位的格式 IEEE-754 存储,也被称为双精度浮点数。
//BigInt 数字,用于表示任意长度的整数。有时会需要它们,因为常规数字不能安全地超过 253 或小于 -253,仅在少数特殊领域才会用到 BigInt。
//BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。
//不能使用Number和BigInt操作数的混合执行算术运算,需要通过显式转换其中的一种类型。 此外,出于兼容性原因,不允许在BigInt上使用一元加号(+)运算符。
参考:https://blog.csdn.net/m0_50914413/article/details/112706493
let big: bigint = 100n;
let sym: symbol = Symbol("me");
默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给其他类型。
如果你在tsconfig.json指定了"strictNullChecks": true ,null 和 undefined 只能赋值给 void 和它们各自的类型。
2、数组
let arr: string[] = ["a", "b"];
let arr2: Array<string> = ["a", "b"];
let arr3: (number | string)[] = ["a", 1, "b", 2]// 联合类型
interface ArrObj {
userName: String,
age: number
}
let arr4: ArrObj[] = [{userName: 'a', age: 2}]//接口
3、函数
//函数声明
function sum1(x: number, y: number) {
return x + y
}
//函数表达式
let sum2: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y
}
//接口定义函数
interface FunType {
(x: number, y?: number): number
}
let sum3: FunType = function (x: number, y?: number): number {
return x + y
}
//默认值
let sum4: FunType = function (x: number = 0, y?: number): number {
return x + y
}
//剩余参数
function push(array: any[], ...items: any[]) {
items.forEach(function (item) {
array.push(item);
});
}
let arr = [];
push(arr, 1, 2, 3);
// 函数重载
type Combinable = string | number
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
4、元组
//元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。
let x: [string, number];
// 类型必须匹配且个数必须为2
x = ['hello', 10]; // OK
x = ['hello', 10,10]; // Error
x = [10, 'hello']; // Error
//解构赋值
let employee: [number, string] = [1, "Semlinker"];
let [id, username] = employee;
console.log(`id: ${ id } `);
console.log(`username: ${ username } `);
//可选元素
let optionalTuple: [string, boolean?];
//剩余元素
type RestTupleType = [number, ...string[]];
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];
console.log(restTuple[0]);
console.log(restTuple[1]);
//只读元组属性
const point: readonly [number, number] = [10, 20];
5、void
void表示没有任何类型,和其他类型是平等关系,不能直接赋值:
只能为它赋予null和undefined(在strictNullChecks未指定为true时)。声明一个void类型的变量没有什么大用,我们一般也只有在函数没有返回值时去声明。
let a: void;
let b: number = a; // Error
方法没有返回值将得到undefined,但是我们需要定义成void类型,而不是undefined类型。否则将报错:
function fun(): undefined {
console.log("this is TypeScript");
};
fun(); // Error
6、never
never类型表示的是那些永不存在的值的类型。
never类型同null和undefined一样,也是任何类型的子类型,也可以赋值给任何类型。
但是没有类型是never的子类型或可以赋值给never类型(除了never本身之外),即使any也不可以赋值给never
①、如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了);
②、函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
// 异常
function err(msg: string): never { // OK
throw new Error(msg);
}
// 死循环
function loopForever(): never { // OK
while (true) {};
}
7、any
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型.
8、unknown
unknown与any一样,所有类型都可以分配给unknown
unknown与any的最大区别是: 任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknown和any
9、原始类型 number、string、boolean、symbol 混淆的首字母大写的 Number、String、Boolean、Symbol 类型
从类型兼容性上看,原始类型兼容对应的对象类型,反过来对象类型不兼容对应的原始类型。
let num: number;
let Num: Number;
Num = num; // ok
num = Num; // ts(2322)报错
不要使用对象类型来注解值的类型
10、object、Object 和 { }
object 代表的是所有非原始类型,也就是说我们不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,null 和 undefined 类型也不能赋给 object。
let lowerCaseObject: object;
lowerCaseObject = 1; // ts(2322)
lowerCaseObject = 'a'; // ts(2322)
lowerCaseObject = true; // ts(2322)
lowerCaseObject = null; // ts(2322)
lowerCaseObject = undefined; // ts(2322)
lowerCaseObject = {}; // ok
Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object。
let upperCaseObject: Object;
upperCaseObject = 1; // ok
upperCaseObject = 'a'; // ok
upperCaseObject = true; // ok
upperCaseObject = null; // ts(2322)
upperCaseObject = undefined; // ts(2322)
upperCaseObject = {}; // ok
大 Object 包含原始类型,小 object 仅包含非原始类型,所以大 Object 似乎是小 object 的父类型。实际上,大 Object 不仅是小 object 的父类型,同时也是小 object 的子类型。
type isLowerCaseObjectExtendsUpperCaseObject = object extends Object ? true : false; // true
type isUpperCaseObjectExtendsLowerCaseObject = Object extends object ? true : false; // true
upperCaseObject = lowerCaseObject; // ok
lowerCaseObject = upperCaseObject; // ok
{ }空对象类型和大 Object 一样,也是表示原始类型和非原始类型的集合,并且在严格模式下,null 和 undefined 也不能赋给 { }
综上结论:{ }、大 Object 是比小 object 更宽泛的类型(least specific),{ } 和大 Object 可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。
类型处理
1、类型推断
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
2、类型断言
// 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
非空断言
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531) 严格模式下
确定赋值断言
允许在实例属性和变量声明后面放置一个! 号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:
let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error
function initialize() {
x = 10;
}
很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:
let x!: number;
initialize();
console.log(2 * x); // Ok
function initialize() {
x = 10;
}
通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。######3、字面量类型
字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。
定义单个的字面量类型并没有太大的用处,它真正的应用场景是可以把多个字面量类型组合成一个联合类型
type Direction = 'up' | 'down';
function move(dir: Direction) {
// ...
}
move('up'); // ok
move('right'); // ts(2345) Argument of type '"right"' is not assignable to parameter of type 'Direction'
interface Config {
size: 'small' | 'big';
isEnable: true | false;
margin: 0 | 2 | 4;
}
4、类型拓宽(Type Widening)
所有通过 let 或 var 定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未显式添加类型注解的条件,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽。
let str = 'this is string'; // 类型是 string
let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
const specifiedStr = 'this is string'; // 类型是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;
当你在一个值之后使用 const 断言时,TypeScript 将为它推断出最窄的类型,没有拓宽。对于真正的常量,这通常是你想要的。当然你也可以对数组使用 const 断言:
5、类型缩小(Type Narrowing)
{
let func = (anything: any) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else if (typeof anything === 'number') {
return anything; // 类型是 number
}
return null;
};
}
帮助类型检查器缩小类型的另一种常见方法是在它们上放置一个明确的 “标签”:
interface UploadEvent {
type: "upload";
filename: string;
contents: string;
}
interface DownloadEvent {
type: "download";
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case "download":
e; // Type is DownloadEvent
break;
case "upload":
e; // Type is UploadEvent
break;
}
}
5、联合类型
联合类型表示取值可以为多种类型中的一种,使用 | 分隔每个类型。
联合类型通常与 null 或 undefined 一起使用:
const sayHello = (name: string | undefined) => {
/* ... */
};
6、类型别名
类型别名用来给一个类型起个新名字。类型别名常用于联合类型。
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
扩展
interface PointX {
x: number
}
type Point = PointX & {
y: number
}
8、交叉类型
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,使用 & 定义交叉类型。
交叉类型真正的用武之地就是将多个接口类型合并成一个类型,从而实现等同接口继承的效果,也就是所谓的合并接口类型,如下代码所示
type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
id: 1,
name: 'name',
age: 18
}
如果同名属性的类型不兼容,比如上面示例中两个接口类型同名的 name 属性类型一个是 number,另一个是 string,合并后,name 属性的类型就是 number 和 string 两个原子类型的交叉类型,即 never,如下代码所示:
type IntersectionTypeConfict = { id: number; name: string; }
& { age: number; name: number; };
const mixedConflict: IntersectionTypeConfict = {
id: 1,
name: 2, // ts(2322) 错误,'number' 类型不能赋给 'never' 类型
age: 2
};
接口(Interfaces)
interface Person {
name: string;
age: number;
}
//定义的变量比接口少或者多了一些属性是不允许的:
let tom: Person = {
name: 'Tom',
age: 25
};
可选 | 只读属性
interface Person {
readonly name: string;
age?: number;
}
任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number; // 这里真实的类型应该为:number | undefined
[propName: string]: string | number | undefined;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
接口扩展
type PointX = {
x: number
}
interface Point extends PointX {
y: number
}
泛型
######单个类型
function identity<T>(arg: T): T {
return arg;
}
######多个类型
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity<Number, string>(68, "Semlinker"));
泛型约束(使用 extends 关键字)
function trace<T>(arg: T): T {
console.log(arg.size); // Error: Property 'size doesn't exist on type 'T'
return arg;
}
interface Sizeable {
size: number;
}
function trace<T extends Sizeable>(arg: T): T {
console.log(arg.size);
return arg;
}
泛型工具类型
1、typeof 的主要用途是在类型上下文中获取变量或者属性的类型。
interface Person {
name: string;
age: number;
}
const sem: Person = { name: "semlinker", age: 30 };
type Sem = typeof sem; // type Sem = Person
const lolo: Sem = { name: "lolo", age: 5 }
const Message = {
name: "jimmy",
age: 18,
address: {
province: '四川',
city: '成都'
}
}
type message = typeof Message;
/*
type message = {
name: string;
age: number;
address: {
province: string;
city: string;
};
}
*/
此外,typeof 操作符除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
2、keyof
该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
实际用途
function prop<T extends object,K extends keyof T>(obj:T, key:K) {
return obj[key];
}
3、 in (用来遍历枚举类型)
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
4、infer(声明一个类型变量并且对它进行使用。)
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
5、extends (添加泛型约束)
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
6、索引类型
在实际开发中,我们经常能遇到这样的场景,在对象中获取一些属性的值,然后建立对应的集合。
let person = {
name: 'musion',
age: 35
}
function getValues<T,K extends keyof T>(person: T, keys: K[]):T[K][] {
return keys.map(key => person[key])
}
7、映射类型(根据旧的类型创建出新的类型, 我们称之为映射类型)
interface TestInterface{
name:string,
age:number
}
// 我们可以通过+/-来指定添加还是删除
type OptionalTestInterface<T> = {
[p in keyof T]+?:T[p]
}
type newTestInterface = OptionalTestInterface<TestInterface>
// type newTestInterface = {
// name?:string,
// age?:number
// }
比如我们再加上只读
type OptionalTestInterface<T> = {
+readonly [p in keyof T]+?:T[p]
}
type newTestInterface = OptionalTestInterface<TestInterface>
// type newTestInterface = {
// readonly name?:string,
// readonly age?:number
// }
8、Partial < T > 将类型的属性变成可选
Partial的定义
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface UserInfo {
id: string;
name: string;
}
//将属性变为可选,如果要处理多层,需自己实现
type NewUserInfo = Partial<UserInfo>;
9、DeepPartial
type DeepPartial<T> = {
// 如果是 object,则递归类型
[U in keyof T]?: T[U] extends object
? DeepPartial<T[U]>
: T[U]
};
type PartialedWindow = DeepPartial<T>; // 现在T上所有属性都变成了可选啦
10、Required(Required将类型的属性变成必选)
type Required<T> = {
[P in keyof T]-?: T[P]
};
Readonly < T > 的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
11、Pick 从某个类型中挑出一些属性出来
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
12、Record(将 K 中所有的属性的值转化为 T 类型)
type Record<K extends keyof any, T> = {
[P in K]: T;
};
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const x: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" },
};
13、ReturnType(用来得到一个函数的返回值类型)
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: any;
type Func = (value: number) => string;
const foo: ReturnType<Func> = "1";
14、Exclude < T, U > 的作用是将某个类型中属于另一个的类型移除掉。
type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
15、Extract < T, U > 的作用是从 T 中提取出 U。
type Extract<T, U> = T extends U ? T : never;
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
16、Omit < T, K extends keyof any > 的作用是使用 T 类型中除了 K 类型的所有属性,来构造一个新的类型。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
17、NonNullable < T > 的作用是用来过滤类型中的 null 及 undefined 类型。
type NonNullable<T> = T extendsnull | undefined ? never : T;
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
18、Parameters<T> 的作用是用于获得函数的参数类型组成的元组类型。
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any
? P : never;
type A = Parameters<() =>void>; // []
type B = Parameters<typeofArray.isArray>; // [any]
type C = Parameters<typeofparseInt>; // [string, (number | undefined)?]
type D = Parameters<typeofMath.max>; // number[]