TS 笔记

190 阅读23分钟

环境配置

  • npm install typescript -g
  • npm i @types/node --save-dev
  • npm i ts-node --g
ts-node xxx.js // 编译成js

对象的类型

typescript中,我们定义对象的方式要用关键字interface(接口),我的理解是使用interface来定义一种约束,让数据的结构满足约束的格式。定义方式如下:

//重名interface  可以合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}
//继承
interface A{
    name:string
}
 
interface B extends A{
    age:number
}
 
let obj:B = {
    age:18,
    name:"string"
}

任意属性 [propName: string]

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

//在这个例子当中我们看到接口中并没有定义C但是并没有报错
//因为我们定义了[propName: string]: any;
//允许添加新的任意属性
interface Person {
    b?:string,
    a:string,
    [propName: string]: any;
}
 
const person:Person  = {
    a:"213",
    c:"123"
}

只读属性 readonly

readonly 只读属性是不允许被赋值的只能读取

//这样写是会报错的
//应为a是只读的不允许重新赋值
interface Person {
    b?: string,
    readonly a: string,
    [propName: string]: any;
}
 
const person: Person = {
    a: "213",
    c: "123"
}
 
person.a = 123

Readonly

  • 这有一个 Readonly 的映射类型,它接收一个泛型 T,用来把它的所有属性标记为只读类型
type Foo = {
  bar: number;
  bas: number;
};

type FooReadonly = Readonly<Foo>;

const foo: Foo = { bar: 123, bas: 456 };
const fooReadonly: FooReadonly = { bar: 123, bas: 456 };

foo.bar = 456; // ok
fooReadonly.bar = 456; // Error: bar 属性只读

添加函数

interface Person {
    b?: string,
    readonly a: string,
    [propName: string]: any;
    cb():void
}
 
const person: Person = {
    a: "213",
    c: "123",
    cb:()=>{
        console.log(123)
    }
}

多维数组

let data:number[][] = [[1,2], [3,4]];
let arr: Array<Array<number>> = [[1, 2],[3, 4]]

arguments类数组

function Arr(...args:any): void {
    console.log(arguments)
    //错误的arguments 是类数组不能这样定义
    let arr:number[] = arguments
}
Arr(111, 222, 333)
 
 
 
function Arr(...args:any): void {
    console.log(arguments) 
    //ts内置对象IArguments 定义
    let arr:IArguments = arguments
}
Arr(111, 222, 333)
 
//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

函数

function add(x: string, y: string): string { // 求和的函数
    return x + y
}
  • 为了使用接口表示函数类型,我们需要给接口定义一个调用签名
  • 它就像是一个只有参数列表和返回值类型的函数定义,参数列表里的每个参数都需要名字和类型
// 函数的完整的写法
(x: number, y: number) => number 当前的这个函数的类型
function (x: number, y: number): number { return x+y }  就相当于符合上面的这个函数类型的值

const add: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y
}

或者

  • 定义一个接口,用来作为某个函数的类型使用
interface ISearchFn {
    // 定义一个调用签名
    (source: string, substring: string):boolean
}
// 定义一个函数,该类型就是上面定义的接口
const searchFn: ISearchFn = function (source: string, substring: string):boolean {
    return source.search(substring) > -1
}
// 调用函数
searchFn('dddsdasdddwf', 'd')

函数剩余参数

// ...args:string[] ---->剩余的参数,放在了一个字符串的数组中,args里面
  function showMsg(str: string,str2:string, ...args: string[]) {
    console.log(str) // a
    // console.log(str2) // b
    console.log(args) // b ,c ,d ,e
  }
  showMsg('a','b','c','d','e')

函数的重载

  • 重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
  • 如果参数类型不同,则参数类型应设置为 any
  • 参数数量不同你可以将不同的参数设置为可选
// 重载函数
function fn(params: number): void
function fn(params: string, params2: number): void

// 执行函数
function fn(params: any, params2?: any): void {
 
    console.log(params)
 
    console.log(params2)
 
}
 
 
 
fn(123)
 
fn('123',456)

类型断言 | 联合类型 | 交叉类型

联合类型

