ts 高级工具用法

ts相关内置工具介绍 #

可选操作链(optional-chaining) #

基础介绍 #

可选操作链,目前处于stage 4

许多API返回一个对象或null / undefined,并且可能仅在结果不为null时才希望从结果中提取属性,Optional Chaining Operator允许开发人员处理许多情况,而无需重复自己和/或在临时变量中分配中间结果,TS中可以直接使用该运算符

const { log } = console;
let test = Math.random() > 0.5 ? ['ssss'] : null;

log(test?.length);

// 1
// undefined
// undefined
// 1

相关案例 #

基础案例

?.运算符左侧(LHS)的值为undefinednull,则表达式的值为undefined。否则,将正常执行后续属性访问,方法函数调用。

a?.b                          // undefined if `a` is null/undefined, `a.b` otherwise.
// a == null ? undefined : a.b

a?.[x]                        // undefined if `a` is null/undefined, `a[x]` otherwise.
// a == null ? undefined : a[x]

a?.b()                        // undefined if `a` is null/undefined
// a == null ? undefined : a.b()
// throws a TypeError if `a.b` is not a function
// otherwise, evaluates to `a.b()`

a?.()                        // undefined if `a` is null/undefined
// a == null ? undefined : a()  
// throws a TypeError if `a` is neither null/undefined, nor a function
// invokes the function `a` otherwise
a is null/undefineda is not null/undefined
a?.bundefineda.b
a?.[x]undefineda[x]
a?.b()undefineda.b()
a?.()undefineda()

短路

如果,运算符左侧(LHS),?.评估为null/undefined,则不评估运算符右侧(RHS),这个过程称为短路(Short-circuiting)

a?.[++x]         // `x` is incremented if and only if `a` is not null/undefined
// a == null ? undefined : a[++x]

长时间短路

实际上,短路在被触发时不仅会跳过当前的属性访问,方法函数调用,而且还会直接跳过可选链接运算符之后的属性访问,方法函数调用的整个执行链

a?.b.c(++x).d  // if `a` is null/undefined, evaluates to undefined. Variable `x` is not incremented.
               // otherwise, evaluates to a.b.c(++x).d.
// a == null ? undefined : a.b.c(++x).d

因为先对anull进行匹配检查。

  • 如果,anull,则直接短路返回undefined
  • 如果,a不为null,但a.b为空,则在试图访问a.bc属性时,就会抛出一个TypeError错误。

该功能由例如C#CoffeeScript来实现;参见现有技术


可选链堆

Optional ChainOptional Chaining运算符,后跟一系列属性访问,方法函数调用。即可选链之后可以跟随另一个可选链

a?.b[3].c?.(x).d
// a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d
// (as always, except that `a` and `a.b[3].c` are evaluated only once)

边缘限制

使用括号运算符可以限制了短路的判断范围。

(a?.b).c
// (a == null ? undefined : a.b).c

因为先对anull进行匹配检查。

  • 如果,anull,则直接短路返回undefined
  • 如果,a不为null,但a.b为空,则直接短路返回undefined,后续即访问undefinedc,就会抛出一个TypeError错误。

可选链 && 删除

可选链可以和delete运算符,用于限制delete运算符直接操作的内容范围。

delete a?.b
// a == null ? true : delete a.b


不支持 #

尽管可以出于完整性考虑将它们包括在内,但由于缺乏实际用例或其他令人信服的原因,因此不支持以下内容

有关讨论,请参见第22期第54期

  • 可选结构: new a?.()
  • 可选模板文字: a?.\string``
  • 构造函数或模板文字中/任选的链后:new a?.b()a?.b\string``

尽管以下内容有一些用例,但不支持以下内容:参见第18期的讨论:

  • 可选的属性分配: a?.b = c

至少在实践中不支持以下内容,因为它没有多大意义,参见问题#4(评论)

  • 可选父类继承:super?.()super?.foo
  • 任何类似于属性访问或函数调用的内容,new?.targetimport?.('foo')等不包含在内。

语法或静态语义将禁止上述所有情况,以便稍后添加支持。


常见问题 #

