关于javascript:如何更好的使用TypeScript

5次阅读

共计 6505 个字符,预计需要花费 17 分钟才能阅读完成。

写 js 很多年,刚开始写 ts 的时候,着实有些不习惯,整体感觉就像一个落拓不羁的少年被圈在了关闭的学校一样,起初用了一段时间发现,ts 确实是学校外面一个严苛的教诲主任,当你一旦不恪守校规的时候,就会让你叫家长(报错)。缓缓的你也就会成为一个灵巧懂事的好学生了,上面大家看看怎么样能力一步一步学好

一、养成“先思考后入手”的好习惯

在以往的开发过程中,习惯总是“先想好一个大略,而后边做边想再边改”。这样的劣势是执行快,顺利的时候效率会很高,但更多的时候会一直地颠覆本人先前的想法,置信不少的人也有跟我相似的领会。

tsjs 的超集。意思就是在 ts 中能够间接书写 js。在我的第一感觉里,js 就像是编译后的可执行文件,家喻户晓 ts 是强类型的语言,就像是 Java 语言。这也意味着它能无效制约开发者在开发过程中“得心应手”的水平。上面具体介绍下👇

1. 定义类型形式和扩大

  • TypeScript 中定义类型有两种形式:接口(interface)和类型别名(type alias)。在上面的例子中,除了语法不一样,定义的类型是一样的:

    //interface
    interface PointI {
    x: number;
    y: number;
    }
    interface SetPointI {(x:number, y:number): void;
    }
    // or
    //type alias
    type PointT = {
    x: number;
    y: number;
    }
    type SetPointT = (x:number, y:number) => void;
  • 接口和类型别名不仅均能够扩大,而且接口和类型别名并不互斥的,也就是说,接口能够扩大类型别名,类型别名也能够扩大接口

    // interface extends type alias
    type PointX = {x: number;} 
    interface PointY extends PointX {y: number;}
    // type alias extends interface
    interface Point2X{x: number;}
    type Point2Y = Point2X & {y: number;}
  • 接口和类型别名也有差异,比方一个接口能够定义屡次,并将被视为一个接口,然而类型别名却不能够反复

    接口和类型别名的选用机会:

    • 在定义公共 API(如编辑一个库)时应用 interface,这样能够不便使用者继承接口;
    • 在定义组件属性(Props)和状态(State)时,倡议应用 type,因为 type 的约束性更强;
    • type 类型不能二次编辑,而 interface 能够随时扩大。

2. TS 反对的 JS 新个性

2.1 可选链(Optional Chaining)

  • TypeScript 3.7 实现了呼声最高的 ECMAScript 性能之一:可选链(Optional Chaining)。有了可选链后,咱们编写代码时如果遇到 nullundefined 就能够立刻进行某些表达式的运行。可选链的外围是新的 ?. 运算符.

    obj?.prop 
    obj?.[expr] 
    arr?.[index] 
    func?.(args)
  • 可选链(Optional Chaining)  ?. 是 ES11(ES2020)新增的个性,可选链 能够让咱们在查问具备多层级的对象时,不再须要进行冗余的各种前置校验:

    let age = user && user.info && user.info.getAge()
    let age = user?.info?.getAge?.()
  • 但须要留神的是,?.&& 运算符行为略有不同,&& 专门用于检测 false 值,比方空字符串、0、NaN、null 和 false 等。而 ?. 只会验证对象是否为 nullundefined,对于 0 或空字符串来说,并不会呈现“短路”。

2.2 可选属性

  • 在面向对象语言中,接口是一个很重要的概念,它是对行为的形象,而具体如何口头须要由类去实现。TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行形象以外,也罕用于对「对象的形态(Shape)」进行形容
    在 TypeScript 中应用 interface 关键字就能够申明一个接口:

    interface Person { 
    name: string; 
    age: number; 
    } 
    let zhangsan: Person = { 
    name: "zhangsan", 
    age: 33, 
    };
  • 上述代码中,咱们申明了 Person 接口,它蕴含了两个必填的属性 name 和 age。在初始化 Person 类型变量时,如果短少某个属性,TypeScript 编译器就会提醒相应的错误信息,比方:
  • 为了解决上述的问题,咱们能够把某个属性申明为可选的:

    interface Person { 
    name: string; 
    age?: number; 
    }  
    let lisi: Person = {name: "lisi"}

