从零学习TS(二)

1,014 阅读7分钟

从零学习TS(二)

加入我们的对赌学习吧 @阿崔cxr

经过第一天的学习-温故而知新

TS官网文档地址

第一天的学习: 看到了 Type Assertions 类型断言

第二天的内容:从Type Assertions类型断言到Narrowing类型收缩

内容有点多:列出个人感觉重点内容吧

  • 类型断言 as 从属关系
  • 类型收缩的 使用类型判断的关键字
  • Discriminated unions 区分联合类型

其实很多点都是要静下来才能慢慢理解的

类型断言

方式有两种: 1、as 2、尖括号

PART 1: 使用

没有断言的时候 myCanvas 的类型推导是Element | null 加上 HTMLCanvasElement 的类型断言 类型就是变成 HTMLCanvasElement

const myCanvas1 = document.querySelector('#canvas_id') as HTMLCanvasElement;
const myCanvas2 = <HTMLCanvasElement>document.querySelector('#canvas_id')

tips: 一般使用as的方式 尖括号的的写法回合很多写法有冲突 除非是在.tsx文件。as 的写法也意思表达更明确

Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won’t be an exception or null generated if the type assertion is wrong. 提醒: 因为类型断言是在编译时删除的,所以不存在与类型断言关联的运行时检查。如果类型断言错误,则不会生成异常或 null。

这段内容 个人理解 是 被断言的类型是指定被赋予的类型,不会去额外的类型推到,所以使用的时候一定要对as的类型走出准确的定义

PART 2 使用要求

当然类型断言也不是绝对的任由你随意转变类型

const x = 'hello' as number

TS提示信息: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first

文档解释:TypeScript only allows type assertions which convert to a more specific or less specific version of a type. This rule prevents “impossible” coercions like: 限制: 只允许断言类型转化为更具体或更不具体的类型:

个人理解这段话:[A-Z] as A 这可以 [A-Z] => 1 是不可以的 A as [A-Z] 是可以的 1 => [A-Z] 是不可以的 被断言的两个类型需要与从属的关系 并且是相互的

来验证下:

type Word = {
    prop1: number
}

type BigWord = {
    prop2: string
} & Word

type OtherWord = {
    otherProp: number
}

let word: BigWord = {
    prop1: 1,
    prop2: 'word'
}

let word2 = {
    prop1: 2
}

// 从属关系 [A-Z] => A
let wordCorrect = word as Word // is OK
let wordError = word as OtherWord
// Conversion of type 'BigWord' to type 'OtherWord' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Property 'otherProp' is missing in type 'BigWord' but required in type 'OtherWord'

// 从属关系 A => [A-Z]
let word2Correct = word2 as BigWord // is OK
let word2Error = word2 as OtherWord
 // Conversion of type '{ prop1: number; }' to type 'OtherWord' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Property 'otherProp' is missing in type '{ prop1: number; }' but required in type 'OtherWord'.ts(2352)

比如我们的第一个as 的例子即使 HTMLCanvasElement 是从属 ELement

const canvas = document.querySelector('canvas') as Element // is OK

温故而知新 确实是有了新的认知啊

part 3 any 来了 强制修改的方式

const a = (expr as any) as T // 把 expr 强制改成T

Literal Types 文字类型

下面的例子: 根据 let const var 的特性 changingString 类型 是 string 而 constantString的类型是 ’hello world‘ constantString 就是文字类型

let changingString = 'hello world';

changingString = 'xyz-fish'

changingString

const constantString = 'hello world'

constantString

可以文字类型合联合, 数值文字类型一行的 --- ant-design源码里tuple tupleNum很好的利用这个

function printText(s: string, alignment: 'left' | 'center' | 'right') {
    // ...
}

printText('Hello World', 'left')
printText('xyz', 'bottom') // Argument of type '"bottom"' is not assignable to parameter of type '"left" | "center" | "right"'

单独的文字类型也可以和其他类型结合起来一起用

interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 });
configure("auto");
configure("automatic"); // Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.ts(2345)

附带: boolean 就是 true | false 的别名

Literal Inference 字面推理

呈上---- 我们字面类型 const stringA = 'string' 是可以推理出string的类型是 string 来看看下面的

const obj = { counter: 0 }
if (true) {
    obi.counter = 1
}

这里申请一个变量是Object 属性值是0 推理出的类型 counter: number

再来一个例子

