类型“unknown”上不存在属性“foreach”_TypeScript 类型进阶

转自 Typescript 类型进阶 https://juejin.im/post/6876698583426007053#heading-43

这篇文章将通过简单实例介绍开发中常见的问题,希望能帮助你更好理解 Typescript。由于介绍 Typescript 基础知识的文章很多,官方文档本身也很全面,关于 Typescript 的基础本文就不再赘述。

为什么要使用 Typescript?

在正文开始前,想思考一个问题,为什么要使用 Typescript

在没有 Typescript 以前,大部分项目都是使用原生 Javascript 开发。而 Javascript 天生是一门“灵活”的语言。“灵活”表现在代码里可以肆无忌惮干任何事,比如拿数字和数组做求和运算,可以调用对象上不存在的方法,给函数传入不符合预期的参数等等,而这些显而易见的问题编码阶段不会有任何错误提示。

const number = 1;
const arr = [1, 2, 3];

console.log(number + arr);

const obj = {};
obj.functionNotExist();

function pow2(value) {
  return Math.pow(value, 2);
}
pow2("bazzzzzz");

在大型项目中,一个类型“小改动”可能会导致很多处代码需要跟着调整,光靠肉眼发现很难很难。我们使用 Typescript 的主要目的就是【类型安全】(type-safe),借助类型声明避免程序做错误的事情。

const number = 1;
const arr = [1, 2, 3];

console.log(number + arr); // 运算符“+”不能应用于类型“number”和“number[]”。

const obj = {};
obj.noExistFunction(); // 类型“{}”上不存在属性“noExistFunction”。

function pow(value: number) {
  return Math.pow(value, 2);
}
pow("bazzzzzz"); // 类型“string”的参数不能赋给类型“number”的参数。

微妙区别

Typescript 中一些关键字,概念存在一些微妙的区别,理解它们有助于编写更好的代码。

any vs unknown

any 表示任意类型,这个类型会逃离 Typescript 的类型检查,和在 Javascript 中一样,any 类型的变量可以执行任意操作,编译时不会报错。unknown 也可以表示任意类型,但它同时也告诉 Typescript 开发者对其也是一无所知,做任何操作时需要慎重。这个类型仅可以执行有限的操作(==、=== 、||、&&、?、!、typeof、instanceof 等等),其他操作需要向 Typescript 证明这个值是什么类型,否则会提示异常。

let foo: any
let bar: unknown

foo.functionNotExist()
bar.functionNotExist() // 对象的类型为 "unknown"。

if (!!bar) { // ==、=== 、||、&&、?、!、typeof、instanceof
  console.log(bar)
}

bar.toFixed(1) // Error

if (typeof bar=== 'number') {
  bar.toFixed(1) // OK
}

any 会增加了运行时出错的风险,不到万不得已不要使用。表示【不知道什么类型】的场景下使用 unknown

{} vs object vs Object

object 表示的是常规的 Javascript 对象类型,非基础数据类型。

    declare function create(o: object): void;create({ prop: 0 }); // OKcreate(null); // Errorcreate(undefined); // Errorcreate(42); // Errorcreate("string"); // Errorcreate(false); // Errorcreate({
    
      toString() {
        return 3;
      },
    }); // OK

{} 表示的非 null,非 undefined 的任意类型。

    declare function create(o: {}): void;create({ prop: 0 }); // OKcreate(null); // Errorcreate(undefined); // Errorcreate(42); // OKcreate("string"); // OKcreate(false); // OKcreate({
    
      toString() {
        return 3;
      },
    }); // OK

Object{} 几乎一致,区别是 Object 类型会对 Object 原型内置的方法(toString/hasOwnPreperty)进行校验。

    declare function create(o: Object): void;
    
    create({ prop: 0 }); // OK
    create(null); // Error
    create(undefined); // Error
    create(42); // OK
    create("string"); // OK
    create(false); // OK
    create({
      toString() {
        return 3;
      },
    }); // Error

如果需要一个对象类型,但对对象的属性没有要求,使用 object{}Object 表示的范围太泛尽量不要使用。

type vs interface

两者都可以用来定义类型。

interface(接口) 只能声明对象类型,支持声明合并(可扩展)。

    interface User {
    
      id: string
    }
     
    interface User {
      name: string
    }
     
    const user = {} as User
     
    console.log(user.id);
    console.log(user.name);

type(类型别名)不支持声明合并、行为有点像const, let 有块级作用域。

    type User = {
    
      id: string,
    }
    
    if (true) {
      type User = {
        name: string,
      }
    
      const user = {} as User;
      console.log(user.name);
      console.log(user.id) // 类型“User”上不存在属性“id”。
    }

type 更为通用,右侧可以是任意类型,包括表达式运算,以及后面会提到的映射类型等等。

    type A = number
    type B = A | string
    type ValueOf = T[keyof T];

如果你是在开发一个包,模块,允许别人进行扩展就用 interface,如果需要定义基础数据类型或者需要类型运算,使用 type

enum vs const enum

默认情况下 enum 会被编译成 Javascript 对象,并且可以通过 value 反向查找。

    enum ActiveType {
    
      active = 1,
      inactive = 2,
    }
    
    function isActive(type: ActiveType) {}
    isActive(ActiveType.active);
    
    // ============================== compile result:
    // var ActiveType;
    // (function (ActiveType) {
    //     ActiveType[ActiveType["active"] = 1] = "active";
    //     ActiveType[ActiveType["inactive"] = 2] = "inactive";
    // })(ActiveType || (ActiveType = {}));
    // function isActive(type) { }
    // isActive(ActiveType.active);
    
    ActiveType[1]; // OK
    ActiveType[10]; // OK!!!

cosnt enum 默认情况下不会生成 Javascript 对象而是把使用到的代码直接输出 value,不支持 value 反向查找。

    const enum ActiveType {
    
      active = 1,
      inactive = 2,
    }
    
    function isActive(type: ActiveType) {}
    isActive(ActiveType.active);
    
    // ============================== compile result:
    // function isActive(type) { }
    // isActive(1 /* active */);
    
    ActiveType[1]; // Error
    ActiveType[10]; // Error

enum 中括号索引取值的方式容易出错,相对 enumconst enum 是更安全的类型。

脚本模式和模块模式

Typescript 存在两种模式,脚本模式(Script)一个文件对应一个 htmlscript 标签,模块模式(Module)下一个文件对应一个 Typescript 的模块。区分的逻辑是,文件内容包不包含 import 或者 export 关键字。

了解这两种模式的区别有助于理解编写演示代码时的一些“怪异”现象。

脚本模式下,所有变量定义,类型声明都是全局的,多个文件定义同一个变量会报错,同名 interface 会进行合并。而模块模式下,所有变量定义,类型声明都是模块内有效的。

两种模式在编写类型声明时也有区别,例如脚本模式下直接 declare var GlobalStore 即可为全局对象编写声明。

    GlobalStore.foo = "foo";
    GlobalStore.bar = "bar"; // Error
    
    declare var GlobalStore: {
      foo: string;
    };

而模块模式下,要为全局对象编写声明需要 declare global

    GlobalStore.foo = "foo";
    GlobalStore.bar = "bar";
    
    declare global {
      var GlobalStore: {
        foo: string;
        bar: string;
      };
    }
    
    export {}; // export 关键字改变文件的模式

类型运算

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值