TypeScript 指北!

95 阅读7分钟

TypeScript vs JavaScript

TypeScript 是"强类型"版的JavaScript,当我们在代码中定义变量(普通变量、函数、组件、hook)等的时候,TypeScript 允许我们在定义的同时指定其类型,这样使用者在使用不当或不规范的时候就会被及时报错提醒

TypeScript 的几种类型

1.number

数字类型,包括小数、其它进制的数字:

let decimal: number = 6; 
let hex: number = 0xf00d; 
let binary: number = 0b1010; 
let octal: number = 0o744; 
let big: bigint = 100n;

2.string

字符串

let name:string = "vanYu";

3.array

在 TS 中,array 一般指所有元素类型相同的集合,比如:

let list:Array<number> = [1,2,3]
// or
interface User {
    name:string
}
const vanYu = {name:'vanYu'}
const Avan = {name:'Avan'}
let personList = [vanYu,Avan] // 这里 vanYu 和 Avan 都是 User 类型的

和这种混合类型的"数组":

let mList = ['vanYu',10]

注意:在 TS 中不是 数组/array,它们叫做 tuple,下面会提到

4.boolean

布尔值

let isDone:boolean = false

5.函数

两种方法 1.在我们熟悉的"JS函数"上直接声明参数和返回值

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

2.直接声明你想要的的函数类型:

export const useMount = (fn: () => void) => { 
    useEffect(() => {
        fn(); 
    }, []); 
}; 
const isFalsy: (value: any) => boolean = (value) => { 
    return value === 0 ? true : !!value; 
};

6. any

any 表示这个值可以是任何值,被定义为 any 就意味着不做任何类型检查

let looselyTyped: any = 4;
// looselyTyped 的值明明是个4,哪里来的ifItExists方法呢?
// 由于声明为any,我们没法在静态检查阶段发现这个错误
looselyTyped.ifItExists();

初学 TS 的童鞋经常会为了让 TS 不再报错就用了很多 any,这样做会失去 TS 的保护。童鞋们应该尽量避免使用 any

7. unknown

unknown 表示这个值可以是任何值

有童鞋可能会问,这和 any 有什么区别呢?
unknown 的用法:在你想用 any 的时候,用 unknown 来代替,简单来说,unknown是一个"严格"版的 any

const isFalsy = (value: unknown) => { 
 // 大家不用考虑这段console有啥意义,把它打在你的代码里对应的位置,观察编辑器会不会报错;
 // 再思考它应不应该报错
  console.log(value.mayNotExist)
  return value === 0 ? true : !!value; 
}; 

8. void

void 类型绝大部分情况下,只会用在这一个地方:表示函数不返回任何值或者返回undefined (因为函数不返回任何值的时候 === 返回 undefined)

const doSomething = (callback: () => void) => { 
    let c = callback() // 在这个位置,回调总是返回 undefined //c 也是 undefined 类型 
}

9. object

除了 number, string, boolean, bigint, symbol, null, or undefined,其它都是 object

10. tuple

tuple 是 "数量固定,类型可以各异" 版的数组


let x: [string, number];
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error

// 可以定义复杂的tuple
type NestedTuple = [string, Array<number>, [boolean, {name: string}]]
let testTuple: NestedTuple = ['1', [1, 2, 3], [true, { name: 'vanYu' }]]; // ok

11.enum

枚举(enum) 类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等

1.基础用法

enum Color {Red, Green, Blue}
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
console.log(c,d,e) //0,1,2

2.设置初始值

enum Color {Red, Green=2, Blue,Yellow=7 ,Dark}
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
let f: Color = Color.Yellow;
let g: Color = Color.Dark;
console.log(c,d,e,f,g) //0 2 3 7 8

3.属性获取

enum Color {Red, Green=2, Blue,Yellow=7 ,Dark}
let c1: string = Color[0];
let c: Color = Color.Red;
let d1: string = Color[1];
let d: Color = Color.Green;
let e1: string = Color[2];
let e: Color = Color.Blue;
let f1: string = Color[3];
let f: Color = Color.Yellow;
let g1: string = Color[4];
let g: Color = Color.Dark;

console.log(c1,c,d1,d,e1,e,f1,f,g1,g) //Red 0 undefined 2 Green 3 Blue 7 undefined 8
// 属性获取时按照属性对应的值来获取的,所以会有 undefined