const handleRequest = (url: string, method: 'GET' | 'POST') => { // ... }

const req = { url: "https://example.com", method: "GET"};
handleRequest(req.url, req.method) // Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'

情况当然可以定义给req定义一个类型 interface Req { url: string; method: 'GET‘ | ’POST‘ }

来看看利用文本类型处理

// 1
const req = { url: "https://example.com", method: "GET" as "GET" };
// 2: as const 会把req素有属性类型斗战成 文本类型 => { readonly url: "https://example.com"; readonly method: "GET";}
const req = { url: "https://example.com", method: "GET" } as const;

null & undefined

看下官方解释

JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.

JavaScript 有两个用于表示缺失或未初始化值的基本值: null 和 undefined。

  • 来看下相对应的tsconfig.json的配置

    strickNullChecks 打开和关闭来判断是否去严格校验

  • 遇到这种情况的一些处理 通过 ! 运算符 这样可以省去null 和 undefined 的判断

    function liveDangerously(x?: number | null) {
            // No error
            console.log(x!.toFixed());
    }
    

enums ---- 先过一遍

Less Common Primitives 不太常见的原语

1、bignit 类型 去用Bignit的时候 tsconfig里target: es2020

2、symbol

const firstName = Symbol('name') // 类型 typeof firstName
const secondName = Symbol('name') // 类型 typeof secondName

Narrowing 类型收缩

先来个例子看下什么叫做类型收缩

function padLeft(padding: number | string, input: string) {
    if (typeof padding === 'number') {
        return new Array(padding + 1).join('') + input
    }

    return padding + input
}

个人理解:对多种类型判断的判断分解处理 ---- 像我们学完后要做的项目 xufei的类型象棋 很多 extends ? 条件判断等

typeof type guard 类型保护

对于typeof 的判断也是根据js的判断来执行的: 对于 typeof === 'object' 的处理需要考虑到null、array的情况

有哪些类型收缩?

Truthiness narrowing 真实性缩小

在js中可于 (== 或者 !! Boolean) false 的时候

  1. 0
  2. NaN
  3. "" (the empty string)
  4. 0n (the bigint version of zero)
  5. null
  6. undefined

Equality narrowing 平等缩小

使用 switch 语句和相等性检查,比如 = = = 、 ! = = 、 = = 和! = 来缩小类型:

function example(x: string | number, y: string | boolean) {
    if (x === y) {
        // 这里x, y只能是 string
        x.toUpperCase();
        y.toLowerCase();
    } else {
        console.log(x);
        console.log(y);
    }
}

ts的强大在逐渐展露出来了

官方文档还给了一个例子:!= null 也可以 但一般建议用全等

The in operator narrowing - in 操作缩小

js里遍历 for in 的特性

instanceof 通过实例的判断

js里的关键字 instanceof

Assignments 工作分配

根据条件推理出联合类型

Control flow analysis 控制流分析

通过一些代码, TS会分子你的代码 把 定义的类型缩小 string | number | boolean => string | number

function example2() {
    let x: number | string | boolean

    // 代码流程开始
    x = Math.random() < 0.5

    if (Math.random() < 0.3) {
            x = 'hello'
    } else {
            x = 100
    }
    // 代码流程结束
    return x // 最终TS 分析 这里 类型是 number | string
}

Using type predicates 使用类型判断的关键字

使用方式: {param} is <type>

感觉重点又来了 代码来分析下吧

type Fish = { swim: () => void };
type Bird = { fly: () => void };

// 我们的值的类型是一个联合类型 怎么去使用呢
declare function getSmallPet(): Fish | Bird;

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined
}

let pet = getSmallPet(); // 这列类型是 Fish | Bird

if (isFish(Fish)) {
    pet.swim(); // 这里我们的pet类型缩小成 Fish
} else {
    pet.fly(); // 这里pet 类型缩小成了 Bird
}

// 可能会有这样的写法
if (pet.swim) { // 但是TS会提示 pet.swim 不存在 Fish | Bird, 也不能存在 Bird 
    // xxx
} else {}

所以这里 遇到联合类型的时候通过is来去缩小类型 然后再去在确定类型的块去写代码 TS 越来越香了

看下对于官方数组的一个类型缩小

 type Fish = { swim: () => void; name: string };
    type Bird = { fly: () => void; name: string };
    declare function getSmallPet(): Fish | Bird;
    function isFish(pet: Fish | Bird): pet is Fish {
            return (pet as Fish).swim !== undefined;
    }
    // ---cut---
    const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
    const underWater1: Fish[] = zoo.filter(isFish);
    // or, equivalently
    const underWater2: Fish[] = zoo.filter(isFish) as Fish[];

    // The predicate may need repeating for more complex examples
    const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
            if (pet.name === "sharkey") return false;
            return isFish(pet);
    });

Discriminated unions 区分联合类型

先来个问题场景:我们要绘制一个图: 这个图可以是: 圆形,也可以是:正方形; 圆形的时候要知道半径,正方形的时候要获知边长

// 定义一个参数类型
interface Shape {
    kind: 'circle' | 'square'
    raduis?: number
    sideLength?: number
}

// 定义画图的方法
function getArea(shape: Shape) {
    if (shape.kind === 'circle') {
        return Math.PI * shape.radius ** 2 // Object is possibly 'undefined'
    }
    return shape.sideLength **2 // also   Object is possibly 'undefined'
}

上述情况可咋搞呢???重点又来了。。

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

type ShapeCorret = Circle | Square;

function getArea2(shape: ShapeCorret) {
    if (shape.kind === 'circle') {
        return Math.PI * shape.radius ** 2
    }
    return shape.sideLength **2
}

上面的代码完美解决了 很多时候 和 累次判断is一起使用 很舒服

never

当类型被缩小到没有其他选项的时候 会有一个never的类型

Exhaustiveness checking 彻底检查

使我们的类型缩小 代码更加严谨

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

interface Three {
    kind: 'three',
    value: number
}

type Shape3 = Circle | Square;

function getArea3(shape: Shape3) {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "square":
            return shape.sideLength ** 2;
        default:
            const _exhaustiveCheck: never = shape; // 这里去检测没有其他的kind
            return _exhaustiveCheck;
    }
}

// 如果 Shape3 = Circle | Square | Three 就会报错

总结

收获很多,很重要的是在逐渐养成良好的学习习惯!大家共勉 加油