TS 基本知识梳理

Posted by CodingWithAlice on October 19, 2022

TS 基本知识梳理(泛型)

第四课

除了视频,学习内容还参考了 RYF的TS教程

一、泛型

前因

主要是为了解决类型声明时,函数返回值的类型和参数类型是相关时,无法反映出参数和返回值之间的类型关系。

特点

带有“类型参数” - 函数名后面尖括号的部分<T>,就是类型参数。

泛型可以理解成一段类型逻辑,需要类型参数来表达。

本质

泛型本质上是一个类型函数,通过输入类型参数,可以在输入类型与输出类型之间,建立一一对应关系。

注意⚠️

可以设置类型参数的默认值,但是 TypeScript 会从实际参数推断 T 的值,覆盖掉默认值,所以不会报错。

​ —> 只是在这种调用时才知道 T 的类型的情况下不报警。如果是实例化一个泛型类,设置了默认类型后,再调用时如果不符合类型校验,是会报错的

// 函数的参数类型是 T[],返回值的类型是 T
function getCode<T = string>(arr: T[]): T {
    return arr[0];
}
// 函数调用时,需要提供类型参数; 也可以省略,让 TS 自己推断
getCode<number>([1, 2, 3]);
getCode([1, 2, 3]); // 不会报错,T的类型被推断为 number,覆盖掉默认值 string

有个热知识:ts 是一种鸭子类型的语言,面向接口编程,而不是面向对象编程 -> 其实 ts 并不关心变量是什么类型,只要满足接口的都可以

  • 看下面的案例,最常用的 useState 的函数签名如下,函数后面的 表示泛型占位符,字母是什么不重要,但是在后面使用的时候,表示入参是 S 类型,输出也是 S

image-20221020091529232


常见写法

泛型通常使用在 函数、接口和类、别名中,以下是不同场景中的写法:

  1. 函数中
// 泛型函数 - 类型参数放在尖括号中,写在函数名后面
function getCode<T>(arg: T): T {
    return arg;
}

变量形式定义的函数

// 两种写法
let myCode1: <T>(arg: T) => T = getCode;
let myCode2: { <T>(arg: T): T } = getCode;
  1. 接口中

第一种写法:类型参数定义在整个接口,接口内部的所有属性/方法都可以使用该类型参数

interface Code<Type> {
    contents: Type;
}
// 泛型接口,调用时需要给出类型参数的值
let code: Code<string>;
// 另一个例子 - 将接口中泛型的写法,结合类使用
interface Comparator<T> {
    compareTo(value: T): number;
}

class Rectangle implements Comparator<Rectangle> {
    compareTo(value: Rectangle): number {
        // ...
    }
}

第二种写法:将类型参数定义在接口的某个方法之中,其他属性/方法不能使用该类型参数

interface Fn {
    <T>(arg: T): T;
}
function getCode<T>(arg: T): T {
    return arg;
}
// 具体的类型需要在 getCode 使用时提供
let code: Fn = getCode;
  1. 类中
// 泛型类的参数写在类名后面
class Rectangle<K, V> {
    key: K;
    value: V;
}
// 继承泛型类的例子
class R<T> {
    value: T;
}
// 继承时,必须给出泛型类的类型参数
class X extends R<any> {}

类的本质是一个构造函数,因此泛型类也可以写成构造函数

type MyCode<T> = new (...arg: any[]) => T;
// 或者
interface MyCode<T> {
    new (...arg: any[]): T;
}

注意:泛型类描述的是 类的实例,不包括静态属性/方法,因为静态属性/方法的定义在类的本身,因此不能引用类型参数。

  1. 别名中
// type 命令定义的类型别名也可以使用泛型
type A<T> = T | undefined | null;

树形结构的例子:

type Tree<T> = {
    value: T;
    left: Tree<T> | null; // 递归饮用了 Tree 自身
    right: Tree<T> | null;
}

二、类型参数的约束条件

TS提供了一种语法,允许在类型参数上面写明约束条件,不满足约束条件时,编译时报错。

// 约束条件:T extends {length: number}
// 表示类型参数 T 必须满足 {length: number}, 否则就会报错
function getCode<T extends {length: number}>(p1: T, p2: T) {
    if(p1.length >= p2.length) {
        return p1;
    }
    return p2;
}
  • 在 interface 中也可以使用 - <T = Element> 表示默认为 Element 类型
