Last active
August 29, 2015 14:07
-
-
Save coodoo/149587de1b9e6f858b28 to your computer and use it in GitHub Desktop.
基於 https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 的中文註解版。詳細討論在: https://twitter.com/huang47/status/524553659401920513
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 選出 UI 元素 | |
var refreshButton = document.querySelector('.refresh'); | |
var closeButton1 = document.querySelector('.close1'); | |
var closeButton2 = document.querySelector('.close2'); | |
var closeButton3 = document.querySelector('.close3'); | |
// 偵聽 ui 元素的 click 事件並生成 observable | |
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click'); | |
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click'); | |
var close2ClickStream = Rx.Observable.fromEvent(closeButton2, 'click'); | |
var close3ClickStream = Rx.Observable.fromEvent(closeButton3, 'click'); | |
var zoken = '76de43f9a5bdb95c268d8b5b0a2548d03da32c2f'; | |
// 基於 refreshClickStream 之上做 startWith 與 map 處理後 | |
// 返還一個新的 stream 叫 requestStream | |
// | |
// 原理是: | |
// 每次按下滑鼠,會觸發一個 click item,進入 clickStream 內 | |
// scheduler 會定時處理這個 item,也就是跑 .map() 處理 | |
// | |
// 特別之處是下面用 startWith() 在初始化時先塞一個 click 事件進去 | |
// 好讓 clickStream 內有值可處理 | |
var requestStream = refreshClickStream | |
// 由於 refreshClickStream[] 裏原本是空無一物 | |
// 因此用 startWith() 往裏面先塞一個物件, | |
// 也就是模擬一個 click 事件,進而觸發第一次 $.getJSON 請求 | |
// 注意 'startup click' 可以是任何字串,這兩個字本身無特別意義 | |
.startWith('startup click') | |
// 每一個 click event 都轉換為一個 requestURL | |
.map(function() { | |
// 這個值是 github api 需要的 | |
var randomOffset = Math.floor(Math.random()*500); | |
// 這裏返還 requestUrl 出去 | |
return 'https://api.github.com/users?since=' + randomOffset + '&access_token=' + zoken; | |
}); | |
// 將每個 requestUrl 轉成真正的 $.getJSON() 呼叫 | |
// 然後對 requestStream 做 flatMap 處理後 | |
// 返還一個新的 stream 叫 responseStream | |
var responseStream = requestStream | |
// 依序走訪 requestStream 中每個元素 | |
// 此例中它是取得每個元素的 url,然後發出 $.getJSON() 操作 | |
// 並且得回一個 promise 物件, | |
// 這裏的問題是: promise stream of json objects 不方便取得裏面真正的資料 | |
// 因此用 flatMap() 將 promise stream 剝開,即可直接存取裏面 server 返還的真正資料 | |
// 注意跑完 flatMap() 後,照慣例一樣是返還一個新 stream,名稱為 responseStream | |
.flatMap(function (requestUrl) { | |
return Rx.Observable.fromPromise($.getJSON(requestUrl)); | |
}); | |
// 這支是 util | |
// 傳入一個 clickStream,在它身上掛各種 operator 後 | |
// 返還一個新的 suggestionStream 出去 | |
// | |
// 傳入:clickStream | |
// 返還:suggestionStream | |
// 轉換: | |
// - startWith: 給入一個初始物件供啟動 | |
// - combineLatest: 監控 closeClickStream 與 responseStream 任一有變化時,就各取一個新值透過 suggestionStream 返還 | |
// - | |
function createSuggestionStream(closeClickStream) { | |
// 以傳入的 closeClickStream 為基礎, | |
// 對它做 startWith, combineLatest, merge 等處理後 | |
// 產生一個新的 stream 返還出去, 名稱叫 suggestionStream | |
return closeClickStream | |
// 往 closeClickStream 裏塞第一筆資料讓它有東西可跑 | |
// 原因:因為下面用 combineLatest() 要求兩個 stream 都至少要有一筆資料 | |
// 因此如果從來沒按過 close button,就永遠不會有產出,因此他模擬一次 click | |
.startWith('startup click') | |
// 也就同時監控 closeClickStream 與 responseSteram 兩個 stream | |
// 只要其中一個有變化,就各取一個最新值,放入 suggestionStream 內交出去 | |
// 實際上它這裏的用意只是:每當 close 鈕被 click 時,就從 responseStream 裏取一個新值 | |
.combineLatest( responseStream, | |
// 因此只要有 click or 新的response 回來,就會跑下面這個 fn | |
function(click, listUsers) { | |
console.log( '一百取一' ); | |
// github 每次返還 100 個用戶,它從中隨機選一個 | |
return listUsers[Math.floor(Math.random()*listUsers.length)]; | |
} | |
) | |
// 重要: | |
// ↑ 至此是將 clickStream 與 responseSteam 合併為一個 stream (假設稱為 comboStream) | |
// ↓ 然後與另一個 refreshClickStream 做合併,生成一個新的 stream (名稱為 suggestionStream) 返還出去 | |
// | |
// 重要關念為 | |
// - 雖然 merge 了,但 comboStream 與 refreshClickStream 仍然是獨立存在的 | |
// - merge 的意義只是說,以上兩者任何一個有變動,都會在新的 suggestionStream 上發出事件 | |
// - 想像成是兩個 data pool, A & B,合併後放入 C 這個 pool,方便外界只要針對 C 做偵聽即可 | |
// - 因此這樣做的好處只是用戶不需分別向 comboStream 與 refreshClickStream 註冊偵聽 | |
// - 這是為何 @huang47 改寫後將它拆成兩段,就必需分為兩次偵聽的原因 | |
//--------------------------------------------------------------------------------- | |
// 將 suggestionStream 與另一個 stream 合併 | |
.merge( | |
// merge 是將上面在處理的 suggestionStream 與一個新的 stream 合併 | |
// | |
// 只有當按下 refresh 鈕時,才會觸發 refrshClickStream 運行 | |
// 也才會進入這段,故意將 result 全設為 null,目地是要讓 ui 清空 | |
// 這裏返還後,整支 suggestionStream 的內容就變成 null 了,因此 ui 就會清空 | |
// | |
console.log( '\n\t跑清空 result' ); | |
return null; | |
}) | |
) | |
// 至此前面一連串的操作已生成一個新 stream 準備要返還(也就是 pool C) | |
// 這行是在這個新的 stream 前面塞一筆初始資料,正好是 null 值 | |
// 這樣會讓 ui 上第一個推薦人選變成空白 | |
.startWith(null); | |
} | |
// 一開始就跑這支 | |
// 為三個空座位各建立一個 source stream 來供應推薦人選 | |
var suggestion1Stream = createSuggestionStream(close1ClickStream); | |
var suggestion2Stream = createSuggestionStream(close2ClickStream); | |
var suggestion3Stream = createSuggestionStream(close3ClickStream); | |
//======================================================================== | |
// | |
// @huang47 改寫版 http://jsfiddle.net/8jFJH/117/ | |
// 重點在移除 merge 操作,讓兩個 stream 獨立存在,也分別做 subscribe() | |
// 這樣只是程式碼比較長,但較易閱讀,也好維護 | |
refreshClickStream | |
.subscribe(function () { | |
['.suggestion1', '.suggestion2', '.suggestion3'] | |
.map(function (s) { return document.querySelector(s); }) | |
.forEach(function (el) { | |
el.style.visibility = 'hidden'; | |
}) | |
}); | |
//-------------------------------------------------------------- | |
// Rendering | |
// 這支只是 util,分別更新每個空位的推薦人選 | |
function renderSuggestion(suggestedUser, selector) { | |
var suggestionEl = document.querySelector(selector); | |
if (suggestedUser === null) { | |
suggestionEl.style.visibility = 'hidden'; | |
} else { | |
suggestionEl.style.visibility = 'visible'; | |
var usernameEl = suggestionEl.querySelector('.username'); | |
usernameEl.href = suggestedUser.html_url; | |
usernameEl.textContent = suggestedUser.login; | |
var imgEl = suggestionEl.querySelector('img'); | |
imgEl.src = ""; | |
imgEl.src = suggestedUser.avatar_url; | |
} | |
} | |
// | |
//---------------------------------------------------------------- | |
// 前面做完處理後,這裏才開始收割,偵聽每次處理完的結果 | |
suggestion1Stream.subscribe(function (suggestedUser) { | |
console.log( 'ui 事件: ', arguments ); | |
renderSuggestion(suggestedUser, '.suggestion1'); | |
}); | |
// suggestion2Stream.subscribe(function (suggestedUser) { | |
// renderSuggestion(suggestedUser, '.suggestion2'); | |
// }); | |
// suggestion3Stream.subscribe(function (suggestedUser) { | |
// renderSuggestion(suggestedUser, '.suggestion3'); | |
// }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment