TypeScript学习(可调用的、断言、Freshness)

可调用的

  • 你可以使用类型别名或者接口来表示一个可被调用的类型注解:
    interface ReturnString {
          
          
      (): string;
    }
    
    它可以表示一个返回值为 string 的函数:
    declare const foo: ReturnString;
    
    const bar = foo(); // bar 被推断为一个字符串。
    

一个实际的例子

interface Complex {
    
    
  (foo: string, bar?: number, ...others: boolean[]): number;
}

一个接口可提供多种调用签名,用以特殊的函数重载:

interface Overloaded {
    
    
  (foo: string): string;
  (foo: number): number;
}

// 实现接口的一个例子:
function stringOrNumber(foo: number): number;
function stringOrNumber(foo: string): string;
function stringOrNumber(foo: any): any {
    
    
  if (typeof foo === 'number') {
    
    
    return foo * foo;
  } else if (typeof foo === 'string') {
    
    
    return `hello ${
      
      foo}`;
  }
}

const overloaded: Overloaded = stringOrNumber;

// 使用
const str = overloaded(''); // str 被推断为 'string'
const num = overloaded(123); // num 被推断为 'number'

这也可以用于内联注解中:

let overloaded: {
    
    
  (foo: string): string;
  (foo: number): number;
};

箭头函数(常用)

为了使指定可调用的类型签名更容易,TypeScript 也允许你使用简单的箭头函数类型注解。例如,在一个以 number 类型为参数,以 string 类型为返回值的函数中,你可以这么写:

// 这里定义一个函数 foo => { return foo.toString() } 按照 (foo: number) 返回值为string的格式进行传参
const simple: (foo: number) => string = foo => foo.toString();

TIP

它仅仅只能作为简单的箭头函数,你无法使用重载。如果想使用重载,你必须使用完整的 { (someArgs): someReturn } 的语法

可实例化

可实例化仅仅是可调用的一种特殊情况,它使用 new 作为前缀。它意味着你需要使用 new 关键字去调用它:

interface CallMeWithNewToGetString {
    
    
  new (): string;
}

// 使用
declare const Foo: CallMeWithNewToGetString;
const bar = new Foo(); // bar 被推断为 string 类型

类型断言

人为的告诉typescript编译器,该类型为什么类型
TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。

const foo = {
    
    };
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'

这里的代码发出了错误警告,因为 foo 的类型推断为 {},即没有属性的对象。因此,你不能在它的属性上添加 bar 或 bas,你可以通过类型断言来避免此问题:

interface Foo {
    
    
  bar: number;
  bas: string;
}

const foo = {
    
    } as Foo;
foo.bar = 123;
foo.bas = 'hello';

as foo 与 < foo >

最初的断言语法如下所示:

let foo: any;
let bar = <string>foo; // 现在 bar 的类型是 'string'

然而,当你在 JSX 中使用 的断言语法时,这会与 JSX 的语法存在歧义:

let foo = <string>bar;</string>;

因此,为了一致性,我们建议你使用上面的as foo的语法来为类型断言。

类型断言与类型转换

它之所以不被称为「类型转换」,是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。

类型断言被认为是有害的

简单的理解,你自己写的不靠谱,出了问题没报错找不到位置
在很多情景下,断言能让你更容易的从遗留项目中迁移(甚至将其他代码粘贴复制到你的项目中),然而,你应该小心谨慎的使用断言。让我们用最初的代码作为示例,如果你没有按约定添加属性,TypeScript 编译器并不会对此发出错误警告:

interface Foo {
    
    
  bar: number;
  bas: string;
}

const foo = {
    
    } as Foo;

// ahhh, 忘记了什么?
interface Foo {
    
    
  bar: number;
  bas: string;
}

const foo = <Foo>{
    
    
  // 编译器将会提供关于 Foo 属性的代码提示
  // 但是开发人员也很容易忘记添加所有的属性
  // 同样,如果 Foo 被重构,这段代码也可能被破坏(例如,一个新的属性被添加)。
};

这也会存在一个同样的问题,如果你忘记了某个属性,编译器同样也不会发出错误警告。使用一种更好的方式:

interface Foo {
    
    
  bar: number;
  bas: string;
}

const foo: Foo = {
    
    
  // 编译器将会提供 Foo 属性的代码提示
};

在某些情景下,你可能需要创建一个临时的变量,但至少,你不会使用一个承诺(可能是假的),而是依靠类型推断来检查你的代码。

双重断言

类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作:

function handler(event: Event) {
    
    
  const mouseEvent = event as MouseEvent;
}

然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:

function handler(event: Event) {
    
    
  const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}

如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:

function handler(event: Event) {
    
    
  const element = (event as any) as HTMLElement; // ok
}

TypeScript 是怎么确定单个断言是否足够

当 S 类型是 T 类型的子集,或者 T 类型是 S 类型的子集时,S 能被成功断言成 T。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以使用 any。

Freshness

为了能让检查对象字面量类型更容易,TypeScript 提供 「Freshness」 的概念(它也被称为更严格的对象字面量检查)用来确保对象字面量在结构上类型兼容。

用例:React State:
Facebook ReactJS 为对象的 Freshness 提供了一个很好的用例,通常在组件中,你只使用少量属性,而不是传入所有,来调用 setState:

// 假设
interface State {
    
    
  foo: string;
  bar: string;
}

// 你可能想做:
this.setState({
    
     foo: 'Hello' }); // Error: 没有属性 'bar'

// 因为 state 包含 'foo' 与 'bar',TypeScript 会强制你这么做:
this.setState({
    
     foo: 'Hello', bar: this.state.bar });

如果你想使用 Freshness,你可能需要将所有成员标记为可选,这仍然会捕捉到拼写错误:

// 假设
interface State {
    
    
  foo?: string;
  bar?: string;
}

// 你可能想做
this.setState({
    
     foo: 'Hello' }); // Yay works fine!

// 由于 Freshness,你也可以防止错别字
this.setState({
    
     foos: 'Hello' }}; // Error: 对象只能指定已知属性

// 仍然会有类型检查
this.setState({
    
     foo: 123 }}; // Error: 无法将 number 类型赋值给 string 类型

猜你喜欢

转载自blog.csdn.net/shadowfall/article/details/121036129