//例如我们的手机号通常是13XXXXXXX 为数字类型 这时候产品说需要支持座机
//所以我们就可以使用联合类型支持座机字符串
let myPhone: number | string  = '010-820'

//这样写是会报错的应为我们的联合类型只有数字和字符串并没有布尔值
let myPhone: number | string  = true

交叉类型

interface People {
  age: number,
  height: number
}
interface Man{
  sex: string
}
const zhangsan = (man: People & Man) => {
  console.log(man.age)
  console.log(man.height)
  console.log(man.sex)
}
zhangsan({age: 18,height: 180,sex: 'male'});

类型断言

  • 语法:
- 值 as 类型      或   <类型>值
- value as string       <string>value
interface A {
       run: string
}
interface B {
       build: string
}
const fn = (type: A | B): string => {
       return type.run
}
// 这样写是有警告的 因为B的接口上面是没有定义run这个属性的
interface A {
       run: string
}
 
interface B {
       build: string
}
 
const fn = (type: A | B): string => {
       return (type as A).run
}
//可以使用类型断言来推断他传入的是A接口的值

需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:

//这样写会报错因为window没有abc这个东西
window.abc = 123

//可以使用any临时断言在 any 类型的变量上,访问任何属性都是允许的。
(window as any).abc = 123

as const

是对字面值的断言,与const直接定义常量是有区别的

如果是普通类型跟直接const 声明是一样的

const names = 'zs'
names = 'aa' //无法修改
 
let names2 = 'zs' as const
names2 = 'aa' //无法修改
// 数组
let a1 = [10, 20] as const;
const a2 = [10, 20];
 
a1.unshift(30); // 错误,此时已经断言字面量为[10, 20],数据无法做任何修改
a2.unshift(30); // 通过,没有修改指针

内置对象

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
//读取div 这种需要类型断言 或者加个判断应为读不到返回null
let div:HTMLElement = document.querySelector('div') as HTMLDivElement
document.addEventListener('click', function (e: MouseEvent) {
    
});
//dom元素的映射表
interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;
    "address": HTMLElement;
    "applet": HTMLAppletElement;
    "area": HTMLAreaElement;
    "article": HTMLElement;
    "aside": HTMLElement;
    "audio": HTMLAudioElement;
    "b": HTMLElement;
    "base": HTMLBaseElement;
    "bdi": HTMLElement;
    "bdo": HTMLElement;
    "blockquote": HTMLQuoteElement;
    "body": HTMLBodyElement;
    "br": HTMLBRElement;
    "button": HTMLButtonElement;
    "canvas": HTMLCanvasElement;
    "caption": HTMLTableCaptionElement;
    "cite": HTMLElement;
    "code": HTMLElement;
    "col": HTMLTableColElement;
    "colgroup": HTMLTableColElement;
    "data": HTMLDataElement;
    "datalist": HTMLDataListElement;
    "dd": HTMLElement;
    "del": HTMLModElement;
    "details": HTMLDetailsElement;
    "dfn": HTMLElement;
    "dialog": HTMLDialogElement;
    "dir": HTMLDirectoryElement;
    "div": HTMLDivElement;
    "dl": HTMLDListElement;
    "dt": HTMLElement;
    "em": HTMLElement;
    "embed": HTMLEmbedElement;
    "fieldset": HTMLFieldSetElement;
    "figcaption": HTMLElement;
    "figure": HTMLElement;
    "font": HTMLFontElement;
    "footer": HTMLElement;
    "form": HTMLFormElement;
    "frame": HTMLFrameElement;
    "frameset": HTMLFrameSetElement;
    "h1": HTMLHeadingElement;
    "h2": HTMLHeadingElement;
    "h3": HTMLHeadingElement;
    "h4": HTMLHeadingElement;
    "h5": HTMLHeadingElement;
    "h6": HTMLHeadingElement;
    "head": HTMLHeadElement;
    "header": HTMLElement;
    "hgroup": HTMLElement;
    "hr": HTMLHRElement;
    "html": HTMLHtmlElement;
    "i": HTMLElement;
    "iframe": HTMLIFrameElement;
    "img": HTMLImageElement;
    "input": HTMLInputElement;
    "ins": HTMLModElement;
    "kbd": HTMLElement;
    "label": HTMLLabelElement;
    "legend": HTMLLegendElement;
    "li": HTMLLIElement;
    "link": HTMLLinkElement;
    "main": HTMLElement;
    "map": HTMLMapElement;
    "mark": HTMLElement;
    "marquee": HTMLMarqueeElement;
    "menu": HTMLMenuElement;
    "meta": HTMLMetaElement;
    "meter": HTMLMeterElement;
    "nav": HTMLElement;
    "noscript": HTMLElement;
    "object": HTMLObjectElement;
    "ol": HTMLOListElement;
    "optgroup": HTMLOptGroupElement;
    "option": HTMLOptionElement;
    "output": HTMLOutputElement;
    "p": HTMLParagraphElement;
    "param": HTMLParamElement;
    "picture": HTMLPictureElement;
    "pre": HTMLPreElement;
    "progress": HTMLProgressElement;
    "q": HTMLQuoteElement;
    "rp": HTMLElement;
    "rt": HTMLElement;
    "ruby": HTMLElement;
    "s": HTMLElement;
    "samp": HTMLElement;
    "script": HTMLScriptElement;
    "section": HTMLElement;
    "select": HTMLSelectElement;
    "slot": HTMLSlotElement;
    "small": HTMLElement;
    "source": HTMLSourceElement;
    "span": HTMLSpanElement;
    "strong": HTMLElement;
    "style": HTMLStyleElement;
    "sub": HTMLElement;
    "summary": HTMLElement;
    "sup": HTMLElement;
    "table": HTMLTableElement;
    "tbody": HTMLTableSectionElement;
    "td": HTMLTableDataCellElement;
    "template": HTMLTemplateElement;
    "textarea": HTMLTextAreaElement;
    "tfoot": HTMLTableSectionElement;
    "th": HTMLTableHeaderCellElement;
    "thead": HTMLTableSectionElement;
    "time": HTMLTimeElement;
    "title": HTMLTitleElement;
    "tr": HTMLTableRowElement;
    "track": HTMLTrackElement;
    "u": HTMLElement;
    "ul": HTMLUListElement;
    "var": HTMLElement;
    "video": HTMLVideoElement;
    "wbr": HTMLElement;
}