obj?.[expr]和func?.(arg)看起来很丑陋。为什么不使用obj?[expr]和func?(arg)一样<语言X>?

我们不使用obj?[expr]func?(arg)语法,因为解析器很难有效地将这些形式,与条件运算符(例如obj?[expr].filter(fun)func?(x - 2) + 3区分开。

这两种情况的替代语法各有其缺陷。而决定哪一个看上去最糟则主要是个人品味的问题。这是我们做出选择的方式:

  • 选择最适合这种obj?.prop情况的语法,这种语法最常出现
  • 将可识别?.的字符序列的使用扩展到其他情况:obj?.[expr]func?.(arg)

至于<language X>,它具有与JavaScript不同的语法约束,这是因为<X不支持某些构造或X中的工作方式不同>

为什么(null)?.b会默认返回undefined,而不是null

a?.ba变量下是否有b属性,如果没有找到对应的b属性,则返回undefined,即因为a.b = undefined,所以,a?.b = undefined

特别是,该值null被认为没有属性。因此,(null)?.b是返回undefined

为什么foo?.(),会在在foo为undefined 或者 null时,抛出错误?

想象一下一个库,它将onChange在用户提供处理程序时调用它,例如。如果用户提供的是数字3而不是函数,则该库很可能会抛出错误的用法并通知用户。这正是所建议的语义要onChange?.()实现的。

此外,这确保了?.在所有情况下都具有一致的含义,无需在我们进行检查的特殊情况下,需要先执行typeof foo === 'function',再执行后续值有效性校验,只需全面检查foo == null即可。

最后,请记住,可选链接不是错误抑制机制

可选操作只会检测,对应的值是undefined 或者 null,不会捕获或抑制通过评估周围的代码引发的错误。

(function () {
    "use strict"
    undeclared_var?.b    // ReferenceError: undeclared_var is not defined
    arguments?.callee    // TypeError: 'callee' may not be accessed in strict mode
    arguments.callee?.() // TypeError: 'callee' may not be accessed in strict mode
    true?.()             // TypeError: true is not a function
})()

更多疑问请查看optional-chaining QA


空合并运算符(nullish-coalescing) #

前因说明 #

执行属性访问时,通常希望提供默认值,如果该属性访问的结果为nullundefined

当前,在JavaScript中表达此意图的典型方法是使用||运算符。

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

const undefinedValue = response.settings.undefinedValue || 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue || 'some other default'; // result: 'some other default'

这在nullundefined值的常见情况下效果很好,但是有一些虚假的值可能会产生令人惊讶的结果:

const headerText = response.settings.headerText || 'Hello, world!'; // Potentially unintended. '' is falsy, result: 'Hello, world!'
const animationDuration = response.settings.animationDuration || 300; // Potentially unintended. 0 is falsy, result: 300
const showSplashScreen = response.settings.showSplashScreen || true; // Potentially unintended. false is falsy, result: true

无效合并运算符旨在更好地处理这些情况,并用作对无效值(nullundefined)的相等性检查。

语法 #

主要使用??运算符,如果左侧的表达式值为undefinednull的时候,则返回其右侧表达式值。

const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: '',
    showSplashScreen: false
  }
};

const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world!'; // ''
const animationDuration = response.settings.animationDuration ?? 300; // 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // false

装饰器(decorator) #

基础介绍 #

设计模式中有种模式是装饰者模式decorator的实现和这个设计模式很相近,主要做一些非侵入式的能力扩展。

ES6 之前,装饰器可能并没有那么重要,因为你只需要加一层 wrapper 就好了,但是现在,由于语法糖 class 的出现,它们不支持所需的一些常见行为,这个时候想要去在多个之间共享或者扩展一些方法的时候,代码会变得错综复杂,难以维护,而这,也正式我们 decorator 的用武之地。

注意目前decorators,还处于第二阶段中,之后语法可能会变化,注意及时关注decorators-github查看最新进度。

如果需要开启 decorator这项experimental支持,需要手动开启experimentalDecorators选项。

tsc --target ES5 --experimentalDecorators
tsconfig.json:

