[ 万字总结 ] 夯实你的 TypeScript 基础!(二)(完结)

1,266 阅读9分钟

承接上篇 [ 万字总结 ] 夯实你的 TypeScript 基础!(一)

9.类型保护

通过判断识别所执行的代码块,自动识别变量属性和方法

一.typeof类型保护

function double(val: number | string) {
    if (typeof val === 'number') {
        val
    } else {
        val
    }
}

二.instanceof类型保护

class Cat { }
class Dog { }

const getInstance = (clazz: { new(): Cat | Dog }) => {
    return new clazz();
}
let r = getInstance(Cat);
if(r instanceof Cat){
    r
}else{
    r
}

三.in类型保护

interface Fish {
    swiming: string,
}
interface Bird {
    fly: string,
    leg: number
}
function getType(animal: Fish | Bird) {
    if ('swiming' in animal) {
        animal // Fish
    } else {
        animal // Bird
    }
}

四.可辨识联合类型

interface WarningButton {
    class: 'warning'
}
interface DangerButton {
    class: 'danger'
}
function createButton(button: WarningButton | DangerButton) {
    if (button.class == 'warning') {
        button // WarningButton
    } else {
        button // DangerButton
    }
}

五.null保护

const addPrefix = (num?: number) => {
    num = num || 1.1;
    function prefix(fix: string) {
        return fix + num?.toFixed()
    }
    return prefix('zf');
}
console.log(addPrefix());

这里要注意的是ts无法检测内部函数变量类型

六.自定义类型保护

interface Fish {
    swiming: string,
}
interface Bird {
    fly: string,
    leg: number
}
function isBird(animal: Fish | Bird):animal is Bird {
    return 'swiming' in animal
}
function getAniaml (animal:Fish | Bird){
    if(isBird(animal)){
        animal
    }else{
        animal
    }
}

七.完整性保护

interface ICircle {
    kind: 'circle',
    r: number
}
interface IRant {
    kind: 'rant',
    width: number,
    height: number
}
interface ISquare {
    kind: 'square',
    width: number
}
type Area = ICircle | IRant | ISquare
const isAssertion = (obj: never) => { }
const getArea = (obj: Area) => {
    switch (obj.kind) {
        case 'circle':
            return 3.14 * obj.r ** 2
        default:
            return isAssertion(obj); // 必须实现所有逻辑
    }
}

10.类型推断

一.赋值推断

赋值时推断,类型从右像左流动,会根据赋值推断出变量类型

let str = 'wj';
let age = 11;
let boolean = true;

二.返回值推断

自动推断函数返回值类型

function sum(a: string, b: string) {
    return a + b;
}
sum('a','b');

三.函数推断

函数从左到右进行推断

type Sum = (a: string, b: string) => string;
const sum: Sum = (a, b) => a + b;

四.属性推断

可以通过属性值,推断出属性的类型

let person = {
    name:'zf',
    age:11
}
let {name,age} = person;

五.类型反推

可以使用typeof关键字反推变量类型

let person = {
    name:'zf',
    age:11
}
type Person = typeof person

六.索引访问操作符

interface IPerson {
    name:string,
    age:number,
    job:{
        address:string
    }
}
type job = IPerson['job']

七.类型映射

interface IPerson {
    name:string,
    age:number
}
type MapPerson = {[key in keyof IPerson]:IPerson[key]}

11.交叉类型

交叉类型(Intersection Types)是将多个类型合并为一个类型

interface Person1 {
    handsome: string,
}
interface Person2 {
    high: string,
}
type P1P2 = Person1 & Person2;
let p: P1P2 = { handsome: '帅', high: '高' }

举例:我们提供两拨人,一拨人都很帅、另一拨人很高。我们希望找到他们的交叉部分 => 又高又帅的人

  • 交叉类型
function mixin<T, K>(a: T, b: K): T & K {
    return { ...a, ...b }
}
const x = mixin({ name: 'zf' }, { age: 11 })
interface IPerson1 {
    name:string,
    age:number
}

interface IPerson2 {
    name:number
    age:number
}
type person = IPerson1 & IPerson2
let name!:never
let person:person = {name,age:11};  // 两个属性之间 string & number的值为never

12.条件类型

一.条件类型基本使用

可以使用extends关键字和三元表达式,实现条件判断