定义文件,在 TypeScript 核心库的定义文件

Class 类

  • protected 修饰符 代表定义的变量私有的只能在内部和继承的子类中访问 不能在外部访问
class Person {
    public name:string
    private age:number 
    protected some:any
    constructor (name:string,ages:number,some:any) {
       this.name = name
       this.age = ages
       this.some = some
    }
    run () {
 
    }
}
 
class Man extends Person{
    constructor () {
        super("张三",18,1)
        console.log(this.some)
    }
    create () {
       console.log(this.some)
    }
}
let xiaoman = new Person('小满',18,1)
let man = new Man()
man.some // 报错
  • static 静态属性 和 静态方法
  • 用static 定义的属性 不可以通过this 去访问 只能通过类名去调用

image.png

image.png

  • static 静态函数 同样也是不能通过this 去调用 也是通过类名去调用

image.png

image.png

  • 需注意: 如果两个函数都是static 静态的是可以通过this互相调用

image.png

  • interface 定义类 使用关键字 implements   后面跟interface的名字多个用逗号隔开 继承还是用extends
 
interface PersonClass {
  get(type: boolean): boolean
}
interface PersonClass2{
  set():void,
  asd:string
}
class A {
  name: string
  constructor() {
      this.name = "123"
  }
}
class Person extends A implements PersonClass,PersonClass2 {
  asd: string
  constructor() {
      super()
      this.asd = '123'
  }
  get(type:boolean) {
      return type
  }
  set () {

  }
}
console.log(new Person())

image.png

抽象类

  • 应用场景

    • 如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
    • 或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法
  • 下面这段代码会报错抽象类无法被实例化

abstract class A {
   public name:string
   
}
new A() // 报错

例子2 我们在A类定义了 getName 抽象方法但未实现