或者修改tsconfig.json配置。

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}

原理 #

decorator其实是一个语法糖,背后通过拦截es5Object.defineProperty(target,name,descriptor)进行装饰功能,详细了解Object.defineProperty可以查看MDN文档

class Melon(){
  @readonly
  name
}

在属性上的修饰符,会在Object.definePropertyMelon原型上注册对应属性之前,执行以下代码。

let descriptor = {
  value:specifiedFunction,
  enumerable:false,
  configurable:true,
  writeable:true
};

descriptor = readonly(Melon.prototype,'name',descriptor) || descriptor;
Object.defineProperty(Melon.prototype,'name',descriptor);

从上面的伪代码我们可以看出,decorator只是在Object.definePropertyMelon.prototype注册属性之前,执行了一个装饰函数,属于一个类对Object.defineProperty的拦截。

也就是说在利用decorator进行能力扩展时,主要是根据装饰目标不同,取到对应的target,name,和descriptor实参,做相关修改和扩展。

target

要在其上定义属性的对象。

name

要定义或修改的属性的名称。

descriptor中主要有configurable,enumerable,value,writable,get,和set属性可配置

属性名属性描述
configurable通用配置项,当且仅当该属性的 configurabletrue 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false
enumerable通用配置项,当且仅当该属性的enumerabletrue时,该属性才能够出现在对象的枚举属性中。默认为 false
value数据描述专有配置项,该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
writable数据描述专有配置项,当且仅当该属性的writabletrue时,value才能被赋值运算符改变。默认为 false
get数据存取专有配置项,一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认为 undefined
set数据存取专有配置项,一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined

descriptor中主要分两种形式来做数据描述符存取描述符

  • 通过Object.defineProperty方法做具体属性值定义,称为数据描述符行为,比如装饰器中对于类属性的复写,就是属于这种情况。
  • 通过Object.defineProperty方法做属性的访问器相关定义,称为存取描述符行为,比如装饰器中对于类整体类方法的复写,就是属于这种情况。vue 2.x之前的版本数据双向观测联动也是通过这个来实现的。
configurableenumerablevaluewritablegetset
数据描述符YesYesYesYesNoNo
存取描述符YesYesNoNoYesYes

语法说明 #

decorator是一种特殊种类的声明,可被附连到一个类声明方法访问器属性,或参数

装饰器使用形式是@expression,在其中expression必须是一个函数,该函数将在运行时使用有关修饰声明的信息来调用。

在一行上使用多个装饰器。

@f @g x

在多行上使用多个装饰器。

@f
@g
x

当多个修饰符应用于一个声明时,它们的求值类似于数学中的函数组成。

上述代码中,是将fg两个方法进行能力组合, 组合结果是 f(g(x))

因此,在TypeScript中的单个声明上评估多个装饰器时,将执行以下步骤:

  • 从顶部到底部评估每个装饰器的表达式。
  • 然后将结果从下到上称为函数。
const { log } = console;

function f() {
    log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        log(target, propertyKey, "f(): called");
    };
}

function g() {
    log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        log(target, propertyKey, "g(): called");
    };
}

class C {
    @f()
    @g()
    method() { }
}

const testObj: C = new C();
testObj.method();

// f(): evaluated
// g(): evaluated
// {},  'method',  g(): called
// {},  'method',  f(): called

代码示例 #

decorator是一种特殊种类的声明,可被附连到一个类声明类方法类访问器类属性,或类方法参数

装饰器使用形式是@expression,在其中expression必须是一个函数,该函数将在运行时使用有关修饰声明的信息来调用。

class declaration && decorator

在类声明之前声明一个类装饰器

类装饰器应用于类的构造函数,可用于观察修改替换类定义。

不能在声明文件,或任何其他环境上下文(例如,在declare类中)中使用类装饰器

类装饰器的表达式将在运行时作为函数调用,装饰类的构造函数为其唯一参数。

下面是一个有关如何重写构造函数的示例。

function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor {
    newProperty = "new property";
    hello = "override";
  };
}