interface Fish {
    name1: string
}
interface Water {
    name2: string
}
interface Bird {
    name3: string
}
interface Sky {
    name4: string
}
type Condition<T> = T extends Fish ? Water : Sky;
let con1: Condition<Fish> = { name2: '水' }

二.条件类型分发

let con2: Condition<Fish|Bird> = { name2: '水' } 

这里会用每一项依次进行分发,最终采用联合类型作为结果,等价于:

type c1 = Condition<Fish>;
type c2 = Condition<Bird>;
type c = c1 | c2

三.内置条件类型

  • 1.Exclude排除类型
type Exclude<T, U> = T extends U ? never : T;
type MyExclude = Exclude<'1' | '2' | '3', '1' | '2'>
  • 2.Extract抽取类型
type Extract<T, U> = T extends U ? T : never;
type MyExtract = Extract<'1' | '2' | '3', '1' | '2'>
  • 3.NoNullable 非空检测
type NonNullable<T> = T extends null | undefined ? never : T
type MyNone = NonNullable<'a' | null | undefined>

四.infer类型推断

  • 1.ReturnType返回值类型
function getUser(a: number, b: number) {
  return { name: 'zf', age: 10 }
}
type ReturnType<T> = T extends (...args: any) => infer R ? R : never
type MyReturn = ReturnType<typeof getUser>
  • 2.Parameters 参数类型
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type MyParams = Parameters<typeof getUser>;
  • 3.ConstructorParameters构造函数参数类型
class Person {
  constructor(name: string, age: number) { }
}
type ConstructorParameters<T> = T extends { new(...args: infer R): any } ? R : never
type MyConstructor = ConstructorParameters<typeof Person>
  • 4.InstanceType 实例类型
type InstanceType<T> = T extends { new(...args: any): infer R } ? R : any
type MyInstance = InstanceType<typeof Person>

五.infer实践

将数组类型转化为联合类型

type ElementOf<T> = T extends Array<infer E> ? E : never;
type TupleToUnion = ElementOf<[string, number, boolean]>;

将两个函数的参数转化为交叉类型

type T1 = { name: string };
type T2 = { age: number };
type ToIntersection<T> = T extends ([(x: infer U) => any, (x: infer U) => any]) ? U : never;
type t3 = ToIntersection<[(x:T1)=>any,(x:T2)=>any]>

表示要把T1T2赋予给x,那么x的值就是T1T2的交集。(参数是逆变的可以传父类)

TS的类型:TS主要是为了代码的安全性来考虑。所以所有的兼容性问题都要从安全性来考虑!

13.内置类型

一.Partial转化可选属性

interface Company {
    num: number
}
interface Person {
    name: string,
    age: string,
    company: Company
}
// type Partial<T> = { [K in keyof T]?: T[K] }; 实现原理
type PartialPerson = Partial<Person>;

遍历所有的属性将属性设置为可选属性,但是无法实现深度转化!

type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
type DeepPartialPerson = DeepPartial<Person>;

我们可以实现深度转化,如果值是对象继续深度转化。

二.Required转化必填属性

interface Company {
    num: number
}
interface Person {
    name: string,
    age: string,
    company: Company
}
type PartialPerson = Partial<Person>;
type Required<T> = {[K in keyof T]-?:T[K]} 
type RequiredPerson = Required<PartialPerson>

将所有的属性转化成必填属性

三.Readonly转化仅读属性

type Readonly<T> = { readonly [K in keyof T]: T[K] }
type RequiredPerson = Readonly<Person>

将所有属性变为仅读状态

四.Pick挑选所需的属性

type Pick<T, U extends keyof T> = { [P in U]: T[P] }
type PickPerson = Pick<Person, 'name' | 'age'>

在已有类型中挑选所需属性

五.Record记录类型

type Record<K extends keyof any, T> = { [P in K]  : T }
let person: Record<string, any> = { name: 'zf', age: 11 };

实现map方法,我们经常用record类型表示映射类型

function map<T extends keyof any, K, U>(obj: Record<T, K>, callback: (item: K, key: T) => U) {
    let result = {} as Record<T, U>
    for (let key in obj) {
        result[key] = callback(obj[key], key)
    }
    return result
}
const r = map({ name: 'zf', age: 11 }, (item, key) => {
    return item
});

六.Omit忽略属性

