博文原地址,期待你的 Star 。
一、BaconJS 是什麼
BaconJS 是一個函式響應式程式設計(Functional Reactive Programming)的 JS 庫。雖說是函式響應式程式設計,但我用了會兒,感覺響應式程式設計更令人印象深刻,它與業界比較火的響應式程式設計庫 RxJS 有點類似。值得一提的是,Angular 內建的許多 API 就使用了 RxJS 。可見,響應式程式設計可能會是未來一個很重要的發展方向。
二、如何理解 FRP
函式響應式程式設計(Function Reactive Programmin),簡稱 FRP 。這是 BaconJS 最顯著的特點,要想入門 BaconJS ,只需理解這種程式設計思維即可,不必被官網提供的海量 API 所困擾。
FRP = FP(Function Programming) + RP(Reactive Programming)
顧名思義,函式響應式程式設計 = 函數語言程式設計(Functional Programming) + 響應式程式設計(Reactive Programming)
這兩種程式設計模式是兩個很大的話題,都可以聊很久。我在這裡就大致的羅列下兩者的一些特點,並簡述下我個人的理解。
2.1 函數語言程式設計(FP)
函數語言程式設計是業界很火的一個概念,我曾不止一次試圖去徹底的弄懂它,但至今也是一知半解的狀態~
不管怎麼樣, 以下幾個 FP 的特點還是需要了解的:
- 純函式(Pure function),函式唯一的輸入只會有唯一的輸出。
- 不可修改狀態(Immutable state)。
我們常常把 FP 和指令式程式設計(Imperative programming)進行比較,我個人認為 FP 更加強調程式碼的可預測性(Predictable),儘可能的避免程式碼的副作用(Side effect),更加關心資料的對映,而指令式程式設計關心的是解決問題的步驟。
2.2 響應式程式設計(RP)
隨著 Google 前端框架 Angular(Angular 1.x 稱為 AngularJS,Angular v2+ 稱為 Angular) 的釋出,由於 Angular 幾乎所有的非同步介面都使用了響應式程式設計庫 RxJS ,響應式程式設計得到了越來越多的關注。
我算是半個 NG 粉,有用 Angular 開發過一個專案,用下來感覺 RxJS 的開發體驗特別好。總的來說,RP 有如下幾個特點:
- 流(Stream)的概念,衍生的概念有:資料流(Data stream)、事件流(Event stream) 。
- 鏈式呼叫(Chain invoke),使程式碼看起來清晰、簡潔。
- 處理非同步資料流的神器。
響應式程式設計主要是用來處理非同步資料流(Asynchronous data stream),可以是 ajax 請求產生的資料流,也可以是 DOM 事件(或者其他時間)產生的事件流。
對於其核心的概念:流(Stream),我的理解是:流是按照時間順序排列的一系列正在進行的事件。在流
這個概念的基礎上,RP 框架提供了一系列(海量)的介面來處理流,使得我們開發出來的程式碼顯得非常清晰。
想要深入的理解響應式程式設計,推薦閱讀: The instroduction to Reactive Programming you've been missing
三、基本概念
3.1 Observable
觀察物件,是 EventStream 和 Property 物件共同的父類,大部分對流進行操作的介面都封裝在該物件中。
3.2 EventStream
EventStream,又稱事件流,是 BaconJS 中出現頻率最高的一個詞。其本質就是一個流物件,開發者可以通過鏈式的方式進行呼叫,並且可以通過一些 Stream API
來提高開發的效率。
3.3 Property
Property,可以理解為事件流對應的"值",通過 toProperty( ) 介面可以將事件流轉化成 Property 物件。
3.4 舉個例子
為了方便理解 EventStream 和 Property ,我們來看個具體的例子。
需求:某個表單頁面,需要在上面填寫手機號和驗證碼才可以點選註冊按鈕。
剖析下這個需求,我們需要做下面幾件事:
- 需要顯示手機號和驗證碼的輸入框,另外還有一個註冊按鈕。
- 驗證輸入手機號和驗證碼格式的有效性。
- 如果手機號和驗證碼都填寫了,且都是有效的,將註冊按鈕的 disabled 屬性設定為 false 。
核心程式碼如下:
function isPhoneAvailable(n) {
return /^[1][3,4,5,7,8][0-9]{9}$/.test(n);
}
function isVCAvailable(c) {
return c.length === 4;
}
// 1. 生成手機號碼輸入框對應的 'keyup' 事件流。
// 2. 通過 map 方法校驗手機號碼的有效性。
// 3. 將事件流物件轉化成 Property 物件,方便後面使用 'and' 方法。
let phoneNumber$ = $('#phone_number')
.asEventStream('keyup')
.map((event) => {
let value = $(event.target).val();
return isPhoneAvailable(value);
})
.toProperty('');
// 同上
let verificationCode$ = $('#verification_code')
.asEventStream('keyup')
.map((event) => {
let value = $(event.target).val();
return isVCAvailable(value);
})
.toProperty('');
// 通過 'Property' 物件的 'and' 方法,生成一個新的 'Property' 物件
// Property.prototype.and 的含義:等同於 return A&B;
let registerButton$ = phoneNumber$.and(verificationCode$);
// 對 registerButton$ 取反,然後通過 'assign' 方法設定註冊按鈕的 disabled 屬性
registerButton$.not().assign($('#register_button'), 'attr', 'disabled');
複製程式碼
四、常用的 API
4.1 Bacon.fromEvent( )
用途:通過 DOM 的 EventTarget 或是 NodeJS 的 EventEmitter 來建立事件流。
NodeJS 例子:
const Bacon = require('baconjs').Bacon;
const events = require('events');
const eventEmitter = new events.EventEmitter();
Bacon
.fromEvent(eventEmitter, 'someEvent')
.onValue((data) => { console.log(data) });
eventEmitter.emit('someEvent', 'some data');
// 輸出 =>
// 'some data'
複製程式碼
4.2 Obervable.property.map( )
用途:對映到一個新的值。
例子:
// 例子一: 可以給回撥函式傳參
Bacon
.fromArray([1, 2, 3])
.map(function (a, b) {
console.log(`${a}, ${b}`)
return a*b;
}, 3)
.log();
// 輸出 =>
// 3, 1
// 3
// 3, 2
// 6
// 3, 3
// 9
// <end>
// 例子二: 可以用 '.foo' 獲取物件的某個屬性值
Bacon
.fromPoll(1000)
.map(() => { return { foo: `foo${parseInt(Math.random()*100)}` } })
.take(4)
.map('.foo')
.log();
// 輸出(foo後面的數字是隨機的) =>
// foo25
// foo35
// foo98
// foo76
// <end>
複製程式碼
4.3 EventStream.merge( )
用途:將一個 EventStream merge 到另一個 EventStream 中。
例子:
let eventStream1 = Bacon.fromArray([1, 2, 5]);
let eventSream2 = Bacon.fromArray([3, 4]);
eventStream1.merge(eventSream2).log();
// 輸出 =>
// 1
// 2
// 3
// 4
// 5
// <end>
複製程式碼
4.4 TIP
剛開始使用 RxJS 的時候容易被海量的 API 給嚇到了,而且並不是所有的介面都有 Demo 有些介面會顯然比較難理解。BaconJS 也是一樣,有許多的介面,光看官方的文件有時會比較難理解。
這裡,我們分享一個我快速理解某個介面的 TIP 。那就是:查閱具體介面單元測試(UT)中的所有 Case 。個人感覺,這比看官方文件高效很多。