Demo地址 vs 不使用Coroutine的控件地址java
本篇並非 ScrollView 的新輪子, 而是對比兩種實現方式的差異, 來認識coroutine.react
要實現的是一個對 RN 中 ScrollView 的封裝, 給它添加一個隱藏的 Header, 具備下拉刷新功能.ios
假設你已經對 js 的 Iterators and generators有所瞭解.git
function* idMaker() {
let index = 0;
while(true)
yield index++;
}
let gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
複製代碼
這是官網 generator 的栗子, yield 做爲一個相似 return 的語法返回id, 下次調用 next()
時候, 繼續上次位置 -> 循環 -> 繼續返回新id.github
The next() method also accepts a value which can be used to modify the internal state of the generator. A value passed to next() will be treated as the result of the last yield expression that paused the generator.express
yield 還能夠捕獲 next(x)
傳的參數, 因此能夠根據傳的不一樣參數, yield 代理轉接不一樣的方法.react-native
再舉個新的栗子.bash
function* logTest(x) {
console.log('hello, in logTest!');
while (true) {
console.log('received:', yield);
}
}
let gen = logTest();
gen.next(); // hello, in logTest!
gen.next(1); // received: 1
gen.next('b'); // received: b
gen.next({a: 1}); // received: {a: 1}
複製代碼
這個方法中, 獲取了 next 的參數, 調用 gen.next(1)
直接輸出告終果.app
如何自動執行 generator , 而不是手動調用 next()
呢? 使用 coroutine
:
function coroutine(f) {
var o = f(); // instantiate the coroutine
o.next(); // execute until the first yield
return function(x) {
o.next(x);
}
}
複製代碼
這樣能夠給 logTest
裝備上 coroutine
:
let coLogTest = coroutine(logTest); // hello, in logTest!
coLogTest('abc'); // received: abc
coLogTest(2); // received: 2
複製代碼
再看個簡單栗子吧:
let loginState = false;
function* loginStateSwitcher() {
while (true) {
yield;
loginState = true;
console.log('Login!');
yield;
loginState = false;
console.log('Logout!');
}
}
let switcher = coroutine(loginStateSwitcher);
switcher(); // Login!
switcher(); // Logout!
switcher(); // Login!
複製代碼
直接一個 switcher()
用戶登陸登出便捷明瞭.
能夠大體看下沒有使用 coroutine 的處理方式:
RefreshHeader
到 ScrollView
的頭上onScrollBeginDrag
, onScroll
, onScrollEndDrag
方法_dragFlag = true
和 _offsetY
onScroll
this.state,refreshStatus
爲 releaseToRefresh
this.state,refreshStatus
爲 pullToRefresh
_dragFlag = false
和記錄 _offsetY
releaseToRefresh
, 去刷新, 設置 _isRefreshing = true
而且 this.state,refreshStatus
設置爲 refreshing
, 調用 props.onRefresh()
方法, scrollView 滾動到保持刷新狀態位置 { x: 0, y: -80 }
onRefresh(onEndRefresh)
, 須要將結束刷新的方法回調給用戶onRefreshEnd
方法裏將 _isRefreshing
設爲 false, this.state,refreshStatus
設爲 pullToRefresh
, scrollView 滾動到初始位置 { x: 0, y: 0}
能夠去看下代碼, 幾乎全部拖拽釋放邏輯分散到 onScrollBeginDrag
, onScroll
, onScrollEndDrag
方法中了, 若是這幾個方法要共享狀態就須要申請幾個臨時變量, 好比 _offsetY
, _isRefreshing
, 和 _dragFlag
.
this.loop = coroutine(function* () {
let e = {};
while (e = yield) {
if (
e.type === RefreshActionType.drag
&& that.state.refreshStatus !== RefreshStatus.refreshing
) {
while (e = yield) {
if (e.type === RefreshActionType.scroll) {
if (e.offsetY <= -REFRESH_VIEW_HEIGHT) {
that.changeRefreshStateTo(RefreshStatus.releaseToRefresh);
} else {
that.changeRefreshStateTo(RefreshStatus.pullToRefresh);
}
} else if (e.type === RefreshActionType.release) {
if (e.offsetY <= -REFRESH_VIEW_HEIGHT) {
that.changeRefreshStateTo(RefreshStatus.refreshing);
that.scrollToRefreshing();
that.props.onRefresh(() => {
// in case the refreshing state not change
setTimeout(that.onRefreshEnd, 500);
});
} else {
that.scrollToNormal();
}
break;
}
}
}
}
});
複製代碼
只須要在相應的事件時候調用 this.loop
便可.
onScroll = (event) => {
const { y } = event.nativeEvent.contentOffset;
this.loop({ type: RefreshActionType.scroll, offsetY: y });
}
onScrollBeginDrag = (event) => {
this.loop({ type: RefreshActionType.drag });
}
onScrollEndDrag = (event) => {
const { y } = event.nativeEvent.contentOffset;
this.loop({ type: RefreshActionType.release, offsetY: y });
}
複製代碼
協程方法接受參數 {type: drag, offsetY: 0}
, 用來根據當時拖拽事件和位置處理相應邏輯.
能夠看到協程方法裏有兩個 while (e = yield)
:
while (e = yield) {
if (
e.type === RefreshActionType.drag
&& that.state.refreshStatus !== RefreshStatus.refreshing) {
// ..
}
複製代碼
第一個配合 if, 能夠限制用戶只有當第一次拖拽開始時候來開啓下一步.
while (e = yield) {
if (e.type === RefreshActionType.scroll) {}
else if (e.type === RefreshActionType.release) {}
}
複製代碼
第二個用來處理滑動過程當中和釋放的事件, 這裏能夠確定用戶是進行了拖拽纔有的事件, 因而就免去了 _dragFlag
臨時變量.
當事件爲 RefreshActionType.scroll
, 再根據 offsetY
調用 changeRefreshStateTo()
設置當前刷新的狀態爲 releaseToRefresh
仍是 pullToRefresh
.
當事件爲 RefreshActionType.release
, 判斷 offsetY
, 若是超過觸發刷新位置, 調用 changeRefreshStateTo()
設置當前刷新狀態爲 refreshing
, 將 scrollview 固定到刷新狀態的位置(不然會自動滑上去), 而且調用 props.onRefresh()
; 若是不超過觸發刷新位置, 則將 scrollView 滑動到初始位置(隱藏header). break 退出當前 while 循環, 繼續等待下次 drag 事件到來.
<Header />
會根據當前狀態展現不一樣文字, 提示用戶繼續下拉刷新,釋放刷新和刷新中
, 根據刷新狀態設置下尖頭,上箭頭仍是 Loading.
PS.
setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
一直是下拉狀態的issue, 是因爲setState不會當即觸發改變狀態致使的, 爲解決這個問題, 個人處理方式是加一個半秒的延遲:
that.props.onRefresh(() => {
// in case the refreshing state not change
setTimeout(that.onRefreshEnd, 500);
});
複製代碼
若是發現其餘優勢, 歡迎留言.
若是還有見過其餘使用場景, 歡迎留言.