4.应用场景

// 后端返回的字段使用 0 - 6 标记对应的日期,这时候就可以使用枚举可提高代码可读性
// 包括后端日常返回0、1 等等状态的时候,我们都可以通过枚举去定义,这样可以提高代码的可读性,便于后续的维护
enum Days {SunMonTueWedThuFriSat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

12. null 和 undefined

nullundefined 在 TypeScript 中既是一个值,也是一个类型:

let u: undefined = undefined; 
let n: null = null;

13. never

详细解释和用法可参考尤大的博客www.zhihu.com/question/35…

// 这个 func返回的就是never类型,用到比较少,在类型操作等场景会用到
const func = () => {
  throw new Error()
}

泛型

泛型 简单来说就是类型变量
泛型 可以用来创建重用的组件,一个组件可以支持多种类型的数据。这样用户就可以以自己的数据类型来使用组件

T代表 Type,在定义泛型时通常作第一个类型变量名称。但实际上可以用任何有效名称代替。除了T之外,以下是常见泛型变量代表的意思:

  • K(Key): 表示对象中键类型;
  • V(Value): 表示对象中值类型;
  • E(Element): 表示元素类型;

1.基本使用

// 这里`identity`方法可以将`number`类型分配给参数和返回类型,使该函数仅可用该原始类型。但该函数并不是可扩展或通用的。
// 如果我们把`number`换成了`any`,那么就是去了定义返回哪种类型的能力,也会是去编译器类型保护的作用
function identity(value:number):number{
  return value;
}
console.log(identity(1))  // 1


// 为了实现任何特定的类型,我们可以使用泛型来解决这个问题,具体实现如下:
function identity<T>(value:T):T{
  return value;
}
console.log(identity<number>(1))  // 返回1 
console.log(identity(1))  // 更常见的做法是省略尖括号内的类型变量,编译器会自动选择类型 返回1 

2.进阶使用

// 定义多个类型变量 引入希望定义的任何数量的类型变量
function identity <T, U>(value: T, message: U) : T {
  console.log(message);
  return value;
}
console.log(identity<number, string>(68, "Semlinker"));

/* 相比之前定义的 `identity` 函数,新的 `identity` 函数增加了一个类型变量 `U`,
但该函数的返回类型我们仍然使用 `T`。如果我们想要返回两种类型的对象该怎么办呢?
针对这个问题,我们有多种方案,其中一种就是使用元组,即为元组设置通用的类型:*/
function identity <T, U>(value: T, message: U) : [T, U] {
  return [value, message];
}

3.泛型接口

interface Identities<V,M>{
  value: v,
  message: M
}
/*在上述的`Identities`接口中,我们引入了类型变量`V`和`M`,进一步说明有效的字母都可以用于表示类型变量,
之后我们就可以将`Identities`接口作为`identity`函数的返回类型:*/
function identity<T, U> (value: T, message: U): Identities<T, U>{
  console.log(value + ": " + typeof (value));
  console.log(message + ": " + typeof (message));
  let identities: Identities<T, U> = {
    value,
    message
  };
  return identities;
}
console.log(identity(68, "Semlinker"));
// 68: number
// Semlinker: string
// {value: 68, message: "Semlinker"}

interface

interface 不是一种类型,应该被翻译成 接口,或者说使用上面介绍的类型,创建一个我们自己的类型

interface User {
  id: number;
}
const u: User = {id: 1}

啥时候需要声明类型

理论上来说在我们声明任何变量的时候都需要声明类型(包括普通变量、函数、组件、hook等等),声明 函数、组件、hook 等需要声明参数 和 返回值的类型。

但是在很多情况下,TS可以帮我们自动推断,我们就不用声明了,比如:

// 这里虽然没有显式声明,但是ts自动推断这是个number
let a = 1

// 自动推断返回值为number
function add(a: number, b: number) {
  return a + b;
}

// 自动推断返回值为boolean
const isFalsy = (value: unknown) => { 
  return value === 0 ? true : !!value; 
}; 

.d.ts

JS 文件 + .d.ts 文件 === ts 文件

.d.ts 文件可以让 JS 文件继续维持自己JS文件的身份,而拥有TS的类型保护

一般我们写业务代码不会用到,但是点击类型跳转一般会跳转到 .d.ts文件