美文网首页
前端进阶(5) - js 扩展:静态类型检查(facebook

前端进阶(5) - js 扩展:静态类型检查(facebook

作者: senntyou | 来源:发表于2018-08-23 19:28 被阅读30次

    js 扩展:静态类型检查(facebook flow)

    js 语言与 java、C 系列等语言有一点很大的不同,就是 js 语言是弱类型语言。js 语言的这个特性可能让大家觉得 js 很自由,没有强制性的约束,但是当遇到大型项目的时候,js 的这个特性就会变得比较麻烦,因为这会导致团队的代码很不可控。这个原因也是促使 TypeScript 诞生的一个很重要的原因。

    但其实很多开发人员还是比较喜欢用 js 来开发项目,所以 facebook 开发出 flow 来帮助 js 语言扩展静态类型检查功能,规避上面提到的问题。

    1. 代码示例

    flow 规定,在需要做 'flow 静态类型检查' 文件的开头加上 // @flow 这段注释,让工具识别这个文件需要做静态类型检查,否则就会当作一般 js 文件对待,不做静态类型检查。

    flow 静态类型几乎可以应用到所有的 js 对象,包括 es6 扩展的 class, module 等,也包括 jsx 语法。

    以下是一些基础的静态类型举例,更详细的可以查看 Type Annotations | Flow.

    1.1 基本类型

    与 js 的基本数据类型类似,包括:

    • boolean: 对应 js 的 Boolean 类型
    • number: 对应 js 的 Number 类型
    • string: 对应 js 的 String 类型
    • null: 对应 js 的 null
    • void: 对应 js 的 undefined

    正常的 js 代码

    let hello = 'hello'; // 声明一个变量
    
    hello = 2 * 2; // 重新赋值
    
    hello = []; // 重新赋值
    

    加上 flow 静态类型检查扩展的代码

    // @flow
    
    let hello: string = 'hello'; // 声明一个 string 类型的变量
    
    hello = 2 * 2; // 报错
    
    hello = []; // 报错
    
    hello = 'hi'; // 重新赋值
    

    1.2 函数

    正常的 js 代码

    function plus(a, b) {
      return a + b;
    }
    
    plus(); // NaN
    plus(1); // NaN
    plus(1, 2); // 3
    plus('hello'); // 'helloundefined'
    plus('hello', ' hi'); // 'hello hi'
    plus({}, {}); // '[object Object][object Object]'
    

    加上 flow 静态类型检查扩展的代码

    // @flow
    
    // 定义一个 '两个数字参数,返回值也是数字' 的函数
    function plus(a: number, b: number): number {
      return a + b;
    }
    
    plus(); // 报错
    plus(1); // 报错
    plus('hello'); // 报错
    plus('hello', ' hi'); // 报错
    plus({}, {}); // 报错
    
    plus(1, 2); // 3
    

    1.3 可能(Maybe),可选(Optional),语义(Literal),混合(Mixed)

    可能(Maybe)类型用一个 ? 在类型前面表示,包含类型本身、nullundefined

    // @flow
    
    let hello: ?string; // 声明一个数据类型可以是 string, null, undefined 的变量
    
    hello = null; // 赋值
    hello = undefined; // 重新赋值
    hello = 'hello'; // 重新赋值
    hello = 1; // 报错
    hello = true; // 报错
    

    可选(Optional)类型一般用于对象属性或者函数参数,在名称后面加一个 ?,包含类型本身、undefined

    // @flow
    
    const obj: {hello? : string}; // 属性 hello 可以是 string, undefined
    
    obj = {}; // 赋值
    obj = {hello: undefined}; // 重新赋值
    obj = {hello: 'hello'}; // 重新赋值
    obj = {hello: null}; // 报错
    obj = {hello: 1}; // 报错
    obj = {hello: true}; // 报错
    
    // 属性 param 可以是 number, undefined
    function method(param?: number) { /* ... */ }
    
    method(); // 正常
    method(undefined); // 正常
    method(1.12); // 正常
    method(null); // 报错
    method('hello'); // 报错
    

    语义(Literal)类型一般用于声明某个,某几个特定的值(多个值用 | 分隔)

    // @flow
    
    let hello: 'hello'; // 声明一个只能赋值 'hello' 的变量
    
    hello = 'hello'; // 赋值
    hello = 'hi'; // 报错
    hello = 12; // 报错
    hello = undefined; // 报错
    hello = null; // 报错
    
    function method(param: 1 | 'hi' | boolean): void { /* ... */ }
    
    method(); // 报错,缺少参数
    method(1); // ok
    method(1.2); // 报错,类型不对
    method('hi'); // ok
    method('hello'); // 报错,类型不对
    method(true); // ok
    method(false); // ok
    

    混合(Mixed)类型是指任意数据类型

    // @flow
    
    let hello: mixed; // 声明一个 mixed 类型的变量
    
    hello = 'hello'; // 赋值
    hello = 'hi'; // 重新赋值
    hello = 12; // 重新赋值
    hello = undefined; // 重新赋值
    hello = null; // 重新赋值
    

    1.4 复合类型

    数组

    // @flow
    
    let arr1: Array<boolean> = [true, false, true]; // 声明一个元素是 boolean 的数组
    arr1 = [true, 1]; // 报错,1 不是 boolean 值
    arr1 = ['']; // 报错,'' 不是 boolean 值
    
    let arr2: Array<string> = ["A", "B", "C"]; // 声明一个元素是 string 的数组
    
    let arr3: Array<mixed> = [1, true, "three"] // 声明一个元素是任意类型的数组
    arr1 = [true, 1]; // 重新赋值 
    arr1 = ['']; // 重新赋值
    

    map

    // @flow
    
    // 声明一个 map 类型,其有一个名为 foo,类型 boolean 的子元素
    let obj1: { foo: boolean } = { foo: true };
    obj1 = {}; // 报错,缺少 foo 这个属性值
    obj1 = {foo: 1}; // 报错,属性值 foo 的类型必须是 boolean
    obj1 = {foo: false, bar: 'hello'}; // 重新赋值
    
    // 声明一个 map 类型,其有名为 foo, bar, baz,类型 number, boolean, string 的子元素
    let obj2: {
      foo: number,
      bar: boolean,
      baz: string,
    } = {
      foo: 1,
      bar: true,
      baz: 'three',
    };
    

    更静态类型可以查看 Type Annotations | Flow.

    2. 使用工具

    安装

    # 全局安装
    npm i -g flow-bin
    
    # 本地安装
    npm i -D flow-bin
    

    使用

    flow init                       # 初始化项目
    
    flow check path/to/dir          # 检查这个目录下所有的文件
    flow check path/to/js/file      # 检查指定文件
    

    3. 配合 babel 一起使用

    因为 flow 静态类型只是对 js 的扩展,并不是 js 原生支持的,也不能直接运行,所以,一般 flow 都是配合 babel 一起使用的,这样就可以在程序运行的时候进行静态类型检查,达到我们想要的效果。

    3.1 babel-preset-flow

    安装 babel-preset-flow,这样 babel 在转码 js 文件时就能识别 flow 的语法。

    npm i -D babel-preset-flow
    

    .babelrc

    {
      "presets": ["flow"]
    }
    

    源文件(flow)

    // @flow
    
    // 定义一个 '两个数字参数,返回值也是数字' 的函数
    function plus(a: number, b: number): number {
      return a + b;
    }
    
    plus(); // 报错
    plus(1); // 报错
    plus('hello'); // 报错
    plus('hello', ' hi'); // 报错
    plus({}, {}); // 报错
    
    plus(1, 2); // 3
    

    转码后的文件

    // 定义一个 '两个数字参数,返回值也是数字' 的函数
    function plus(a, b) {
      return a + b;
    }
    
    plus(); // 报错
    plus(1); // 报错
    plus('hello'); // 报错
    plus('hello', ' hi'); // 报错
    plus({}, {}); // 报错
    
    plus(1, 2); // 3
    

    3.2 babel-plugin-flow-runtime

    一般会在开发环境下,使用 babel-plugin-flow-runtime 插件,这样就可以在开发的时候,实时检查数据类型,就像原生的运行 flow 静态类型检查一样。(一般在产品环境不会使用这个功能,因为会额外消耗 js 的性能)

    npm i -D babel-plugin-flow-runtime flow-runtime
    

    .babelrc

    {
      "presets": ["flow"],
      "plugins": ["flow-runtime"]
    }
    
    

    源文件(flow)

    // @flow
    
    // 定义一个 '两个数字参数,返回值也是数字' 的函数
    function plus(a: number, b: number): number {
      return a + b;
    }
    
    plus(); // 报错
    plus(1); // 报错
    plus('hello'); // 报错
    plus('hello', ' hi'); // 报错
    plus({}, {}); // 报错
    
    plus(1, 2); // 3
    

    转码后的文件

    import t from 'flow-runtime';
    
    
    // 定义一个 '两个数字参数,返回值也是数字' 的函数
    function plus(a, b) {
      return a + b;
    }
    
    t.annotate(plus, t.function(t.param('a', t.number()), t.param('b', t.number()), t.return(t.number())));
    plus(); // 报错
    plus(1); // 报错
    plus('hello'); // 报错
    plus('hello', ' hi'); // 报错
    plus({}, {}); // 报错
    
    plus(1, 2); // 3
    

    这个时候,js 文件就会导入 flow-runtime 模块,对 plus 函数的参数 a, b 和返回值进行数据类型检查,如果不符合数据定义,就会报错。

    4. 后续

    更多博客,查看 https://github.com/senntyou/blogs

    作者:深予之 (@senntyou)

    版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

    相关文章

      网友评论

          本文标题:前端进阶(5) - js 扩展:静态类型检查(facebook

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