• 【前端】关于Flow中的联结类型如何使用


    在Flow 的使用入门一文中,我们在语法层面简要介绍了 Flow 的实际使用;其中有一个联结类型,本文介绍联结类型的使用及相关知识点。

    联结类型简介

    「联结类型」是笔者的翻译,Flow 文档中的原名是叫「Union Type」;意即多个类型联结在一起。不过我也有看到译文称为「联合类型」的;译名暂时不重要,确保我们讨论的是同一个东西就行。

    联结类型的书写方式是多个类型用 | 符号联结在一起,如 type A = 1 | 2 | 3;不过如果类型名称较长,也可以分多行来写,每一个类型一行,注意:每个类型名前都要有 | 符号。

    // @flow
    type T = 
      | string
      | number

    联结类型的使用

    联结类型可以将多个任意类型联结在一起,比如:

    // @flow
    type A = 1 | 2 | 3;
    type B = number | 'a' | boolean;
    type C = 'a' | 1 | true | Array;
    type D = A | B | C;

    如代码所示,可以是多个字面量值类型的联结,多个基本类型的联结,甚至是多个联结类型的再次联结。从语义上联结类型可以理解成「或」;如类型为 A 的变量值可以是 1 或者 2 或者 3;是一个允许的类型的集合。

    既然提出了集合的概念,就有子集。在 Flow 的联合类型中;如果某个联结类型 T 满足某一约束要求,那么类型 T 的子集构成的联结类型,也满足这一约束要求;可以说,针对两个联结类型 A 和 B,如果 A 是 B 的子集,那么 A 是 B 的子类型。

    // @flow
    type Base = 1 | 2 | 3 | 4 | 5;
    type Sub = 1 | 2 | 3;
    function myMethod(arg: Base) {
      // ... code here
    }
    let a: Sub = 1;
    myMethod(a);

    联结类型的细化

    函数参数接受一个联结类型是很常用的;比如 jQuery:既可以接受一个 CSS 选择符(string 类型),也可以接受一个函数(Function 类型);如果要给 jQuery 的参数写一个类型定义,大概是这样:

    // @flow
    type T = string | Function;
    function jQuery(arg: T) {
      // ...
    }
    jQuery('div');
    jQuery(function(){
      // ...
    })

    Flow 要求,如果一个函数接受一个联结类型,传入的参数类型可以是类型中的任意一个(one of those types);但是函数内部,必须对联结类型的每一种情况进行处理(all of the possible types);在 Flow 的官网中,把这个原则称为 requires one in, but all out ,而这个过程称为 类型的细化(Refinements) 。

    // @flow
    function myMethod(value: string | number | boolean){
      if (typeof value === 'string') {
        // Block A
      } else if (typeof value === 'number') {
        // Block B
      } else {
        // Block C
      }
    }

    在上述代码中,定义 value 类型是 string、number 和 boolean 的联结类型;因此在函数内部对 value 进行处理时,必须进行区分;只有 value 是 string 类型值才会运行到 A 代码块;只有 value 是 number 类型值才会运行到 B 代码块;而在 C 代码块中,已经把联结类型中的 string 和 number 都排除了,所以这里可以安全得把 value 作为 boolean 类型处理。

    这样通过细化处理把联结类型拆解逐一处理,可以确保联结类型的安全使用。

    不过有一点神奇的是:在处理联结类型后,如果所有的可能类型都判断并处理了,并且还有其他的逻辑分支,Flow 是不做检查的。

    // @flow
    function myMethod(value: string | number ) {
      if(typeof value === 'string') {
        // Block A;
        var a: string = value;
      } else if(typeof value === 'number') {
        // Block B;
        var b: number = value;
      } else {
        // Block C;
        var c: Object = value;
        var d: Array<*> = value.push('a');
        var e: number = value - 1;
      }
    }

    在代码块 C 中,对 value 进行的操作肯定是有冲突的;但是运行 Flow 却不会报错,因为 Flow 聪明的知道:在 A 和 B 代码块中,已经处理了 value 所有的可能性,程序运行是无法到达 C 代码块的。

    Flow 检查器必须知道每一个代码块的可到达性,以及运行到达这段代码块时变量的类型可能;这称为静态类型检查器的可到达性分析( reachability analysis )。

    互斥类型的细化

    在之前的例子中,我们是通过 JavaScript 语言中的 typeof 来判断变量到底是属于联结类型中的哪一个。但是在很多情况下,我们没法使用 typeof 来进行;比如多个对象类型的联结,那又如何处理呢?

    // @flow
    type A = {
      name: string,
    }
    type B = {
      age: number,
    }
    type C = A | B;

    类型 C 是 A 和 B 构成的联结类型;A 定义的类型,即要求该类型的变量,必须有 name 属性,取值是 string 类型(类似于 TypeScript 中的 Interface)。在 JavaScript 的代码中,区分 A、B 两种类型肯定能不能用 typeof,因为都是对象;针对这种类型,Flow 提供两种方案。

    一、互斥类型

    // @flow
    type Success = {
      success: true,
      value: boolean,
    };
    type Failure = {
      success: false,
      error: string
    }
    type Response = Success | Failure;
    
    function handleResponse(res: Response) {
      if(res.success){
        var a: boolean = res.value;
      } else {
        var b: string = res.error;
      }
    }

    类型 Success 和 Failure 都有一个 同名的属性 (success),且定义的类型是一个 精确的字面量类型 ;Flow 将这样的多个对象类型称为互斥类型( disjoint unions )。他们之间就是根据这个同名的属性进行区分的。

    如果刚好你的业务数据中有可以用来区分的同名字段,可以使用互斥类型,否则还需要为了适应 Flow 的要求去修改业务数据结构。

    二、确定结构的对象类型( exact object type )

    使用对象类型,我们只能限制对象中必须有哪些属性,这之外还有其他哪些属性是不限制的;如果我们能精确的定义,接受的对象类型参数只能有哪些属性,那就可以用 确定结构的对象类型 来做类型标注。 确定结构的对象类型 是在定义对象类型的时候,在大括号内部加上一对 | 符号,比如:

    // @flow
    type T = {|
      name: string,
      age: number
    |}

    这个类型的变量有且只有这两个属性。如果是两个这样的 确定结构的对象类型 构成的联结类型,在使用时根据各自独有的属性就可以进行区分了。

    理想的情况是这么用的:

    // @flow
    type A = {|
      name: string
    |}
    type B = {|
      age: number
    |}
    type C = A | B;
    
    function myMethod(value: C) {
      if (typeof value.name === 'string') {
        var a: string = value.name;
      } else {
        var b: number = value.age;
      }
    }
    // 这个例子 Flow 检查是会出错的!!!

    不过遗憾的是,Flow 当前(v0.42.0)对此的实现是不太完善的。

    1. 从代码逻辑来讲,如果限制了参数 value 的类型必须是 C 的话,那么在 else 代码块里,可以确定 value 的类型只能是 B;但是 Flow 没有推断出来。如果你在使用中碰到这样的坑,目前可以这么修改你的代码使其通过 Flow 的检查。

    // @flow
    type A = {|
      name: string
    |}
    type B = {|
      age: number
    |}
    type C = A | B;
    
    function myMethod(value: C) {
      if (typeof value.name === 'string') {
        var a: string = value.name;
      } else if(typeof value.age === 'number') {
        var b: number = value.age;
      }
    }

    2.

    把上述代码中的 typeof 判断类型,改成用 in 操作符判断属性存在,符合 JavaScript 的语言逻辑,但是通不过 Flow 的检查。

    3. 如果直接用 if(o.p) 来区分,能通过 Flow 的检查,但是从 JavaScript 语言逻辑来讲是不正确的;比如判断一个数字类型变量是否存在,数字为 0 时结果就不正确。

  • 相关阅读:
    conda和pip安装有什么区别
    [附源码]Python计算机毕业设计Django新能源汽车租赁
    Android EditText限制只能输入整数和小数,且数字总长度小于等于11位(可自定义),整数5位(可自定义),小数5位(可自定义)
    Linux使用ifconfig命令没有显示ens33或者没有ip地址
    GET请求或者POST请求的特点和业务中的应用场景
    使用magic-api构建迅速开发平台的成功案例分享
    信而泰OLT使用介绍-网络测试仪实操
    pta(浙大第四版)五道经典练习题③
    数据挖掘与知识发现 导论
    有了这个技术,再也不为水浸事件发愁啦!
  • 原文地址:https://blog.csdn.net/m0_62089210/article/details/127821206