@classDecorator
class Greeter {
  property = "property";
  hello: string;
  constructor(m: string) {
    this.hello = m;
  }
}

console.log(new Greeter("world"));

如果,类装饰器返回一个值,它将用提供的构造函数替换类声明。

注意,如果是选择返回新的构造函数,则必须注意维护原始原型,运行时应用装饰器的逻辑不会为执行此操作。


property declaration && decorator

属性装饰器是在属性声明之前声明。

属性装饰器不能在声明文件重载或任何其他环境上下文(例如在declare类中)中使用。

属性装饰器的表达式将在运行时作为函数调用,并带有以下两个参数:

  • 静态成员的类的构造函数或实例成员的类的原型。
  • 成员的名称。

注意,由于在TypeScript中未初始化属性装饰器,因此未将属性描述符作为参数提供给属性装饰器

这是因为当前在定义原型成员时没有描述实例属性的机制,也没有观察修改该属性的初始化程序的方法。

返回值也将被忽略。因此,属性装饰器只能用于观察已为类声明了特定名称属性

可以使用此信息来记录有关该属性的元数据@format装饰器和getFormat函数应用于Greeter类上方法的属性装饰器的示例。

import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
  @format("Hello, %s")
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

@format("Hello, %s")被调用时,通过Reflect.metadata函数,增加了使用属性元数据条目。

getFormat被调用时,它读取的格式中的元数据值

注意此示例需要reflect-metadata库。有关库的更多信息,请参见元数据reflect-metadata


method declaration && decorator

方法声明之前声明方法装饰器装饰器将应用于方法的属性描述符,并可用于观察,修改或替换方法定义。

方法装饰器不能在声明文件重载或任何其他环境上下文(例如在declare类中)中使用。

方法装饰器的表达式将在运行时作为函数调用,并带有以下三个参数

  • 静态成员的类的构造函数或实例成员的类的原型。
  • 成员的名称。
  • 成员的属性描述符。

注:如果你的js编译版本低于是小于ES5,属性描述将会是undefined

如果方法装饰器返回一个值,它将用作方法属性描述符

以下是@enumerable应用于Greeter类上方法的方法装饰器的示例

const { log } = console;

function enumerable(value: boolean) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}

const testObj: Greeter = new Greeter('melon');
log(testObj.greet());// Hello, melon
log(testObj.greeting);// melon

我们可以@enumerable使用以下函数声明来定义方法装饰器,当@enumerable(false)装饰器被调用时,它修改enumerable的属性描述符的属性。


accessor declaration && decorator

访问器声明之前,就声明了一个访问器装饰器访问器修饰符应用于访问器的属性描述符,可用于观察修改替换访问器的定义。

访问装饰器不能在声明文件重载或任何其他环境上下文(例如在declare类中)中使用。

注意,TypeScript不允许装饰单个成员的getset访问器。而是,该成员的所有装饰器必须应用于按文档顺序指定的第一个访问器。

这是因为访问器装饰器适用于Property Descriptor,它组合了getset访问器,而不是分别合并每个声明。

访问器装饰器的表达式将在运行时作为函数调用,并带有以下三个参数

  • 静态成员的类的构造函数或实例成员的类的原型。
  • 成员的名称。
  • 成员的属性描述符。

注:如果你的js编译版本低于是小于ES5,属性描述将会是undefined

如果访问器装饰器返回一个值,它将用作成员属性描述符

以下是@configurable应用于Point类成员的访问器修饰符的示例

const { log } = console;

function configurable(value: boolean) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.configurable = value;
  };
}

class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  @configurable(false)
  get x() {
    return this._x;
  }

  @configurable(false)
  get y() {
    return this._y;
  }
}

const testObj: Point = new Point(3,4);
log(testObj.x);// 3
log(testObj.y);// 4

testObj.x = 4;// Cannot assign to 'x' because it is a read-only property.
testObj.y = 5;// Cannot assign to 'y' because it is a read-only property.


parameter declaration && decorator

参数声明之前声明参数装饰器参数装饰器应用于类构造函数,或方法声明的函数。

参数装饰器不能在声明文件重载或任何其他环境上下文(例如在declare类中)中使用。