我们B类实现了A定义的抽象方法 如不实现就不报错 我们定义的抽象方法必须在派生类实现

abstract class A {
   name: string
   constructor(name: string) {
      this.name = name;
   }
   print(): string {
      return this.name // 非抽象方法可以实现方法体
   }
 
   abstract getName(): string
}
 
class B extends A {
   constructor() {
      super('zs')
   }
   getName(): string {
      return this.name
   }
}
 
let b = new B();
b.print() // 非抽象方法可以实现方法体
console.log(b.getName());

never类型

  • TypeScript 将使用 never 类型来表示不应该存在的状态
// 返回never的函数必须存在无法达到的终点
 
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
    throw new Error(message);
}
 
// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
    while (true) {
    }
}
  • never 与 void 的差异

    //void类型只是没有返回值 但本身不会出错
    function Void():void {
        console.log();
    }
 
    //只会抛出异常没有返回值
    function Never():never {
    throw new Error('aaa')
    }
  • never 类型的一个应用场景

interface A {
    type: "foo"
}
 
interface B {
    type: "bar"
}
type All = A | B ;
function handleValue(val: All) {
    switch (val.type) {
        case 'foo':
            break;
        case 'bar':
            break
        default:
            //兜底逻辑 一般是不会进入这儿如果进来了就是程序异常了
            const exhaustiveCheck:never = val;
            break
    }
}
  • 比如新来了一个同事他新增了一个C接口,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。 而且这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG。那 TS 有没有办法帮助我们在类型检查阶段发现这个问题呢?
interface A {
    type: "foo"
}
 
interface B {
    type: "bar"
}
interface C {
    type: "bizz"
}
type All = A | B | C;
function handleValue(val: All) {
    switch (val.type) {
        case 'foo':
            break;
        case 'bar':
            break
        default:
            //兜底逻辑 一般是不会进入这儿如果进来了就是程序异常了
 
            const exhaustiveCheck: never = val;
            break
    }
}
  • 现在联合了C接口,由于任何类型都不能赋值给 never 类型的变量,所以当存在进入 default 分支的可能性时,TS的类型检查会及时帮我们发现这个问题

symbol类型

  • symbol类型的值是通过Symbol构造函数创建的
  • 可以传递参数作为唯一标识 只支持 string 和 number类型的参数
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key
  • Symbol的值是唯一的
const s1 = Symbol()
const s2 = Symbol()
// s1 === s2 =>false
  • 用作对象属性的键
let sym = Symbol();
let obj = {
    [sym]: "value"
};
console.log(obj[sym]); // "value"
  • 使用symbol定义的属性,是不能通过如下方式遍历拿到的
const symbol1 = Symbol('666')
const symbol2 = Symbol('777')
const obj1= {
   [symbol1]: '小满',
   [symbol2]: '二蛋',
   age: 19,
   sex: '女'
}
// 1 for in 遍历
for (const key in obj1) {
   // 注意在console看key,是不是没有遍历到symbol1
   console.log(key)
}
// 2 Object.keys 遍历
Object.keys(obj1)
console.log(Object.keys(obj1))
// 3 getOwnPropertyNames
console.log(Object.getOwnPropertyNames(obj1))
// 4 JSON.stringfy
console.log(JSON.stringify(obj1))
  • 如何拿到
// 1 拿到具体的symbol 属性,对象中有几个就会拿到几个
Object.getOwnPropertySymbols(obj1)
console.log(Object.getOwnPropertySymbols(obj1)) // [ Symbol(666), Symbol(777) ]
// 2 es6 的 Reflect 拿到对象的所有属性
Reflect.ownKeys(obj1)
console.log(Reflect.ownKeys(obj1)) // [ 'age', 'sex', Symbol(666), Symbol(777) ]

Symbol.iterator 迭代器 和 生成器 for of

  • 支持遍历大部分类型迭代器 Array NodeList argumetns Set Map 等,不包括Object
var arr = [1,2,3,4];
let iterator = arr[Symbol.iterator]();
 