let person = {
    name: 'wj',
    age: 11,
    address: '回龙观'
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type OmitAddress = Omit<typeof person, 'address'>

忽略person中的address属性 (先排除掉不需要的key,在通过key选出需要的属性)

14.装包和拆包

一.装包

type Proxy<T> = {
    get():T,
    set(value:T):void
}
type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>
} 
let props = {
    name: 'wj',
    age: 11
}
function proxify<T>(obj:T):Proxify<T>{
    let result = {} as Proxify<T>;
    for(let key in obj){
        let value = obj[key];
        result[key] = {
            get(){
                return value
            },
            set:(newValue)=>value = newValue
        }
    }
    return result
}
let proxpProps = proxify(props);

二.拆包

function unProxify<T>(proxpProps:Proxify<T>):T{
    let result = {} as T;
    for(let key in proxpProps){
        let value = proxpProps[key];
        result[key] = value.get()
    }
    return result
}
let proxy = unProxify(proxpProps)

15.自定义类型

一.Diff实现

求两个对象不同的部分

let person1 = {
    name: 'wj',
    age: 11,
    address: '回龙观'
}
let person2 = {
    address: '回龙观',
}
type Diff<T extends object,K extends Object> = Omit<T,keyof K>
type DiffPerson = Diff<typeof person1,typeof person2>

二.InterSection交集

let person1 = {
    name: 'wj',
    age: 11,
    address: '回龙观'
}
let person2 = {
    address: '回龙观',
}
type InterSection<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>>
type InterSectionPerson = InterSection<typeof person1, typeof person2>

三.Overwrite属性覆盖

type OldProps = { name: string, age: number, visible: boolean };
type NewProps = { age: string, other: string };

type Diff<T extends object,K extends Object> = Omit<T,keyof K>
type InterSection<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>>
type Overwrite<T extends object, K extends object, I = Diff<T,K> & InterSection<K,T>> = Pick<I,keyof I>
type ReplaceProps = Overwrite<OldProps, NewProps>

如果存在已有属性则使用新属性类型进行覆盖操作

四.Merge对象合并

type Compute<A extends any> = { [K in keyof A]: A[K] };
type Merge<T, K> = Compute<Omit<T, keyof K> & K>;
type MergeObj = Merge<OldProps,NewProps>

将两个对象类型进行合并操作

16.unknown

一.unknown类型

unknown类型,任何类型都可以赋值为unknown类型。 它是 any 类型对应的安全类型

let unknown:unknown;
unknown = 'zf';
unknown = 11;

不能访问unknown类型上的属性,不能作为函数、类来使用

  • 联合类型中的unknown

    type UnionUnknown = unknown | null | string | number
    

    联合类型与unknown都是unknown类型

  • 交叉类型中的unknown

    type inter = unknown & null
    

    交叉类型与unknown都是其他类型

二.unknown特性

  • never是unknown的子类型

    type isNever = never extends unknown ? true : false;=
    
  • keyof unknown 是never

    type key = keyof unknown;
    
  • unknown类型不能被遍历

    type IMap<T> = {
        [P in keyof T]:number
    }
    type t = IMap<unknown>;
    

unknown类型不能和number类型进行 +运算,可以用于等或不等操作

17.模块和命名空间

默认情况下 ,我们编写的代码处于全局命名空间中

一.模块

文件模块: 如果在你的 TypeScript 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域 。

// a.ts导出
export default 'zf'

// index.ts导入
import name from './a'

二.命名空间

命名空间可以用于组织代码,避免文件内命名冲突

  • 命名空间的使用

    export namespace zoo {
        export class Dog { eat() { console.log('zoo dog'); } }
    }
    export namespace home {
        export class Dog { eat() { console.log('home dog'); } }
    }
    
    let dog_of_zoo = new zoo.Dog();
    dog_of_zoo.eat();
    let dog_of_home = new home.Dog();
    dog_of_home.eat();
    
  • 命名空间嵌套使用

    export namespace zoo {
        export class Dog { eat() { console.log('zoo dog'); } }
        export namespace bear{
            export const name = '熊'
        } 
    }
    console.log(zoo.bear.name); 
    

命名空间中导出的变量可以通过命名空间使用。

18.类型声明

一.声明全局变量

  • 普通类型声明
declare let age: number;
declare function sum(a: string, b: string): void;
declare class Animal { };
declare const enum Seaons{
    Spring,
    Summer,
    Autumn,
    Winter
}
declare interface Person {
    name:string,
    age:number
}