2.3 空值合并运算符(Nullish coalescing Operator)

  • 当空值合并运算符的左表达式不为 null 或 undefined 时,不会对右表达式进行求值。

    const goods = {price: 0,}
    let goods1 = goods.price ?? '暂无报价'
    let goods2 = goods.jdPrice ?? '暂无报价'
    console.log(goods1)//0
    console.log(goods2)// 暂无报价
  • 与逻辑或操作符(||)不同,|| 会在左侧操作数为 false 值(例如,'' 或 0)时返回右侧操作数。也就是说,如果应用 || 来为某些变量设置默认值,可能会遇到意料之外的行为:

     const goods = {price: 0,}
     let goods1 = goods.price || '暂无报价'
     let goods2 = goods.price ?? '暂无报价'
     console.log(goods1)// 暂无报价
     console.log(goods2)//0

3. 类型收窄

  • TypeScript 类型收窄就是从 宽类型 转换成 窄类型 的过程,其罕用于解决联结类型变量的场景。
  • 在 TypeScript 中,有许多办法能够收窄变量的类型:

    • 类型断言
    • 类型守卫
    • 双重断言

3.1 类型断言

  • 类型断言有两种:值 as 类型 or  < 类型 > 值 个别咱们对立应用 值 as 类型 这样的语法,因为 <> 容易跟泛型语法起抵触。
  • 当 TypeScript 不确定一个联结类型的变量到底是哪个类型的时候,咱们 只能拜访此联结类型的所有类型中共有的属性或办法

    interface rabbit {
      name: string;
      jump(): void;}
    interface dog {
      name: string;
      run(): void;}
    function isRabbit1(animal: rabbit | dog){return animal.name}
  • 而有时候,咱们的确须要在还不确定类型的时候就拜访其中一个类型特有的属性或办法,如:
  • 上图的例子中,获取 animal.jump 的时候会报错。此时能够应用类型断言,将 animal 断言成 rabbit 类型,就能够解决拜访 animal.jump 时报错的问题:

    interface rabbit {
      name: string;
      jump(): void;}
    interface dog {
      name: string;
      run(): void;}
    function isRabbit(animal: rabbit | dog){if(typeof (animal as rabbit).jump === 'function'){return true}
      return false
    }
  • 类型断言虽好,然而不能滥用,滥用之后可能会绕过编译器,然而无奈防止 运行时 的谬误。

    interface rabbit {
      name: string;
      jump(): void;}
    interface dog {
      name: string;
      run(): void;}
    function rabbitJump(animal: rabbit | dog)
    {(animal as rabbit).jump()}
    const jack: dog = {
      name: 'Jack',
      run() {console.log("跑")
      }
    }
    rabbitJump(jack)// 运行会报错
  • TypeScript 编译器信赖了咱们的断言,故在调用 rabbitJump() 时没有编译谬误,但因为 jack 上并没有 jump 办法,就会导致在运行时产生谬误。
  • 应用类型断言时肯定要分外小心,尽量避免断言后调用办法或援用深层属性,以缩小不必要的运行时谬误。

3.2 类型守卫

  • 类型守卫次要有以下几种形式:

    • typeof:用于判断 numberstringboolean或 symbol 四种类型;
    • instanceof:用于判断一个实例是否属于某个类
    • in:用于判断一个属性 / 办法是否属于某个对象
  • 能够利用 typeof 实现类型收窄和 never 类型的个性做全面性查看,如上面的代码所示:
type Foo = string | number 
function test(input: Foo) {if (typeof input == 'string') {// 这里 input 的类型「收紧」为 string} else if (typeof input == 'number') {// 这里 input 的类型「收紧」为 number} else {
    // 这里 input 的类型「收紧」为 never
    const isShow: never = input
  }
}
  • 能够看到,在最初的 else 分支外面,咱们把收窄为 never 的 input 赋值给一个显示申明的 never 变量,如果所有逻辑正确,那么这里应该可能编译通过。然而如果起初有一天你的共事批改了 Foo 的类型,并且遗记了批改 test 办法外面控制流的时候:
  • 这时候 else 分支的 input 类型会被收窄为 boolean 类型,导致无奈赋值给 never 类型,这时就会产生一个编译谬误。
  • 应用 instanceof 运算符收窄变量的类型
  • 应用 in 做属性查看

    interface Foo {foo: string;}
    interface Bar {bar: string;}
    function test(input: Foo | Bar) {if ('foo' in input) {// 这里 input 的类型「收紧」为 Foo} else {// 这里 input 的类型「收紧」为 Bar}
    }

3.3 双重断言

  • 类型断言并不总是能胜利,比方:
  • 如上例子中的代码将会报错(类型 “Event” 到类型 “HTMLElement” 的转换可能是谬误的,因为两种类型不能充沛重叠。如果这是无意的 …),只管曾经应用了类型断言。
  • 如果你依然想应用那个类型,你能够应用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:

    function handler(event: Event) {const element = (event as any) as HTMLElement; // OK
    }
  • TypeScript 是怎么确定单个断言是否足够 ? 当 S 类型是 T 类型的子集,或者 T 类型是 S 类型的子集时,S 能被胜利断言成 T。这是为了在进行类型断言时提供额定的安全性,齐全毫无根据的断言是危险的,如果你想这么做,你能够应用 any

4. 枚举

  • 枚举是组织收集有关联变量的一种形式, 首先咱们看看一般枚举,如果你应用过其它编程语言应该会很相熟。

    enum Color {
    Red = 1, //1
    Green, //2
    Blue, //3
    }
  • 如上,咱们定义了一个数字枚举,Red应用初始化为 1。其余的成员会从 1开始主动增长。换句话说,Color.Red的值为 1Green为 2Blue为 3Red要是没有初始化,即从 0 开始。
    然而这样写就有问题了:
  • 当枚举某个成员非数字时,上面增长的枚举成员须要和其保持一致,这样方能防止报错

    enum Color {
    Red, // 0 
    Green = "Green",// Green
    Blue = "Blue",// Blue
    }
  • 一般枚举的值不会在编译阶段计算,而是保留到程序的执行阶段,咱们看看上面的例子:

    enum Color {
    // 常量枚举
    Red, 
    purple,
    Green = Color.Red,
    Blue = 1 + 1,
    // 十分量枚举
    yellow = Math.random(),
    black = 'hello'.length,
    }
  • 上例的编译后果是:

    var Color;
    (function(Color) {Color[(Color['Red'] = 0)] = 'Red';
    Color[(Color['purple'] = 0)] = 'purple';
    Color[(Color['Green'] = 0)] = 'Green';
    Color[(Color['Blue'] = 2)] = 'Blue';
    Color[(Color['yellow'] =  Math.random())] = 'yellow';
    Color[(Color['black'] = 'hello'.length)] = 'black';
    })(Color || (Color = {}));
  • 先让咱们聚焦 Color[(Color['Red'] = 0)] = 'Red'' 这行代码,其中 Color['Red'] = 0 的意思是将 Color 对象里的 Red 成员值设置为 0。留神,JavaScript 赋值运算符返回的值是被赋予的值(在此例子中是 0),因而下一次 JavaScript 运行时执行的代码是 Color[0] = 'Red'。意味着你能够应用 Color 变量来把字符串枚举类型革新成一个数字或者是数字类型的枚举类型,如下所示:

    enum Color {
    Red, 
    Blue = 1 + 1,
    yellow = Math.random(),
    black = 'hello'.length,
    }
    console.log(Color[0]); // 'Red'
    console.log(Color['Red']); // 0
    console.log(Color[Color.Red]); // 'Red'

5. 高级类型

  • 除了 stringnumberboolean 这种根底类型外,咱们还应该理解一些类型申明中的一些高级用法。

5.1 extends 关键字

  • 根底用法:

    T extends U ? X : Y
  • 示意,如果 T 能够赋值给 U(类型兼容),则返回 X,否则返回 Y;以内置的泛型接口 Extract 为例,它的实现如下:

    type Extract<T, U> = T extends U ? T : never
  • TypeScript 将应用 never 类型来示意不应该存在的状态。下面的意思是,如果 T 中的类型在 U 存在,则返回,否则摈弃。
  • 假如咱们两个类,有三个公共的属性,能够通过 Extract 提取这三个公共属性:
  • TypeScript 中内置了很多工具泛型,除了介绍的这些,内置的泛型在 TypeScript 内置的 lib.es5.d.ts 中都有定义,所以不须要任何依赖就能够间接应用。

5.2 应用 keyof

  • 根底实例:

    interface Foo {
    name: string;
    age: number
    }
    type T = keyof Foo 
    // 等同于
    type T = "name" | "age"
  • extends 常常与 keyof 一起应用,例如咱们有一个 getValue 办法专门用来获取对象的值,然而这个对象并不确定,咱们就能够应用 extends 和 keyof 进行束缚:
  • 当传入对象没有的 key 时,编辑器则会报错。

5.3 应用 in

  • in 则能够遍历枚举类型, 例如:
type Keys = "a" | "b"
type Obj =  {[p in Keys]: any
} //  {a: any, b: any}
  • keyof 产生联结类型, in 则能够遍历枚举类型, 所以他们常常一起应用。

二、好的习惯都须要缓缓养成

如果只是把握了 TypeScript 的一些根底类型,可能很难熟能生巧的去应用 TypeScript。想要很好的驾驭它,只能一直的学习和把握它。

本文只是介绍了几种罕用的实际,心愿还没接触 TypeScript 或对 TypeScript 还不太熟悉的小伙伴赶快在我的项目实际起来,致力晋升代码可维护性和开发幸福感,感激大家的浏览🙏

三、参考文档

  1. TypeScript 官网文档
正文完
 0