最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Typescript 4.0新特性一览|牛气冲天新年征文

    正文概述 掘金(倒影)   2021-02-07   694

    可变元组类型(Variadic Tuple Types)

    旧版本Typescript要实现一个concat方法,一般实现方式是:

    function concat<T, U>(arr1: T[], arr2: U[]) {
      return [...arr1, ...arr2]
    }
    

    这种方式实现的concat方法的返回值类型是一个联合类型的数组(T | U)[]

    declare const arr1: number[]
    declare const arr2: string[]
    
    // type: (string | number)[]
    const arr = concat(arr1, arr2)
    

    如果要对元祖进行concat操作呢

    declare const arr1: [number, string]
    declare const arr2: [boolean]
    
    // type: (string | number | boolean)[]
    const arr = concat(arr1, arr2)
    

    元祖具有特定长度和元素类型,上例的返回值类型显然是不精确的,期望的返回值类型是[number, string, boolean]

    要实现期望的结果,在旧版本的TS中只能编写重载

    function concat<T, U, V>(arr1: [T, U], arr2: [V]): [T, U, V]
    function concat<T, U>(arr1: T[], arr2: U[]) {
      return [...arr1, ...arr2]
    }
    

    但如果传入的元祖长度不能确定,我们只能不断的编写重载以尽可能覆盖所有的情况,这显然是不可接受的。

    TypeScript 4.0 带来了两个基础更改,并在推断方面进行了改进。

    其中一个更改是范型可用于扩展运算符。这意味着可以用范型声明一个可变的元祖。

    由此就可以实现一个类型支持更好的concat函数

    function concat<T extends unknown[], U extends unknown[]>(t: [...T], u: [...U]): [...T, ...U] {
        return [...t, ...u];
    }
    
    declare const arr1: [string, number]
    declare const arr2: string[]
    declare const arr3: ['hello']
    
    concat(arr1, arr2) // [string, number, ...string[]]
    concat(arr1, arr3) // [string, number, 'hello']
    

    另一个更改是旧版本Typescript的rest参数只支持数组类型,且必须放在元祖的最后;而现在可以放在元祖的任意位置。

    // 旧版本ts
    type t1 = [...string[]]
    type t2 = [...[string, number]] // error: A rest element type must be an array type.
    type t3 = [...string[], string] // A rest element must be last in a tuple type
    

    不确定长度的数组类型使用扩展运算符,如果不放置于最后,那其后的所有元素都将被推断该数组元素的类型和其后元素类型的联合类型

    type t1 = string[]
    type t2 = [boolean, ...t1, number] // [boolean, ...(string | number)[]]
    

    标记元祖元素(Labeled Tuple Elements)

    如果我们要创建一个图标,可能有如下实现

    function createIcon(url: string, size: [number, number]) {
        const [width, height] = size
        return new BMapGL.Icon(url, new BMapGL.Size(width, height));
    }
    

    其中的size参数是元祖类型,但我们在调用createIcon方法的时候只清楚size的类型,并不清楚每个元素的意义。在调用时还需要跳转到函数体去查看size每个元素的意义。

    Typescript 4.0新特性一览|牛气冲天新年征文

    在Typescript 4.0中,元祖元素可以被标记。上例中的createIcon方法可以这样实现:

    function createIcon(url: string, size: [width: number, height: number]) {
        const [width, height] = size
        return new BMapGL.Icon(url, new BMapGL.Size(width, height));
    }
    

    这样在调用时就能查看size参数每个元素的意义

    Typescript 4.0新特性一览|牛气冲天新年征文

    一些使用规则

    在标记一个元组元素时,还必须标记元组中的所有其他元素。

    type Size = [width: number, number] // error: Tuple members must all have names or all not have names.
    

    解构标记时无需使用不同名称命名变量。如下例,解构size参数时变量命名无需使用widthheight

    function createIcon(url: string, size: [width: number, height: number]) {
        const [w, h] = size
        return new BMapGL.Icon(url, new BMapGL.Size(w, h));
    }
    

    使用带标记的元祖可实现重载

    Typescript 4.0新特性一览|牛气冲天新年征文

    从构造器函数中推断类属性(Class Property Inference from Constructors)

    如下例,在旧版本的Typescript中,当开启了noImplicitAny选项,定义的实例属性areasideLength会报错。因为其没有显式声明类型,从而被推断为any。

    class Square { 
        area; 
        sideLength; 
        constructor(sideLength: number) { 
            this.sideLength = sideLength; 
            this.area = sideLength ** 2; 
        } 
    }
    

    而在Typescript 4.0中该实例属性的类型会从 constructor函数中推断,areasideLength都被推断为number类型,不会报错。

    如果对类实例属性的初始化没有写在constructor函数中,Typescript就无法推断该实例属性的类型。

    class Square { 
        // error: Member 'sideLength' implicitly has an 'any' type.
        sideLength; 
        constructor(sideLength: number) { 
            this.initialize(sideLength) 
        } 
        initialize(sideLength: number) { 
            this.sideLength = sideLength; 
        }
    } 
    

    此时需要显示声明实例属性的类型,而如果开启strictPropertyInitialization选项(检查已声明但未在构造函数中设置的类属性)还需要显示赋值断言来使类型系统识别类型

    class Square {
        sideLength!: number; 
        constructor(sideLength: number) { 
            this.initialize(sideLength) 
        } 
        initialize(sideLength: number) { 
            this.sideLength = sideLength; 
        } 
    } 
    

    短路赋值运算符(Short-Circuiting Assignment Operators)

    ES2021新增的特性中包含了逻辑赋值运算符(Logical Assignment Operators)的提案。

    当变量a为truthy时,将其值设置为b,即等价于a = a && b

    a &&= b;
    

    当变量a为falsy时,将其设置为b,即等价于a = a || b

    a ||= b;
    

    当变量a为nullish时,将其设置为b,即等价于a = a ?? b

    ??操作符是ES2020的新增特性Nullish Coalescing

    // set a to b only when a is nullish
    a ??= b;
    

    Typescript 4.0支持了上述特性

    catch子句变量支持声明为unknown(unknown on catch Clause Bindings)

    在旧版本的Typescript中,catch子句的变量拥有any类型,且不可以被声明为其他类型

    try {
    
    } catch(err: unknown) { // error: Catch clause variable cannot have a type annotation.
    
    }
    

    在Typescript 4.0版本支持将该变量声明为unknown,上例不会报错。

    之所以这样做是因为any类型可以兼容其他所有类型,如上例对err变量进行任何操作都不会报类型错误。而unknownany 更安全,因为它会在我们操作值之前提醒我们执行某种类型检查。

    try {
    
    } catch(err: unknown) {
        if (err instanceof Error) {
            console.error(err.message)
        }
    }
    

    定制 JSX Fragment 工厂函数

    旧版本的Typescript便已支持定制JSX工厂函数,可通过jsxFactory选项进行定制。

    在Typescript 4.0中支持通过新的jsxFragmentFactory 选项来定制 Fragment 工厂函数。

    如下tsconfig.json配置告诉 TypeScript 以与 React 兼容的方式转换 JSX,但将每个工厂函数切换为 h 而不是 React.createElement,并使用 Fragment 而不是 React.Fragment

    使用如下tsconfig.json配置

    { 
      "compilerOptions": { 
        "target": "esnext", 
        "module": "commonjs", 
        "jsx": "react", 
        "jsxFactory": "h", 
        "jsxFragmentFactory": "Fragment" 
      } 
    } 
    

    编译如下代码

    import { h, Fragment } from "preact"; 
    let stuff = <> 
        <div>Hello</div> 
    </>; 
    

    将输出

    "use strict";
    exports.__esModule = true;
    /** @jsx h */
    /** @jsxFrag Fragment */
    var preact_1 = require("preact");
    var stuff = preact_1.h(preact_1.Fragment, null,
        preact_1.h("div", null, "Hello"));
    
    

    JSX工厂函数支持使用/** @jsx */注释,去指定当前文件使用的JSX工厂函数。同样,Fragment工厂函数可通过新的/** @jsxFrag */注释去指定。

    如下,在文件头部指定当前文件使用的JSX工厂函数和Fragment工厂函数。

    通过注释指定的方式比在tsconfig.json文件中配置的优先级高

    /** @jsx h */ 
    /** @jsxFrag Fragment */ 
    import { h, Fragment } from "preact"; 
    let stuff = <> 
        <div>Hello</div> 
    </>; 
    

    重大更改

    lib.d.ts

    Typescript 4.0删除了 document.origin,它仅在 IE 的旧版本中有效,而 Safari MDN 建议改用 self.origin。

    如下,在Typescript 4.0版本访问documentorigin属性将提示该属性不存在

    document.origin // error: Property 'origin' does not exist on type 'Document'
    

    如果要在旧版本的IE使用该属性,需要显式设置

    interface Document {
        origin: string
    }
    
    console.log(document.origin)
    

    属性重写访问器(反之亦然)会报错(Properties Overriding Accessors (and vice versa) is an Error)

    旧版本的Typescript中,子类的实例属性覆盖父类的访问器属性只有在使用useDefineForClassFields选项时才会报错。

    class Base { 
        get foo() { 
            return 100; 
        } 
        set foo(val) { 
            // ... 
        } 
    } 
    class Derived extends Base {
    // 旧版本在使用useDefineForClassFields选项会报错 error: 'foo' is defined as an accessor in class 'Base', but is overridden here in 'Derived' as an instance property.
        foo = 10;
    } 
    

    而在Typescript 4.0版本中,无论是否使用useDefineForClassFields选项,子类的实例属性覆盖父类的访问器属性(或子类的访问器属性覆盖父类的实例属性)总是报错。

    delete 的操作对象必须是可选的

    Typescript 4.0版本在启用 strictNullChecks 选项时,使用delete 运算符,操作对象现在必须为 anyunknownnever 或为可选(因为它在类型中包含 undefined)。否则,使用 delete 运算符将会报错。

    interface Thing { 
        prop: string; 
        a: unknown;
        b: any;
        c: never;
        d: undefined;
    } 
    function f(x: Thing) { 
        delete x.prop; // error: The operand of a 'delete' operator must be optional.
        delete x.a
        delete x.b
        delete x.c
        delete x.d
    }
    

    参考资料

    • Announcing TypeScript 4.0

    起源地下载网 » Typescript 4.0新特性一览|牛气冲天新年征文

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元