interface FormEvent<T = Element> extends SyntheticEvent<T> {}
  • 同时设置约束条件和默认值(默认值必须满足约束条件)
type Fn<A extends string, B extends string = "Alice"> = [A, B];
type Result = Fn<"hello">; // ['hello', 'Alice']

.d.ts + js 文件 = ts 文件

tsc(TS官方提供的编译器-一个npm模块)作用:把 .ts 转变成 .js,给出错误,但是仍旧生成编译产物。

三、TS 类型(纯基础记录)

所有的类型名称都是小写字母,大写字母是JS中内置对象,而不是类型名称

1、number + bigint
// number 包含所有整数、浮点数、非十进制数
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
// bigint 和 number 不兼容(大整数)
let big: bigint = 100n; 
let big1: bigint = 123; // 报错
2、string - 普通字符串/模版字符串
let name: string = "Alice"; // `${x} Alice`
3、array - 所有元素 类型相同 的值的集合

:tada:注意:在 js 中 [‘Alice’, 428] 是一个数组,在 TS 中不算数组,叫做 tuple

let list: Array<number> = [1, 2, 3];
// 也可以表示成 list: number[]
4、boolean
let isFalse: boolean = false;
5、函数

两种声明方法:1⃣️ 在 js 函数上直接声明参数和返回值

const isFalsy = (value: any) : boolean => {
  return value === 0 ? true : !!value;
} 

2⃣️ 声明想要的函数类型

const isFalsy: (value: any) => boolean = (value) => {
  return value === 0 ? true : !!value;
} 
6、any

不推荐使用,使用 any 相当于关闭了变量的类型检查(甚至会污染其他变量,把错误留到进行时 -> TS引入 unkown类型解决)

7、void

一般只使用在一个地方:表示函数不返回任何值或返回 undefined

8、object

除了 number、string、boolean、bigint、symbol、null、undefined 都是 object

在 JS 的设计中,所有对象、数组、函数都是 object 类型

9、tuple

另一种 array - “数量固定,类型可以各异”,在 js 中不区分,在 ts 中区分

const [name, setName] = useState<string>('')

使用场景 - custom hook 的返回值 - 没经验,截图留念先

image-20221019212157924

10、enum
enum Color {
  Yellow,
  Green,
  Blue,
  Red
}

let c: Color = Color.Green;
11、undefined 和 null

既是一个值,也是一个类型

注意⚠️:如果没有声明类型的变量被赋值给 undefined 或者 null,它们的类型会被推断为 any(在设置编译选项 strictNullChecks 可以被推断为正确类型)

let u: undefined = undefined;
let n: null = null;
12、unkown - 严格版本的 any

和 any 的 共同点:表示这个值可以是任何值

和 any 的 不同点

  1. 不能直接赋值给其他类型(除了unkown本身和 any)-> 避免了污染
let a: unkown = 1;
let b: boolean = a; // 报错,unkown 不能赋值给 boolean
let c: number = a; //报错,unkown不能赋值给 number
  1. 不能直接调用 unkown 变量的方法和属性
let obj: unkown = {a: 1};
obj.a; // 报错,unkown 不能被调用

let fun: unkown = (n = 0) => n+1;
fun(); // 报错,unkown 不能被调用

let str :unkown = 'hello';
str.trim(); // 报错,unkown 不能被调用
  1. unkown 只能进行比较运算、取反运算、typeof 运算符、instanceof 运算符,其他的运算都会报错
let num: unkown = 1;
num + 1; //报错,unkown 不能被运算
num === 1; // 正确
13、never 空类型 -> 即不可能有这样的值
let x: never; // 不能给变量x赋值,否则都会报错

特点:可以赋值给任意其他类型(空集是任何集合的子集)

// 函数抛错就是返回的 never 类型
const fun: () => never = () => {
  throw new Error()
}
let num: number = fun(); // 正常
let str: string = fun(); // 正常

总结:TS中 anyunkown是两个顶层类型,但是只有一个底层类型never

14、symbol
const x: symbol = Symbol();

四、知识点补充

1、TS规定,变量只有赋值后才能使用,否则就会报错

2、TS的代码只涉及类型,所有跟值相关的代码都由JS完成,TS代码的编译过程,实际上就是把“类型代码”全部拿掉,只保留“值代码”