Vue2源码梳理:关于静态类型检查工具flow

关于flow

1 ) 概述

  • https://flow.org/en/docs/getting-started/
  • Flow 是 facebook 出品的 JavaScript 静态类型检查工具
  • Vue.js 的源码利用了 Flow 做了静态类型检查,所以了解 Flow 有助于我们阅读源码
  • vue的源码,它并不是用纯ES6写的,用了flow作为这个静态语法检查工具
  • 关于vue2的版本,在 [email protected] 都在使用,在 [email protected] 开始向vue3版本靠了,都不用一套构建流程了
  • 所以,我们用的都是 2.6.14 以下的版本

2 ) Why 使用

  • JavaScript 是动态类型语言,它的灵活性有目共睹
  • 但是过于灵活的副作用是很容易就写出非常隐蔽的隐患代码
  • 在编译期甚至看上去都不会报错,但在运行阶段就可能出现各种奇怪的 bug。
  • 类型检查是当前动态类型语言的发展趋势,所谓类型检查,就是在编译期尽早发现(由类型错误引起的)bug
  • 又不影响代码运行(不需要运行时动态检查类型),使编写 JavaScript 具有和编写 Java 等强类型语言相近的体验
  • 项目越复杂就越需要通过工具的手段来保证项目的维护性和增强代码的可读性
  • Vue.js 在做 2.0 重构的时候,在 ES2015 的基础上,除了 ESLint 保证代码风格之外,也引入了 Flow 做静态类型检查
  • 之所以选择 Flow,主要是因为 Babel 和 ESLint 都有对应的 Flow 插件以支持语法
  • 可以完全沿用现有的构建配置,非常小成本的改动就可以拥有静态类型检查的能力

3 )Flow 的工作方式

  • 通常类型检查分成 2 种方式:
    • 类型推断:通过变量的使用上下文来推断出变量类型,然后根据这些推断来检查类型
    • 类型注释:事先注释好我们期待的类型,Flow 会基于这些注释来判断

flow的类型推断

  • 它不需要任何代码修改即可进行类型检查,最小化开发者的工作量
  • 它不会强制你改变开发习惯,因为它会自动推断出变量的类型
  • 这就是所谓的类型推断,Flow 最重要的特性之一
  • 安装flow: $ npm i -g flow-bin
  • flow 工具还有一个配置文件 .flowconfig, 使用 $ flow init 来创建
    [ignore]
    
    [include]
    
    [libs]
    
    [lints]
    
    [options]
    
    [strict]
    
  • 写一些代码: index.js
    /*@flow*/
    function split(str) {
      return str.split(' ')
    }
    split(11)
    
  • 上述代码中,split被字符串来调用,如果传入的是number, 则会报错,执行:$ flow,可见报错
  • 备注:代码中的 /@flow/ 是有用的,如果不加,则不会进行检查
  • 这个就是非常简单的类型推断, 但是下面的代码是不会报错的
    /*@flow*/
    function add(x, y){
      return x + y
    }
    add('Hello', 11)
    
  • 其实我们希望上述代码报错,如何修改呢?需要借助类型注释

flow的类型注释

1 )number类型

  • 修改上述代码,如下
    /*@flow*/
    function add(x: number, y: number): number {
      return x + y
    }
    add('Hello', 11)
    
  • 经过上述修改,再次运行 flow 就会报错,这里是ES6 和 TS中的标准语法
  • 现在 Flow 就能检查出错误,因为函数参数的期待类型为数字,而我们提供了字符串

2 )数组类型

  • 给出如下代码
    /*@flow*/
    var arr: Array<number> = [1, 2, 3]
    arr.push('Hello')
    
  • 数组类型注释的格式是 Array,T 表示数组中每项的数据类型
  • 在上述代码中,arr 是每项均为数字的数组
  • 如果我们给这个数组添加了一个字符串,Flow 能检查出错误

3 ) 类和对象

  • 给出如下代码
    /*@flow*/
    class Bar {
      x: string;           // x 是字符串
      y: string | number | void;  // y 可以是字符串或者数字或者不传
      z: boolean;
    
      constructor(x: string, y: string | number | void) {
        this.x = x
        this.y = y
        this.z = false
      }
    }
    
    // var bar: Bar = new Bar('hello') // 这里y是void不传 也可
    var bar: Bar = new Bar('hello', 4)
    
    var obj: { a: string, b: number, c: Array<string>, d: Bar } = {
      a: 'hello',
      b: 11,
      c: ['hello', 'world'],
      d: new Bar('hello', 3)
    }
    
  • 上述代码是没有问题的,类的类型注释格式如上,可以对类自身的属性做类型检查,也可以对构造函数的参数做类型检查
  • 这里需要注意的是,属性 y 的类型中间用 | 做间隔,表示 y 的类型即可以是字符串也可以是数字
  • 对象的注释类型类似于类,需要指定对象属性的类型