参数装饰器的表达式将在运行时作为函数调用,并带有以下三个参数

  • 静态成员的类的构造函数或实例成员的类的原型。
  • 成员的名称。
  • 函数的参数列表中参数的序号索引。

注意,参数装饰器只能用于观察已在方法上声明了的参数参数装饰器的返回值将被忽略。

以下是@required应用于Greeter类上方法的参数装饰器的示例

import "reflect-metadata";

const requiredMetadataKey = Symbol("required");

function required(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  let existingRequiredParameters: number[] =
    Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata(
    requiredMetadataKey,
    existingRequiredParameters,
    target,
    propertyKey
  );
}

function validate(
  target: any,
  propertyName: string,
  descriptor: TypedPropertyDescriptor<Function>
) {
  let method = descriptor.value;
  descriptor.value = function() {
    let requiredParameters: number[] = Reflect.getOwnMetadata(
      requiredMetadataKey,
      target,
      propertyName
    );
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if (
          parameterIndex >= arguments.length ||
          arguments[parameterIndex] === undefined
        ) {
          throw new Error("Missing required argument.");
        }
      }
    }

    return method.apply(this, arguments);
  };
}

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  @validate
  greet(@required name: string) {
    return "Hello " + name + ", " + this.greeting;
  }
}

@required装饰器用于标记该参数的元数据条目。然后,@validate装饰器是将现有greet方法包装在一个函数中,该函数在调用原始方法之前先验证参数

注意此示例需要reflect-metadata库。有关库的更多信息,请参见元数据reflect-metadata


注意事项 #

因为存在函数提升,这边目前装饰器更多是应用于修改一个类声明类方法类访问器类属性,或类方法参数中,不能直接装饰具体方法

let counter = 0;

const add = function () {
  counter++;
};

@add
function foo() {
}

上面的代码,意图是执行后counter等于 1,但是实际上结果是counter等于 0。因为函数提升,使得实际执行的代码是下面这样。

@add
function foo() {
}

let counter;
let add;

counter = 0;

add = function () {
  counter++;
};

下面是另一个例子。

const readOnly = require("some-decorator");

@readOnly
function foo() {
}

上面代码也有问题,因为实际执行是下面这样。

let readOnly;

@readOnly
function foo() {
}

readOnly = require("some-decorator");

总之,由于存在函数提升,使得装饰器不能用于函数。类是不会存在函数提升的,所以就没有这方面的问题。

另一方面,如果一定要装饰函数,可以采用高阶函数的形式直接执行。

function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

mixins #


DOM Manipulation #

基础介绍 #

自标准化以来的20多年来,JavaScript已经走了很长一段路。尽管在2020年,JavaScript可以在服务器数据科学甚至IoT设备上使用,但更多的场景还是在Web浏览器。

网站由HTMLXML文档组成。这些文档是静态的,它们不会更改。document对象模型(DOM)是浏览器,为了方便操作静态页面提供的编程接口。

很多相关UI库这边都是DOM API可用于更改文档结构,样式和内容,实现相关操作。

TypeScriptJavaScript的类型化超集,它附带DOM API的类型定义。这些定义可以在任何默认的TypeScript项目中轻松获得,在TypeScript可以通过HTMLElement类型进行DOM相关操作。

在此处探索DOM类型定义的源代码:HTMLElement 实现


代码案例 #

下面示例,如何在TypeScript中,将<p>Hello, World</p>元素添加到#app元素中。

// 1. Select the div element using the id property
const app = document.getElementById("app");

// 2. Create a new <p></p> element programmatically
const p = document.createElement("p");

// 3. Add the text content
p.textContent = "Hello, World!";

// 4. Append the p element to the div element
app?.appendChild(p);

编译并运行index.html页面后,生成的HTML将为

<div id="app">
  <p>Hello, World!</p>
</div>

接口介绍 #

TypeScript代码的第一行使用全局变量document,检查该变量是否显示它是由lib.dom.d.ts文件中的Document接口定义的。

Document.getElementById

