美文网首页程序员
浅析TypeScript

浅析TypeScript

作者: 光哥很霸气 | 来源:发表于2019-03-06 22:39 被阅读10次

    TypeScript是什么

    1. 是微软开发的一项开源技术;
    2. 属于JavaScript类型的超集,即JavaScript支持的TypeScript全都支持,但是TS支持的JS原生不一定支持;
    3. 给予原生JavaScript类型支持,原生JavaScript其实是动态类型的编程语言,有一句话大家应该听说过,「动态类型一时爽,代码重构火葬场」;而TypeScript的存在,就相当于对JavaScript在大型项目的开发上,给予有力的支撑。

    为什么选择TypeScript

    辅助开发流

    我们在进行软件开发的时候,通常都是接口先行的(这里的接口不是狭义上的interface)。
    具体到前端来说,面对一个页面,即是自顶向下的,设计组件。
    这其中就包括组件功能的划分,组件之间的交互,以及理解如何组合组件形成一个新的子系统,各个新的子系统又联系在一起,形成一个更大的系统,最终有效的结合在一起,组成最后的页面。
    在这些逻辑关系中,接口都发挥着重要的作用。而现如今的前端,除了需要处理view层逻辑,还需要处理service逻辑以及页面中间态逻辑,这其中TypeScript能发挥巨大价值。
    没有理清楚接口就尝试写代码,相当于一开始就陷入细节的陷阱里。
    其实不仅是写代码,如果有熟悉写书的朋友一定知道,在写一本书之前,最先做的就是写目录,一本书的目录写好了,那么这本书的整体方向脉络基本就决定,而剩下要去做的,就是填充内容了。

    更早发现错误

    前端业务里,有大部分的bug是由于调用方对实现方所需的类型的不确定导致的

    img

    rollbar是一个前端监控平台,通过其给出的数据可以看出,大部分问题基本都是类型问题,而通过typescript的类型校验,可以直接在编译阶段直接规避该问题。

    显著提升的代码效率

    当你的项目中使用TypeScript时,大部分时候我们都不需要关注所调用的代码具体是怎么实现的,依赖了哪些参数,只要通过类型就可以初步判断。
    并且在vscode编辑器强大的支持下,我们可以实现诸如代码自动引入,类型未编译即可校验等强大功能。

    Mar-06-2019 22-47-28.gif

    快速入门

    JavaScript vs TypeScript

    通过JavaScript与TypeScript的对比,相信大家能快速理解并学习TypeScript。

    变量声明

    // JavaScript
    let name = 'Mike';
    let age = 18;
    name = 10; // 正常运行
    
    // TypeScript
    let name:string = 'Mike';
    let age:number = 18;
    name = 10; // 由于name的类型已经定义为string,因此会抛出异常
    // 通常变量类型不需要显示的声明,TypeScript会自己解析变量类型
    let height = 20; // 此时height为number类型
    

    函数声明

    // JavaScript
    function func(params1:number,params2:string){
      // ...
    }
    func(1, '1') // 编译通过
    func(1, 1) // 编译通过
    
    // TypeScript
    function func(params1:number,params2:string){
      // ...
    }
    func(1, '1') // 编译通过
    func(1, 1) // 编译失败
    

    React组件

    // JSX
    import React from 'react';
    import T from 'prop-types';
    
    class App extends React.Components{
      static propTypes = {
        name: T.string.isRequired
      }
      state = {
        age:13
      }
      render(){
        return <div>{this.props.name} age:{this.state.age}</div>
      }
    }
    
    // JSX
    import React from 'react';
    interface AppProps {
      name: string
    }
    interface AppState {
      age: number
    }
    class App extends React.Components<AppProps,AppState>{
      state = {
        age: 13
      }
      render(){
        return <div>{this.props.name} age:{this.state.age}</div>
      }
    }
    

    理解重载

    重载是Java,C++等语言具有的一种特性。在写代码的过程中,我们常常会有一个函数,需要根据不同的参数,进行不同的逻辑处理。而重载可以定义参数不同的函数,根据变量名的不同,自动执行对应变量名的函数。
    下面以Java为例

    public class Overloading {
        public int test(){
            System.out.println("test1");
            return 1;
        }
     
        public void test(int a){
            System.out.println("test2");
        }   
     
        //以下两个参数类型顺序不同
        public String test(int a,String s){
            System.out.println("test3");
            return "returntest3";
        }   
     
        public String test(String s,int a){
            System.out.println("test4");
            return "returntest4";
        }   
     
        public static void main(String[] args){
            Overloading o = new Overloading();
            System.out.println(o.test());
            o.test(1);
            System.out.println(o.test(1,"test3"));
            System.out.println(o.test("test4",1));
        }
    }
    

    然而TypeScript里的重载并没有那么智能,其重载的主要目的还是做静态类型检查。

    function getItself(it: string): string;
    function getItself(it: number): number;
    
    function getItself(it: string | number) {
      let result;
      if (typeof it === 'string') {
        result = '123';
        return result;
      } else {
        result = 123;
        return result;
      }
    }
    
    const a = getItself(123) // a此时为number类型
    const b = getItself('123') // b此时为string类型
    

    理解interface

    在TypeScript文档里,花了很大的篇幅去描述interface,即接口。可以说明interface是TypeScript里非常重要的概念。

    One of TypeScript’s core principles is that type-checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”. In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well as contracts with code outside of your project.

    duck typing,也就是鸭子类型,摘自维基百科里说,“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”,
    其实在我们平时写代码的过程中,广泛的使用了鸭子类型,例如

    const coordinates = {x:1,y:3};
    function printCoordinates(params){
      console.log(`x:${params.x};y:${params.y}`)
    }
    printCoordinates(coordinates)
    

    如上对于coordiantes对象的使用便是鸭子类型,即我们不关心这个变量是从哪个类继承来的,只要它有x坐标,y坐标即可。
    正是由于我们代码中广泛的使用,且其非常容易发生错误(rollbar报错排行榜第一名),因此TypeScript引入interface帮助我们加以辅助。

    const coordinates = {x:1,y:3};
    function addCoordinates(params){
      return params.x + params.y + params.z
    }
    addCoordinates(coordinates)
    

    由于调用方并不知道addCoordinates参数对象,还需要一个z值,便会导致代码直接报错,页面crash。

    const coordinates = {x:1,y:3};
    interface ParamsInterface {
      x: number;
      y: number;
      z: number;
    }
    function addCoordinates(params: ParamsInterface){
      return params.x + params.y + params.z
    }
    addCoordinates(coordinates) // 编译不通过
    

    可以看到使用了TypeScript加持后,这类问题可以直接在编译时,甚至加上编辑器的支持,可以直接在写代码的过程中,就能发现错误。

    理解泛型

    假设我们要实现一个方法,其作用就是将对象包装成一个数组,用JavaScript实现即时:

    function toArray(params){
      return [params]
    }
    toArray({name:'Mike'})
    

    然而当使用TypeScript实现的时候,我们需要在执行前就定义好函数返回的类型,但是我们又不能确定这个对象到底是什么类型,这里就可以借助泛型来实现:

    function toArray<T>(params: T): T[] {
      return [params];
    }
    toArray<{ name: string }>({ name: 'Mike' });
    

    其实可以简单地将泛型理解为类型的变量,在这里,通过给toArray提供一个泛型变量,让toArray可以根据不同类型,返回不同的类型。

    高级技巧

    索引访问操作符T[K]

    function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
      return o[name]; // o[name] is of type T[K]
    }
    getProperty({age:18}, 'age')
    

    如上T[K]返回的类型就是number
    首先泛型T代表传入参数,即{age:number},第二个name被约束成keyof T,也就是T对象的key,在这里就是age。那么返回值T[K]就能很直白的推断出为number类型。

    泛型约束

    通过对泛型进行extends,对类型进行一个约束。
    例如希望类型都具有length类型

    interface Lengthwise {
        length: number;
    }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
        console.log(arg.length);  // Now we know it has a .length property, so no more error
        return arg;
    }
    

    映射类型 [P in Keys]

    type Keys = 'option1' | 'option2';
    type Flags = { [K in Keys]: boolean };
    

    等价于

    type Flags = {
        option1: boolean;
        option2: boolean;
    }
    

    将所有属性变成可选属性

    type Option<T> = {
        [P in keyof T]?: T[P]
    }
    interface Person {
        name: string;
        age: number;
    }
    type OptionPerson = Option<Person>;
    

    Exclude<T,U>
    接受两个类型,去除T中的U

    Excract<'age'|'name','age'> // 'age'
    

    Extract<T,U>
    同样接受两个类型,提取T中的U

    Excract<'age'|'name'|'height','age'|'weight'> // 'age'
    

    更多可能性

    现在开发流程,前后端都各自维护一个系统,彼此独立且随着业务进展越来越大。而前后端之间唯一的桥梁就是接口文档(例如proto),这个桥梁看似可行,其实是非常脆弱的。
    为什么这么说,因为其中一端产生变化,另外一端其实是无感的。 往往只能通过人为的方式去告知另一端发生的变化,(而这里便是沟通成本),并且由于经常有时候,一个后端服务被N个应用调用,因此其需要通知到N个相关维护人员,类似广播事件。而对于被告知的一方,其也需要人肉的方式去检查代码由于接口变更所引起的变化。
    对于需要人肉检查代码引起的变化,可以通过TypeScript对接口modal进行类型规范,很大程度上加以改善;但是对于变更维护来说,仍然有许多人工的,重复性的劳动在里面。 这是大家需要解决的问题。

    相关文章

      网友评论

        本文标题:浅析TypeScript

        本文链接:https://www.haomeiwen.com/subject/bhmfpqtx.html