More Related Content
Similar to Why Redux-Observable? (20)
Why Redux-Observable?
- 11. 搜尋功能
你可能會這樣寫...
11
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
- 13. 搜尋功能
回傳搜尋資料之前,
顯⽰示 Loading 動畫
13
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST });
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
- 14. 搜尋功能
GET API,取得搜尋資料
14
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
- 15. 搜尋功能
觸發另外⼀一個 action
顯⽰示搜尋結果
15
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
- 16. 搜尋功能
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
變更更網址
16
- 17. 搜尋功能
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
傳入關鍵字,記錄 History
17
- 21. 搜尋功能
宣告 controller 變數
21
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
- 22. 搜尋功能
新增 controller
提供取消 request 的⽅方法
22
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?keyword=${keyword}&lim
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
- 23. 搜尋功能
將舊的 request 取消,
執⾏行行新的 request
23
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?keyword=${keyword}&lim
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
- 24. 搜尋功能
使⽤用 redux-thunk…
24
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?keyword=${keyword}&limit=20`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
- 26. 26
redux-thunk redux-observable
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
export const searchEpic = action$ !=>
action$
.ofType(SEARCH_REQUEST)
.filter(action !=> action.payload)
.switchMap(getSearchResult)
.mergeMap(res !=>
Observable.of(
receiveSearchResult(res.data),
push(`/search/${res.keyword}`),
addSearchHistory(res.keyword)
)
);
- 27. 27
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
export const searchEpic = action$ !=>
action$
.ofType(SEARCH_REQUEST)
.filter(action !=> action.payload)
.switchMap(getSearchResult)
.mergeMap(res !=>
Observable.of(
receiveSearchResult(res.data),
push(`/search/${res.keyword}`),
addSearchHistory(res.keyword)
)
);
25⾏行行程式碼 12⾏行行程式碼
程式碼 少了了 1/2
redux-observable
⼤大約
- 28. 28
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
export const searchEpic = action$ !=>
action$
.ofType(SEARCH_REQUEST)
.filter(action !=> action.payload)
.switchMap(getSearchResult)
.mergeMap(res !=>
Observable.of(
receiveSearchResult(res.data),
push(`/search/${res.keyword}`),
addSearchHistory(res.keyword)
)
);
有 if 判斷 沒有 if 判斷
程式碼 容易易閱讀
redux-observable
比較
- 29. 29
let controller;
export const search = keyword !=> dispatch !=> {
if (!keyword) {
return;
}
dispatch({ type: SEARCH_REQUEST});
if (controller) {
controller.abort();
}
controller = new AbortController();
return fetch(`/search?k=${keyword}`, {
signal: controller.signal,
}).then(res !=> {
dispatch(receiveSearchResult(res));
dispatch(push(`/search/${keyword}`));
dispatch(addKeywordHistory(keyword));
controller = undefined;
});
};
export const searchEpic = action$ !=>
action$
.ofType(SEARCH_REQUEST)
.filter(action !=> action.payload)
.switchMap(getSearchResult)
.mergeMap(res !=>
Observable.of(
receiveSearchResult(res.data),
push(`/search/${res.keyword}`),
addSearchHistory(res.keyword)
)
);
只⽀支援 firefox 57 版 只有 IE 10 以下不⽀支援
有效率的完成功能
redux-observable
- 40. export const openToastEpic = action$ !=> {
return action$
.ofType(OPEN_TOAST)
.delay(3000)
.mapTo({ type: CLOSE_TOAST });
};
Epic
40
- 41. export const openToastEpic = action$ !=> {
return action$
.ofType(OPEN_TOAST)
.delay(3000)
.mapTo({ type: CLOSE_TOAST });
};
Epic
41
- 42. export const openToastEpic = action$ !=> {
return action$
.ofType(OPEN_TOAST)
.delay(3000)
.mapTo({ type: CLOSE_TOAST });
};
Epic
42
- 45. rootEpic.js
45
import { combineEpics } from 'redux-observable';
import { openToastEpic } from './toastStatus.js';
import { checkEmailEpic } from './emailCheckStatus.js';
import { searchEpic } from './search.js';
import { getArticlesEpic } from './articles.js';
export default combineEpics(
openToastEpic,
checkEmailEpic,
searchEpic,
getArticlesEpic
);
- 55. 訊息通知
建立 action creator 和 epic
55
export const openToast = () !=> ({
type: OPEN_TOAST,
});
export const openToastEpic = action$ !=> {
return action$
.ofType(OPEN_TOAST)
.delay(3000)
.mapTo({ type: CLOSE_TOAST });
};
- 58. 訊息通知
delay 3 秒
58
export const openToastEpic = action$ !=> {
return action$
.ofType(OPEN_TOAST)
.delay(3000)
.mapTo({ type: CLOSE_TOAST });
};
- 59. 訊息通知
觸發關閉 Toast 的 action
59
export const openToastEpic = action$ !=> {
return action$
.ofType(OPEN_TOAST)
.delay(3000)
.mapTo({ type: CLOSE_TOAST });
};
- 60. 訊息通知
到 reducer、更更新 state
改變 store
60
export default function toastStatus(state = false, action) {
switch (action.type) {
case OPEN_TOAST:
return true;
case CLOSE_TOAST:
return false;
default:
return state;
}
}
- 61. 訊息通知
到 reducer、更更新 state
改變 store
61
export default function toastStatus(state = false, action) {
switch (action.type) {
case OPEN_TOAST:
return true;
case CLOSE_TOAST:
return false;
default:
return state;
}
}
- 69. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
過濾出對應的 action type
69
- 70. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
觸發後,靜置500毫秒
70
- 71. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
觸發後,靜置500毫秒才做驗證
71
通常⽤用在輸入框
靜置⼀一段時間才觸發
debounceTime
- 72. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
接收最新的 request,
舊的 request 都取消
72
- 73. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
取最新的 request,
舊的 request 都取消
73
取消舊的 request,執⾏行行新的 request
switchMap
情境1: 表單即時驗證
情境2: Autocomplete 的輸入框
- 74. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
獲取 Email 狀狀態
74
- 76. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
Email 格式正確才發 request
76
- 77. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
觸發後,靜置500毫秒才做驗證
77
快速輕鬆的完成功能
不需要會 RxJS,就可以使⽤用 operator
- 80. 過濾出對應的 action type
80
export const getArticlesEpic = action$ !=> {
return action$
.ofType(GET_ARTICLES)
.exhaustMap(getArticlesAPI)
.map(receiveArticles);
};
載入更更多⽂文章
- 81. 取原本送出的 request,
新的 request 都取消
81
export const getArticlesEpic = action$ !=> {
return action$
.ofType(GET_ARTICLES)
.exhaustMap(getArticlesAPI)
.map(receiveArticles);
};
載入更更多⽂文章
- 84. 無限滾動⽂文章
export const getArticlesEpic = action$ !=> {
return action$
.ofType(GET_ARTICLES)
.exhaustMap(getArticlesAPI)
.map(receiveArticles);
};
取原本送出的 request,
新的 request 都取消
84
- 85. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
取最新的 request,
舊的 request 都取消
85
取原本送出的 request,新的 request 都取消
exhaustMap
情境1: 看更更多⽂文章
情境2: 上傳檔案
- 86. export const getArticlesEpic = actions !=> {
return actions
.ofType(GET_ARTICLES)
.throttleTime(100)
.exhaustMap(getArticlesAPI)
.map(receiveArticles);
};
無限滾動⽂文章
throttle 100 毫秒
才送 request
86
- 87. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
觸發後,靜置500毫秒才做驗證
87
通常⽤用在連續性⾏行行為
throttleTime
情境1: 滾動事件
情境2: 拖拉事件
避免⾼高頻率觸發
- 88. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
觸發後,靜置500毫秒才做驗證
88
重⽤用性與可讀性提昇
組合使⽤用 operator
- 89. Email 即時驗證
export const checkEmailEpic = actions !=> {
return actions
.ofType(CHECK_EMAIL)
.debounceTime(500)
.filter(validateEmail)
.switchMap(checkEmailIsUnique)
.map(receiveEmailStatus);
};
觸發後,靜置500毫秒才做驗證
89
優雅的解決非同步問題
簡單使⽤用 operator
- 93. 93
13 Jul 2015
redux-thunk redux-saga
3 Dec 2015
redux-observable
21 Apr 2016
redux-cycle
3 Dec 2016
2017/10/23
Redux middlewares
- 94. redux-thunk redux-observable
94
export const openPopup = () !=> dispatch !=> {
dispatch({
type: OPEN_POPUP,
});
setTimeout(() !=> {
dispatch({
type: CLOSE_POPUP,
});
}, 3000);
};
export const openPopupEpic = action$ !=> {
return action$
.ofType(OPEN_POPUP)
.delay(3000)
.mapTo({ type: CLOSE_POPUP });
};
- 96. redux-saga redux-observable
96
export function* openPopupAsync () {
yield call(delay, 3000)
yield put({ type: 'CLOSE_POPUP' })
}
export function* watchOpenPopupAsync () {
yield takeEvery('OPEN_POPUP', openPopupAsync)
}
export const openPopupEpic = action$ !=> {
return action$
.ofType(OPEN_POPUP)
.delay(3000)
.mapTo({ type: CLOSE_POPUP });
};
- 97. redux-saga
•技術不能轉移
•依賴 syntax (Generator)
97
export function* openPopupAsync () {
yield call(delay, 3000)
yield put({ type: 'CLOSE_POPUP' })
}
export function* watchOpenPopupAsync () {
yield takeEvery('OPEN_POPUP', openPopupAsync)
}
•可以處理理較複雜的非同步問題
•星星數較多,使⽤用社群較⼤大
- 98. redux-cycles redux-observable
98
export const openPopupEpic = action$ !=> {
return action$
.ofType(OPEN_POPUP)
.delay(3000)
.mapTo({ type: CLOSE_POPUP });
};
function openPopup (sources) {
const openPopup$ = sources.ACTION
.filter(action !=> action.type &&=== OPEN_POPUP)
.delay(3000)
.mapTo({ type: CLOSE_POPUP });
return {
ACTION: openPopup$
}
}
- 99. redux-cycles redux-observable
99
function fetchUserData(sources) {
const request$ = sources.ACTION
.filter(action !=> action.type &&=== FETCH_USER)
.map(action !=> ({
url: `${API_URL}users/`,
category: 'users',
}));
const action$ = sources.HTTP
.select('users')
.flatten()
.map(fetchUserFulfilled);
return {
ACTION: action$,
HTTP: request$,
};
}
const fetchUserDataEpic = action$ !=>
action$
.ofType(FETCH_USER)
.mergeMap(action !=>
ajax.getJSON(`${API_URL}users/`)
.map(fetchUserFulfilled)
);
- 100. redux-cycle
•社群⼈人數較少
•過度封裝?
100
function fetchUserData(sources) {
const request$ = sources.ACTION
.filter(action !=> action.type &&=== FETCH_USER)
.map(action !=> ({
url: `${API_URL}users/`,
category: 'users',
}));
const action$ = sources.HTTP
.select('users')
.flatten()
.map(fetchUserFulfilled);
return {
ACTION: action$,
HTTP: request$,
};
}
•可以處理理較複雜的
非同步問題