4 ) Null

  • 若想任意类型 T 可以为 null 或者 undefined,只需类似如下写成 ?T 的格式即可
    /*@flow*/
    var foo: ?string = null
    
  • 此时,foo 可以为字符串,也可以为 null,
  • 目前我们只列举了 Flow 的一些常见的类型注释

5 )特殊类型

const a:'foo' = 'foo' // 字面量类型
const b: 'success'|'warnning'|'danger' = 'success'; // 联合指定类型
const c:string | number = 1 // 指定多个简单类型 或者等于一个字符串
const d: {foo: string, bar: number } = {foo: 'foo', bar: 100 } // 指定引用类型
// mixed 类型 所有类型的联合类型 是一个具体类型 编译阶段会检查 强类型,内部调用需要判断类型才能调用具体类型的方法
function e(value:mixed) {}
e('a')
e(1)
// any类型 接受所有类型 弱类型 编译阶段不会检查
function f(value: any) {}
f('a')
f(1)

6 ) 第三方库的使用

  • 如果引入第三方库,Flow检查时就会抛出错误,但这并不是我们期待的错误
  • 我们只需创建一个库定义(libdef)。libdef是包含第三方库声明的JS文件简称
  • 比如,Flow并不认识 $,所以会报错,要解决这个问题,我们需要引入jQuery的库定义
  • 使用flow-typed来解决,通过 $npm install -g flow-typed 安装flow-typed仓库
    • 它包含了众多流行的第三方库的 libdef
  • 只需在项目根目录下创建一个名为 flow-typed 的文件夹,并且下载相关的定义文件即可
  • 安装成功之后, 运行 $ flow-typed install 来检查 package.json文件
  • 并进行下载所有项目中用到的第三方库的libdef
  • 等下载完后,再 $ npm run flow,就会发现没有错误了
  • 如果你用的库并不在flow-typed仓库,你可以创建你自己的 libdef
    • 参考:https://flow.org/en/docs/libdefs/creation/

7 )移除类型注释

7.1 使用 flow-remove-types 工具

  • 额外添加的类型注释不是正确的JavaScript语法,打包编译的时候需要在源码中移除

  • 可以通过flow-remove-types来剔除,或者如果你已经用Babel来转译JS

  • 你可以使用Babel preset来移除。我们只讨论第一种方法

  • 首先需要安装 flow-remove-types 作为项目依赖库

    • $ npm install --save-dev flow-remove-types
  • 之后,在package.json文件中添加另一个 script 入口

    "scripts": {
      "flow": "flow",
      "build": "flow-remove-types src/ -d dist/"
    }
    
  • 运行npm run build将剔除src文件夹下的所有类型注释,在dist文件夹中保存编译后的版本

  • 编译后的文件就是普通的能运行于浏览器的JavaScript文件

7.2 使用 babel-preset-flow

  • https://babeljs.io/docs/babel-preset-flow/

Flow 在Vue.js源码中的应用

  • 有时我们想引用第三方库,或者自定义一些类型,但 Flow 并不认识,因此检查的时候会报错
  • 为了解决这类问题,Flow 提出了一个 libdef 的概念,可以用来识别这些第三方库或者是自定义类型
  • 而 Vue.js 也利用了这一特性
  • 在 Vue.js 的主目录下有 .flowconfig 文件, 它是 Flow 的配置文件
    • 参考: https://flow.org/en/docs/config/
  • 这其中的 [libs] 部分用来描述包含指定库定义的目录,默认是名为 flow-typed 的目录
  • 这里 [libs] 配置的是flow,表示指定的库定义都在 flow 文件夹,我们打开这个目录,会发现文件如下
    flow
    ├── compiler.js        # 编译相关
    ├── component.js       # 组件数据结构
    ├── global-api.js      # Global API 结构
    ├── modules.js         # 第三方库定义
    ├── options.js         # 选项相关
    ├── ssr.js             # 服务端渲染相关
    ├── vnode.js           # 虚拟 node 相关
    
  • 可以看到,Vue.js 有很多自定义类型的定义,在阅读源码的时候
  • 如果遇到某个类型并想了解它完整的数据结构的时候,可以回来翻阅这些数据结构的定义
  • 通过对 Flow 的认识,有助于我们阅读 Vue 的源码
  • 并且这种静态类型检查的方式非常有利于大型项目源码的开发和维护
  • 类似 Flow 的工具还有如 TypeScript

你可能感兴趣的:(Vue,Weex,vue.js)