console.log(iterator.next());  //{ value: 1, done: false }
console.log(iterator.next());  //{ value: 2, done: false }
console.log(iterator.next());  //{ value: 3, done: false }
console.log(iterator.next());  //{ value: 4, done: false }
console.log(iterator.next());  //{ value: undefined, done: true }

泛型

  • 函数泛型
function Sub<T,U>(a:T,b:U):Array<T|U> {
    const params:Array<T|U> = [a,b]
    return params
}
Sub<Boolean,number>(false,1)

配合 axios 使用

  • 通常情况下,我们会把后端返回数据格式单独放入一个 interface 里:
// 请求接口数据
export interface ResponseData<T = any> {
  /**
   * 状态码
   * @type { number }
   */
  code: number;

  /**
   * 数据
   * @type { T }
   */
  result: T;

  /**
   * 消息
   * @type { string }
   */
  message: string;
}
  • 当我们把 API 单独抽离成单个模块时:
// 在 axios.ts 文件中对 axios 进行了处理,例如添加通用配置、拦截器等
import Ax from './axios';

import { ResponseData } from './interface.ts';

export function getUser<T>() {
  return Ax.get<ResponseData<T>>('/somepath')
    .then(res => res.data)
    .catch(err => console.error(err));
}
  • 接着我们写入返回的数据类型 User,这可以让 TypeScript 顺利推断出我们想要的类型:
interface User {
  name: string;
  age: number;
}

async function test() {
  // user 被推断出为
  // {
  //  code: number,
  //  result: { name: string, age: number },
  //  message: string
  // }
  const user = await getUser<User>();
}

泛型约束

  • 我们期望在一个泛型的变量上面,获取其length参数,但是,有的数据类型是没有length属性的,这时候我们就可以使用泛型约束
  • 于是,我们就得对使用的泛型进行约束,我们约束其为具有length属性的类型,这里我们会用到interface,代码如下
interface Len {
   length:number
}
function getLegnth<T extends Len>(arg:T) {
  return arg.length
}
getLegnth<string>('123')

使用keyof 约束对象

  • 其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型
function prop<T, K extends keyof T>(obj: T, key: K) {
   return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a') 
prop(o, 'd') //此时就会报错发现找不到
// 用于创建字符串列表映射至 `K: V` 的函数
function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
  return o.reduce((res, key) => {
    res[key] = key;
    return res;
  }, Object.create(null));
}

// 创建 K: V
const Direction = strEnum(['North', 'South', 'East', 'West']);

/* 
const Direction: {
    North: "North";
    South: "South";
    East: "East";
    West: "West";
}
*/

// 创建一个类型
type Direction = keyof typeof Direction;
/* 
type Direction = "North" | "South" | "East" | "West"
*/


// 简单的使用
let sample: Direction;

sample = Direction.North; // Okay
sample = 'North'; // Okay
sample = 'AnythingElse'; // ERROR!

keyof 操作符能让你捕获一个类型的键。例如,你可以使用它来捕获变量的键名称,再通过使用 typeof 来获取类型之后:

const colors = {
  red: 'red',
  blue: 'blue'
};

type Colors = keyof typeof colors;

let color: Colors; // color 的类型是 'red' | 'blue'
color = 'red'; // ok
color = 'blue'; // ok
color = 'anythingElse'; // Error

枚举

  • 枚举是组织收集有关联变量的一种方式
export enum EvidenceTypeEnum {
  UNKNOWN = '',
  PASSPORT_VISA = 'passport_visa',
  PASSPORT = 'passport',
  SIGHTED_STUDENT_CARD = 'sighted_tertiary_edu_id',
  SIGHTED_KEYPASS_CARD = 'sighted_keypass_card',
  SIGHTED_PROOF_OF_AGE_CARD = 'sighted_proof_of_age_card'
}
// Where `someStringFromBackend` will be '' | 'passport_visa' | 'passport' ... etc.
const value = 'passport' as EvidenceTypeEnum;

// Sample use in code
if (value === EvidenceTypeEnum.PASSPORT) {
  console.log('You provided a passport');
  console.log(value); // `passport`
}
  • 有静态方法的枚举

  • 你可以使用 enum + namespace 的声明的方式向枚举类型添加静态方法。如下例所示,我们将静态成员 isBusinessDay 添加到枚举上:
