前端進階(5) - js 擴展:靜態類型檢查(facebook flow)

js 擴展:靜態類型檢查(facebook flow)

js 語言與 java、C 系列等語言有一點很大的不一樣,就是 js 語言是弱類型語言。js 語言的這個特性可能讓你們以爲 js 很自由,沒有強制性的約束,可是當遇到大型項目的時候,js 的這個特性就會變得比較麻煩,由於這會致使團隊的代碼很不可控。這個緣由也是促使 TypeScript 誕生的一個很重要的緣由。java

但其實不少開發人員仍是比較喜歡用 js 來開發項目,因此 facebook 開發出 flow 來幫助 js 語言擴展靜態類型檢查功能,規避上面提到的問題。git

1. 代碼示例

flow 規定,在須要作 'flow 靜態類型檢查' 文件的開頭加上 // @flow 這段註釋,讓工具識別這個文件須要作靜態類型檢查,不然就會看成通常 js 文件對待,不作靜態類型檢查。es6

flow 靜態類型幾乎能夠應用到全部的 js 對象,包括 es6 擴展的 class, module 等,也包括 jsx 語法。github

如下是一些基礎的靜態類型舉例,更詳細的能夠查看 Type Annotations | Flow.npm

1.1 基本類型

與 js 的基本數據類型相似,包括:數組

  • boolean: 對應 js 的 Boolean 類型
  • number: 對應 js 的 Number 類型
  • string: 對應 js 的 String 類型
  • null: 對應 js 的 null
  • void: 對應 js 的 undefined

正常的 js 代碼babel

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許可證

相關文章
相關標籤/搜索