美文网首页
TypeScript(二)TypeScript类型、可选链

TypeScript(二)TypeScript类型、可选链

作者: Imkata | 来源:发表于2022-05-10 20:09 被阅读0次

    JavaScript和TypeScript的数据类型

    我们经常说TypeScript是JavaScript的一个超集:

    JavaScript类型 – string类型

    string类型是字符串类型,可以使用单引号或者双引号表示:

    同时也支持ES6的模板字符串来拼接变量和字符串:

    默认情况下,如果可以推导出对应的标识符的类型时,一般情况下是不加类型注解的。

    JavaScript类型 – number类型

    数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为number类型。

    let num: number = 123
    num = 222
    

    如果你学习过ES6应该知道,ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:

    //十进制
    let num1: number = 100    //100
    //二进制
    let num2: number = 0b100  //4
    //八进制
    let num3: number = 0o100  //64
    //十六进制
    let num4: number = 0x100  //256
    
    console.log(num1, num2, num3, num4)
    

    JavaScript类型 – boolean类型

    boolean类型只有两个取值:true和false,非常简单。

    JavaScript类型 – Array类型

    数组类型的定义也非常简单,有两种方式:

    // 一个数组中在TypeScript开发中, 最好存放的数据类型是固定的(string), 在数组中存放不同的类型是不好的习惯
    // 类型注解: type annotation
    
    // 泛型写法 不推荐(react jsx中是有冲突  <div></div>)
    const names1: Array<string> = []
    const names2: string[] = [] // 推荐
    
    // 在数组中存放不同的类型是不好的习惯
    // names.push("abc")
    // names.push(123)
    

    如果添加其他类型到数组中,那么会报错:

    JavaScript类型 – Object类型

    object对象类型可以用于描述一个对象:

    但是从myinfo中我们不能获取数据,也不能设置数据:

    所以一般情况下,我们使用对象就直接让它进行类型推导,如下:

    const info = {
      name: "why",
      age: 18
    }
    
    console.log(info.name)
    

    JavaScript类型 – Symbol类型

    在ES5中,我们是不可以在对象中添加相同的属性名称的,比如下面的做法:

    通常我们的做法是定义两个不同的属性名字:比如identity1和identity2。

    ES6新增symbol类型,我们也可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值:

    const title1: symbol = Symbol("title")
    const title2: symbol = Symbol('title')
    
    const info = {
      [title1]: "程序员",
      [title2]: "老师"
    }
    

    JavaScript类型 – null和undefined类型

    在 JavaScript 中,undefined 和 null 是两个基本数据类型,在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型。也就是说null类型只有一个值null,undefined类型也只有一个值undefined。

    TypeScript类型 - any类型

    在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于Dart语言中的dynamic类型)。

    any类型有点像一种讨巧的TypeScript手段,我们可以对any类型的变量进行任何的操作:包括获取不存在的属性、方法,可以给一个any类型的变量赋任何的值,比如数字、字符串的值。

    如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any,包括在Vue源码中,也会使用到any来进行某些类型的适配。添加any类型后,其实就和原生的JavaScript代码是一样。

    TypeScript类型 - unknown类型

    unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。

    什么意思呢?我们来看下面的场景:

    function foo() {
      return "abc"
    }
    
    function bar() {
      return 123
    }
    
    // any类型可以赋值给任意类型
    // unknown类型只能赋值给any和unknown类型
    
    let flag = true
    let result: unknown // 最好不要使用any,因为any太灵活了
    if (flag) {
      result = foo()
    } else {
      result = bar()
    }
    
    //报错
    let message: string = result
    //报错
    let num: number = result
    
    console.log(result)
    
    export {}
    

    any类型和unknown类型的区别:

    • any类型可以赋值给任意类型
    • unknown类型只能赋值给any和unknown类型

    解释:定义成any类型那么肯定可以赋值给任意类型,定义成unknown类型就只能赋值给any和unknown类型。unknown类型是TypeScript3.x出现的,目的就是为了防止我们随意赋值,比如把一个unknown类型赋值给string,这肯定是不行的。

    TypeScript类型 - void类型

    void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型。我们可以将null和undefined赋值给void类型,也就是函数可以返回null或者undefined。

    这个函数我们没有写任何类型,那么它默认返回值的类型就是void的,我们也可以显示的来指定返回值是void。

    TypeScript类型 - never类型

    never 表示永远不会发生值的类型,比如一个函数是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型。

    never有什么样的应用场景呢?这里我们举一个例子,但是它用到了联合类型,后面我们会讲到。

    TypeScript类型 - Tuple类型

    tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift等。

    那么tuple和数组有什么区别呢?

    • 首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中(可以放在对象或者元组中)。
    • 其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型。

    Tuple的应用场景

    tuple通常可以作为返回的值,在使用的时候会非常的方便。

    //使用泛型优化
    function useState<T>(state: T) {
      let currentState = state
      const changeState = (newState: T) => {
        currentState = newState
      }
      //const info: [string, number] = ["abc", 18] 和下面类似
      //返回元组类型
      const tuple: [T, (newState: T) => void] = [currentState, changeState]
      return tuple
    }
    
    //对返回值进行解构 counter是number类型, setCounter是函数类型
    const [counter, setCounter] = useState(10);
    setCounter(1000)
    const [title, setTitle] = useState("abc")
    
    // 定义函数类型
    type MyFunction = () => void
    // 使用函数类型
    const foo: MyFunction = () => {}
    

    函数的参数类型

    函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型。

    声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:

    函数的返回值类型

    我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面:

    和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型。某些第三方库出于方便理解,会明确指定返回类型,这个看个人喜好。

    匿名函数的参数类型

    匿名函数与函数声明会有一些不同,当一个函数出现在TypeScript可以确定该函数会被如何调用的地方时,该函数的参数会自动指定类型。

    // 通常情况下, 在定义一个函数时, 都会给参数加上类型注解的
    function foo(message: string) {
    
    }
    
    const names = ["abc", "cba", "nba"]
    // item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解
    // 上下文中的函数: 可以不添加类型注解
    names.forEach(function(item) {
      console.log(item.split(""))
    })
    

    我们并没有指定item的类型,但是item是一个string类型,这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型,这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型。

    对象类型

    如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?我们可以使用对象类型。

    在这里我们使用了一个对象来作为类型,在对象我们可以添加属性,并且告知TypeScript该属性需要是什么类型,属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的,每个属性的类型部分也是可选的,如果不指定,那么就是any类型。

    可选类型

    对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?

    事实上,可选类型可以看做是 类型 | undefined 的联合类型。

    联合类型

    TypeScript的类型系统允许我们使用多种运算符从现有类型中构建新类型,也就是联合类型(Union Type),联合类型是由两个或者多个其他类型组成的类型,表示可以是这些类型中的任何一个值,联合类型中的每一个类型被称之为联合成员(union's members)。

    使用联合类型

    传入给一个联合类型的值是非常简单的,只要保证是联合类型中的某一个类型的值即可。但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法,那么我们怎么处理这样的问题呢?

    我们需要使用缩小(narrow)联合(后续我们还会专门讲解缩小相关的功能),TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型。

    类型别名

    在前面,我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多次,怎么解决这个问题呢?

    比如我们可以使用type给对象类型起一个别名:

    类型断言 as

    有时候TypeScript无法获取具体的类型信息,这个时候我们需要使用类型断言(Type Assertions)。比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型。

    另外案例:Person是Student的父类,关于多态。

    class Person {
    }
    
    //学生可以学习
    class Student extends Person {
      studying() {
      }
    }
    
    function sayHello(p: Person) {
      // 多态
      (p as Student).studying()
    }
    
    const stu = new Student()
    //传入Student, Student肯定是Person
    sayHello(stu)
    
    

    TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:

    按照提示先转为unknown就不会报错了,但是不推荐用。

    非空类型断言 !

    当我们编写下面的代码时,在执行ts的编译阶段会报错:

    这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的。

    但是,如果我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言。非空断言使用的是 !,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测,这类似swift的强制解包。

    可选链的使用

    可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性。
    可选链使用可选链操作符 ?. 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行。虽然可选链操作是ECMAScript提出的特性,但是和TypeScript一起使用更般配。

    ?? 和 !! 的作用

    • ??:空值合并操作符,它是ES11增加的新特性,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。和三目运算符有点像,但是比三目运算符更简洁。
    • !!:将一个其他类型转换成boolean类型,类似于Boolean(变量)的方式,其实就是取反两次。
    //空值合并操作符其实和逻辑或有点像,但是最开始逻辑或发明出来是用在if判断里面的
    //所以如果在if判断中,使用逻辑或,其他地方使用空值合并操作符
    if (message || "你好啊,李银河") {}
    

    字面量类型

    除了前面我们所讲过的类型之外,也可以使用字面量类型(literal types):

    //这时候message是string类型
    let message = "Hello World"
    
    //这时候message是"Hello World"类型, "Hello World"也是可以作为类型的, 叫做字面量类型
    const message = "Hello World"
    // 字面量类型要求类型和值是一样的
    const message: "Hello World" = "Hello World"
    

    那么这样做有什么意义呢?
    默认情况下这么做是没有太大的意义的,但是我们可以将多个类型联合在一起,这样传的参数就固定了,这就很有意义了。

    // 字面量类型的意义, 就是必须结合联合类型
    type Alignment = 'left' | 'right' | 'center'
    
    let align: Alignment = 'left'
    align = 'right'
    
    function changeAlign(align: Alignment) {
      console.log('修改方向:',align)
    }
    
    changeAlign(align)
    

    字面量推理

    我们来看下面的代码:

    上面代码最后一行会报错,这是因为我们的info对象在进行字面量推理的时候,info.method其实是string类型的,但是request函数的method参数是字面量类型的,我们没办法将一个string类型赋值给一个字面量类型,所以会报错。

    解决方式一:

    type Method = 'GET' | 'POST'
    function request(url: string, method: Method) {}
      
    const options = {
      url: "https://www.coderwhy.org/abc",
      method: "POST"
    }
     
    //直接将method转成更具体的类型
    request(options.url, options.method as Method) 
    

    解决方式二:

    type Method = 'GET' | 'POST'
    function request(url: string, method: Method) {}
    
    const options = {
      url: "https://www.coderwhy.org/abc",
      method: "POST"
    } as const
    //通过as const将比较宽泛的类型转成字面量类型
    
    //这样直接使用就行了
    request(options.url, options.method)
    

    解决方式三:

    type Method = 'GET' | 'POST'
    function request(url: string, method: Method) {}
    
    //定义参数对象类型
    type Request = {
      url: string,
      method: Method
    }
    
    const options: Request = {
      url: "https://www.coderwhy.org/abc",
      method: "POST"
    }
    
    request(options.url, options.method)
    

    一般我们使用方式三,虽然麻烦,但是更规范。

    相关文章

      网友评论

          本文标题:TypeScript(二)TypeScript类型、可选链

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