enum Weekday {
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

namespace Weekday {
  export function isBusinessDay(day: Weekday) {
    switch (day) {
      case Weekday.Saturday:
      case Weekday.Sunday:
        return false;
      default:
        return true;
    }
  }
}

const mon = Weekday.Monday;
const sun = Weekday.Sunday;

console.log(Weekday.isBusinessDay(mon)); // true
console.log(Weekday.isBusinessDay(sun));

tsconfig.json配置文件

  • 这个文件是通过tsc --init命令生成的
  • 配置详解
"compilerOptions": {
  "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
  "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
  "diagnostics": true, // 打印诊断信息 
  "target": "ES5", // 目标语言的版本
  "module": "CommonJS", // 生成代码的模板标准
  "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
  "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
  "allowJS": true, // 允许编译器编译JS,JSX文件
  "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
  "outDir": "./dist", // 指定输出目录
  "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
  "declaration": true, // 生成声明文件,开启后会自动生成声明文件
  "declarationDir": "./file", // 指定生成声明文件存放目录
  "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
  "sourceMap": true, // 生成目标文件的sourceMap文件
  "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
  "declarationMap": true, // 为声明文件生成sourceMap
  "typeRoots": [], // 声明文件目录,默认时node_modules/@types
  "types": [], // 加载的声明文件包
  "removeComments":true, // 删除注释 
  "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
  "noEmitOnError": true, // 发送错误时不输出任何文件
  "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
  "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
  "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
  "strict": true, // 开启所有严格的类型检查
  "alwaysStrict": true, // 在代码中注入'use strict'
  "noImplicitAny": true, // 不允许隐式的any类型
  "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
  "strictFunctionTypes": true, // 不允许函数参数双向协变
  "strictPropertyInitialization": true, // 类的实例属性必须初始化
  "strictBindCallApply": true, // 严格的bind/call/apply检查
  "noImplicitThis": true, // 不允许this有隐式的any类型
  "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
  "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
  "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
  "noImplicitReturns": true, //每个分支都会有返回值
  "esModuleInterop": true, // 允许export=导出,由import from 导入
  "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
  "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
  "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
  "paths": { // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
    "jquery": ["node_modules/jquery/dist/jquery.min.js"]
  },
  "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
  "listEmittedFiles": true, // 打印输出文件
  "listFiles": true// 打印编译的文件(包括引用的声明文件)
}
 
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
   "src/**/*"
],
// 指定一个排除列表(include的反向操作)
 "exclude": [
   "demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
 "files": [
   "demo.ts"
]

常用

  • includes 指定编译文件默认是编译当前目录下所有的ts文件
  • exclude 指定排除的文件
  • target 指定编译js 的版本例如es5  es6
  • allowJS 是否允许编译js文件
  • removeComments 是否在编译过程中删除文件中的注释
  • rootDir 编译文件的目录
  • outDir 输出的目录
  • sourceMap 代码源文件
  • strict 严格模式
  • module 默认common.js  可选es6模式 amd  umd 等

namespace命名空间

  • 我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace避免这个问题出现
  • 内部模块,主要用于组织代码,避免命名冲突。
  • 命名空间内的类 默认私有
  • 通过 export 暴露
  • 通过 namespace 关键字定义
  • TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)
  • 命名空间中通过export将想要暴露的部分导出, 如果不用export 导出是无法读取其值
namespace a {
    export const Time: number = 1000
    export const fn = <T>(arg: T): T => {
        return arg
    }
    fn(Time)
}
 
 
namespace b {
     export const Time: number = 1000
     export const fn = <T>(arg: T): T => {
        return arg
    }
    fn(Time)
}
 
a.Time
b.Time

嵌套命名空间

namespace a {
    export namespace b {
        export class Vue {
            parameters: string
            constructor(parameters: string) {
                this.parameters = parameters
            }
        }
    }
}
let v = a.b.Vue // ts-node 直接编译会报错 使用 node 命令
 
new v('1')

抽离命名空间

// a.ts
export namespace V {
    export const a = 1
}
// b.ts
import {V} from './a'
console.log(V); // {a:1}