类型声明在编译的时候都会被删除,不会影响真正的代码。目的是不重构原有的js代码,而且可以得到很好的TS支持

练习: 声明jQuery类型

jquery通过外部CDN方式引入,想在代码中直接使用

declare const $:(selector:string)=>{
    height(num?:number):void
    width(num?:number):void
};
$('').height();
  • 命名空间声明
declare namespace jQuery {
    function ajax(url:string,otpions:object):void;
    namespace fn {
        function extend(obj:object):void
    }
}
jQuery.ajax('/',{});
jQuery.fn.extend({});

namespace表示一个全局变量包含很多子属性 , 命名空间内部不需要使用 declare 声明属性或方法

二. 类型声明文件

类型声明文件以.d.ts结尾。默认在项目编译时会查找所有以.d.ts结尾的文件

// jquery.d.ts
declare const $:(selector:string)=>{
    height(num?:number):void
    width(num?:number):void
};

declare namespace jQuery {
    function ajax(url:string,otpions:object):void;
    namespace fn {
        function extend(obj:object):void
    }
}

三.编写第三方声明文件

配置tsconfig.json

  • jquery声明文件
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
    "*": ["types/*"]
},
// types/jquery/index.d.ts

declare function jQuery(selector: string): HTMLElement;
declare namespace jQuery {
    function ajax(url: string): void
}
export = jQuery;
  • events模块声明文件
import { EventEmitter } from "zf-events";
var e = new EventEmitter();
e.on('message', function (text) {
   console.log(text)
})
e.emit('message', 'hello');
export type Listener = (...args: any[]) => void;
export type Type = string | symbol

export class EventEmitter {
   static defaultMaxListeners: number;
   emit(type: Type, ...args: any[]): boolean;
   addListener(type: Type, listener: Listener): this;
   on(type: Type, listener: Listener): this;
   once(type: Type, listener: Listener): this;
}

四.模块导入导出

import $ from 'jquery'  // 只适用于 export default $

const $ = require('jquery'); // 没有声明文件可以直接使用 require语法

import * as $ from 'jquery'  // 为了支持 Commonjs规范 和 AMD规范 导出时采用export = jquery

import $ = require('jquery')  // export = jquery 在commonjs规范中使用

五.第三方声明文件

@types是一个约定的前缀,所有的第三方声明的类型库都会带有这样的前缀

npm install @types/jquery -S

当使用jquery时默认会查找 node_modules/@types/jquery/index.d.ts 文件

查找规范

  • node_modules/jquery/package.json 中的types字段
  • node_modules/jquery/index.d.ts
  • node_modules/@types/jquery/index.d.ts

19.扩展全局变量类型

一.扩展局部变量

可以直接使用接口对已有类型进行扩展

interface String {
    double():string
}
String.prototype.double = function () {
    return this as string + this;
}
let str = 'wj';
interface Window {
    mynane:string
}
console.log(window.mynane)

二.模块内全局扩展

declare global{
    interface String {
        double():string;
    }
    interface Window{
        myname:string
    }
}

声明全局表示对全局进行扩展

三.声明合并

同一名称的两个独立声明会被合并成一个单一声明,合并后的声明拥有原先两个声明的特性。

1.同名接口合并

interface Animal {
    name:string
}
interface Animal {
    age:number
}
let a:Animal = {name:'zf',age:10};

2.命名空间的合并

  • 扩展类

    class Form {}
    namespace Form {
        export const type = 'form'
    }
    
  • 扩展方法

    function getName(){}
    namespace getName {
        export const type = 'form'
    }
    
  • 扩展枚举类型

    enum Seasons {
        Spring = 'Spring',
        Summer = 'Summer'
    }
    namespace Seasons{
        export let Autum = 'Autum';
        export let Winter = 'Winter'
    }
    

3.交叉类型合并

import { createStore, Store } from 'redux';
type StoreWithExt = Store & {
    ext:string
}
let store:StoreWithExt

四.生成声明文件

配置tsconfig.json 为true 生成声明文件

"declaration": true

完结撒花

以上就是 TypeScript 基础的全部啦,掌握了这些对于面试已经是绰绰有余,不过对于语言来说还是需要多加练习哟。欢迎点赞收藏 !