协程在RN中的使用
vs
本篇并不是 ScrollView 的新轮子, 而是对比两种实现方式的差别, 来认识coroutine.
要实现的是一个对 RN 中 ScrollView 的封装, 给它添加一个隐藏的 Header, 具有下拉刷新功能.
假设你已经对 js 的 Iterators and generators有所了解.
什么是 Coroutine
这是官网 generator 的栗子, yield 作为一个类似 return 的语法返回id, 下次调用 next()
时候, 继续上次位置 -> 循环 -> 继续返回新id.
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.
yield 还可以捕获 next(x)
传的参数, 所以可以根据传的不同参数, yield 代理转接不同的方法.
再举个新的栗子.
这个方法中, 获取了 next 的参数, 调用 gen.next(1)
直接输出了结果.
如何自动执行 generator , 而不是手动调用 next()
呢? 使用 coroutine
:
这样可以给 logTest
装备上 coroutine
:
再看个简单栗子吧:
直接一个 switcher()
用户登录登出便捷明了.
ScrollView 下拉刷新的逻辑
可以大致看下没有使用 coroutine 的处理方式:
- 放一个
RefreshHeader
到ScrollView
的头上 - 绑定
onScrollBeginDrag
,onScroll
,onScrollEndDrag
方法 - 用户开始拖拽 scrollview, 记录
_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 }
- props 里的
onRefresh(onEndRefresh)
, 需要将结束刷新的方法回调给用户 onRefreshEnd
方法里将_isRefreshing
设为 false,this.state,refreshStatus
设为pullToRefresh
, scrollView 滚动到初始位置{ x: 0, y: 0}
- 设置标志位
可以去看下代码, 几乎所有拖拽释放逻辑分散到 onScrollBeginDrag
, onScroll
, onScrollEndDrag
方法中了, 如果这几个方法要共享状态就需要申请几个临时变量, 比如 _offsetY
, _isRefreshing
, 和 _dragFlag
.
使用 coroutine 统筹管理
只需要在相应的事件时候调用 this.loop
即可.
协程方法接受参数 {type: drag, offsetY: 0}
, 用来根据当时拖拽事件和位置处理相应逻辑.
可以看到协程方法里有两个 while (e = yield)
:
第一个配合 if, 可以限制用户只有当第一次拖拽开始时候来开启下一步.
第二个用来处理滑动过程中和释放的事件, 这里可以肯定用户是进行了拖拽才有的事件, 于是就免去了 _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不会立即触发改变状态导致的, 为解决这个问题, 我的处理方式是加一个半秒的延迟:
使用 coroutine 的优点
- 逻辑清晰
- 减少不必要的变量
如果发现其他优点, 欢迎留言.
其他使用场景
如果还有见过其他使用场景, 欢迎留言.