重名的命名空间会合并

image.png

三斜线指令

  • 三斜线指令是包含单个XML标签的单行注释。 注释的内容会作为编译器指令使用。

  • 三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。

  • /// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。

  • 三斜线引用告诉编译器在编译过程中要引入的额外的文件。

  • 你也可以把它理解为import,它可以告诉编译器在编译过程中要引入的额外的文件

声明文件 declare

  • 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
  • 第三方的声明文件包都收录到了 npm
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
/// <reference /> 三斜线指令

Mixins混入

1.对象混入

  • 此时 people 会被推断成一个交差类型 Name & Age & sex;
interface Name {
    name: string
}
interface Age {
    age: number
}
interface Sex {
    sex: number
}
 
let people1: Name = { name: "zs" }
let people2: Age = { age: 20 }
let people3: Sex = { sex: 1 }
 
const people = Object.assign(people1,people2,people3)

2.类的混入

首先声明两个mixins类 (严格模式要关闭不然编译不过)

class A {
    type: boolean = false;
    changeType() {
        this.type = !this.type
    }
}
 
class B {
    name: string = '张三';
    getName(): string {
        return this.name;
    }
}
  • 下面创建一个类,结合了这两个mixins
  • 首先应该注意到的是,没使用extends而是使用implements把类当成了接口
  • 我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性
class C implements A,B{
    type:boolean
    changeType:()=>void;
    name: string;
    getName:()=> string
}
  • 最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码
  • Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性,对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名
Mixins(C, [A, B])
function Mixins(curCls: any, itemCls: any[]) {
    itemCls.forEach(item => {
        Object.getOwnPropertyNames(item.prototype).forEach(name => {
            curCls.prototype[name] = item.prototype[name]
        })
    })
}
  • 使用

image.png

混合

  • 从可重用组件构建类的另一种方式是通过基类来构建它们,这种方式称为混合。
// 所有 mixins 都需要
type Constructor<T = {}> = new (...args: any[]) => T;

/////////////
// mixins 例子
////////////

// 添加属性的混合例子
function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = Date.now();
  };
}

// 添加属性和方法的混合例子
function Activatable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    isActivated = false;

    activate() {
      this.isActivated = true;
    }

    deactivate() {
      this.isActivated = false;
    }
  };
}

///////////
// 组合类
///////////

// 简单的类
class User {
  name = '';
}

// 添加 Timestamped 的 User
const TimestampedUser = Timestamped(User);

// Tina Timestamped 和 Activatable 的类
const TimestampedActivatableUser = Timestamped(Activatable(User));

//////////
// 使用组合类
//////////

const timestampedUserExample = new TimestampedUser();
console.log(timestampedUserExample.timestamp);

const timestampedActivatableUserExample = new TimestampedActivatableUser();
console.log(timestampedActivatableUserExample.timestamp);
console.log(timestampedActivatableUserExample.isActivated);

装饰器Decorator

  • 装饰器是一种特殊类型的声明,它能够被附加到类声明方法, 访问符属性参数上。
  • 增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能
  • 启用实验性的装饰器特性,你必须在命令行tsconfig.json里启用编译器选项
"experimentalDecorators": true
  • 首先定义一个类
class A {
    constructor() {
 
    }
}
  • 定义一个类装饰器函数 他会把ClassA的构造函数传入你的watcher函数当做第一个参数
const watcher: ClassDecorator = (target: Function) => {
    target.prototype.getParams = <T>(params: T):T => {
        return params
    }
}
  • 使用的时候 直接通过@函数名使用
@watcher
class A {
    constructor() {
 
    }
}
  • 验证
const a = new A();
console.log((a as any).getParams('123'));

装饰器工厂

  • 其实也就是一个高阶函数 外层的函数接受值 里层的函数最终接受类的构造函数
const watcher = (name: string): ClassDecorator => {
    return (target: Function) => {
        target.prototype.getParams = <T>(params: T): T => {
            return params
        }
        target.prototype.getOptions = (): string => {
            return name
        }
    }
}
 
@watcher('name')
class A {
    constructor() {
 
    }
}
 