向其传递元素ID字符串,如果能够在页面上找到对应元素,将返回HTMLElement,如果找不到,则会返回null

getElementById(elementId: string): HTMLElement | null;

它充当所有其他元素接口的基础接口,例如,p代码示例中变量的类型HTMLParagraphElement

因为,该方法返回值无法在运行前确定,所以,可以结合可选链运算符来调用HTMLElement接口。


Document.createElement

向其传递元素任何内容string,将返回标准HTMLElement。开发人员可以通过这个接口是创建唯一的HTML元素标签。

例如

  • document.createElement('a'),那么它将是type的元素HTMLAnchorElement
  • document.createElement('xyz')返回一个<xyz></xyz>元素,显然不是HTML规范指定的元素。
createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;

上面是一个重载的函数定义。第二个重载是最简单的,并且与该getElementById方法非常相似。

对于createElement的第一个定义,它使用了一些高级通用模式,最好将其分解为多块。

  • 从通用表达式开始:<K extends keyof HTMLElementTagNameMap>。该表达式定义了一个通用参数K,该参数被限制在接口的键上HTMLElementTagNameMap

HTMLElementTagNameMap映射接口包含每个指定的HTML标记名称及其对应的类型接口,例如,这是前5个映射值:

interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;
    "address": HTMLElement;
    "applet": HTMLAppletElement;
    "area": HTMLAreaElement;
        ...
}

有些元素不具有唯一的属性,因此它们只是返回HTMLElement,而其他类型的确具有唯一的属性和方法,因此它们返回其特定的接口(将从扩展或实现HTMLElement)。

  • 现在,在createElement定义的其余部分:(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]

第一个参数tagName定义为通用参数KTypeScript解释器足够聪明,可以从此参数推断出通用参数。这意味着开发人员在使用该方法时实际上不必指定泛型参数。传递给tagName参数的任何值都将被推断为K,因此可以在定义的其余部分中使用。

返回值HTMLElementTagNameMap[K]接受tagName参数,并使用它返回相应的类型


Node.appendChild

具体HTMLElement对象执行appendChild方法,向其传递元素任何内容string,将返回标准HTMLElement

appendChild<T extends Node>(newChild: T): T;

此方法的工作方式与从createElement参数T推断出通用参数的方法类似newChildT被限制在另一个基本接口上Node


querySelector && querySelectorAll

这两种方法都是获取适合更多唯一约束的dom元素列表的出色工具。它们在lib.dom.d.ts中定义为

/**
 * Returns the first element that is a descendant of node that matches selectors.
 */
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;

/**
 * Returns all element descendants of node that match selectors.
 */
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;

querySelectorAll定义类似于getElementByTagName,但它返回一个新类型:NodeListOf

此返回类型本质上是标准JavaScript list元素的自定义实现。可以说,替换NodeListOf<E>E[]会带来非常相似的用户体验。

NodeListOf只有实现了以下属性和方法:lengthitem(index)forEach((value, key, parent) => void),和数字索引

注意,此方法返回元素列表,而不是节点列表,这是NodeList是取的Node.childNodes属性。

要查看实际使用的这些方法,请将现有代码修改为:

<ul>
  <li>First :)</li>
  <li>Second!</li>
  <li>Third times a charm.</li>
</ul>;

const first = document.querySelector("li"); // returns the first li element
const all = document.querySelectorAll("li"); // returns the list of all li elements

类型断言 #

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。

通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型

通过类型断言这种方式可以告诉编译器,"相信我,我知道自己在干什么"。

类型断言好比其它语言里的类型转换,或者类型装箱,但是不进行特殊的数据检查解构

它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你已经进行了必须的检查。

类型断言有两种形式。

其一是"尖括号"语法:

const someValue: any = "this is a string";

const strLength: number = (<string>someValue).length;

// 当然直接写也是可以的。
const strLength2: number = someValue.length;// 16

另一个为as语法:

const someValue: any = "this is a string";

const strLength: number = (someValue as string).length;

两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有as语法断言是被允许的。


类型校验 #


泛型 #


高级类型 #


Utility Types #


Build by Loppo 0.6.16