const a = new A();
console.log((a as any).getParams('123'));

装饰器组合

  • 就是可以使用多个装饰器
const watcher = (name: string): ClassDecorator => {
    return (target: Function) => {
        target.prototype.getParams = <T>(params: T): T => {
            return params
        }
        target.prototype.getOptions = (): string => {
            return name
        }
    }
}
const watcher2 = (name: string): ClassDecorator => {
    return (target: Function) => {
        target.prototype.getNames = ():string => {
            return name
        }
    }
}
 
@watcher2('name2')
@watcher('name')
class A {
    constructor() {
 
    }
}

const a = new A();
console.log((a as any).getOptions());
console.log((a as any).getNames());

方法装饰器

  • 返回三个参数
      1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
      1. 成员的名字。
      1. 成员的属性描述符。
[
  {},
  'setParams',
  {
    value: [Function: setParasm],
    writable: true,
    enumerable: false,
    configurable: true
  }
]
const met:MethodDecorator = (...args) => {
    console.log(args);
}
 
class A {
    constructor() {
 
    }
    @met
    getName ():string {
        return 'zs'
    }
}
 
 
const a = new A();

属性装饰器

  • 返回两个参数
      1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
      1. 属性的名字。

[ {}, 'name', undefined ]

const met:PropertyDecorator = (...args) => {
    console.log(args);
}
 
class A {
    @met
    name:string
    constructor() {
 
    }
}
 
const a = new A();

参数装饰器

  • 返回三个参数
      1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
      1. 成员的名字。
      1. 参数在函数参数列表中的索引。

[ {}, 'setParams', 0 ]

const met:ParameterDecorator = (...args) => {
    console.log(args);
}
 
class A {
    constructor() {
 
    }
    setParasm (@met name:string = '213') {
 
    }
}
 
 
const a = new A();

Partial

/**
 * Make all properties in T optional
  将T中的所有属性设置为可选
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};
  • 使用
type Person = {
    name:string,
    age:number
}
 
type p = Partial<Person>
  • 转换后全部转为了可选
type p = {
    name?: string | undefined;
    age?: number | undefined;
}

keyof 是干什么的?

in 是干什么的?

? 是将该属性变为可选属性

T[P] 是干什么的?

  1. keyof将一个接口对象的全部属性取出来变成联合类型 'name' | 'age' | 'sex'

  2. in 我们可以理解成for in P 就是key 遍历 keyof T 就是联合类型的每一项

3 这个操作就是将每一个属性变成可选项

4 T[P] 索引访问操作符,与 JavaScript 种访问属性值的操作类似

Pick

  • 从类型定义T的属性中,选取指定一组属性,返回一个新的类型定义。
/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
type Person = {
    name:string,
    age:number,
    text:string
    address:string
}
 
type Ex = "text" | "age"
 
type A = Pick<Person,Ex>

image.png

Record

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
  • keyof any 返回 string | number | symbol 的联合类型
  • in 我们可以理解成for in P 就是key 遍历 keyof any 就是string number symbol类型的每一项
  • extends来约束我们的类型
  • T 直接返回类型
  • 做到了约束 对象的key 同时约束了 value
type Person = {
  name:string
  age:number
  text:string
}

type KEY = "A" | "B" | "C"

type BB = Record<KEY, Person> // 限制 Object 的 key 和 value

let obj: BB = {
  A: {name: 'zs', age: 10, text: '我是张三'},
  B: {name: 'ls', age: 11, text: '我是李四'},
  C: {name: 'ww', age: 20, text: '我是王五'},
}

image.png

image.png

Omit 过滤 与Pick相反


 export interface IProduct {
  id: number
  title: string
  price: number
  inventory: number // 库存
}

// Omit 过滤 过滤掉 inventory
// Pick 挑选 Pick<IProduct, 'inventory'> // 取inventory
// &合并
type CartProduct = {
  quantity: number
} & Omit<IProduct, 'inventory'>

结果为
type CartProduct = {
    quantity: number
    id: number
    title: string
    price: number
}

infer

  • infer 是TypeScript 新增到的关键字 充当占位符