成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

RxJS 實(shí)戰(zhàn)篇(一)拖拽

frontoldman / 2496人閱讀

摘要:相比之下,響應(yīng)式編程在解決此類問(wèn)題上有著得天獨(dú)厚的優(yōu)勢(shì)。當(dāng)然要加深對(duì)的理解還是得多多實(shí)戰(zhàn)。要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的拖拽,需要對(duì)等多個(gè)事件進(jìn)行觀察,并相應(yīng)地改變小方塊的位置。具體實(shí)現(xiàn)可以參見(jiàn)添加初始延遲需求在拖拽的實(shí)際應(yīng)用中,有時(shí)會(huì)希望有個(gè)初始延遲。

本文最初發(fā)布于我的個(gè)人博客:咀嚼之味

面對(duì)交互性很強(qiáng)、數(shù)據(jù)變化復(fù)雜的場(chǎng)景,傳統(tǒng)的前端開(kāi)發(fā)方式往往存在一些共有的問(wèn)題:1). UI 狀態(tài)與數(shù)據(jù)難以追蹤;2). 寫出的代碼可讀性很差,邏輯代碼分布離散。

相比之下,響應(yīng)式編程(Reactive Programming)在解決此類問(wèn)題上有著得天獨(dú)厚的優(yōu)勢(shì)。Vue、Mobx、RxJS 這些庫(kù)都是響應(yīng)式編程思想的結(jié)晶。

很多人在接觸到 RxJS 后會(huì)有一個(gè)共同的感覺(jué):這個(gè)庫(kù)雖然很強(qiáng)大,但奈何各種各樣的 operators 太多了,在實(shí)際場(chǎng)景中根本不知道怎么運(yùn)用!所以本文并不旨在闡釋響應(yīng)式編程的優(yōu)越性,而是通過(guò)循序漸進(jìn)的實(shí)例來(lái)展示 RxJS 常用 operators 的使用場(chǎng)景。如果你尚未入門 RxJS,推薦你可以先看看一位來(lái)自臺(tái)灣的前端工程師 Jerry Hong 寫的 30 天精通 RxJS 系列。不要被三十天這個(gè)標(biāo)題給嚇到啦,如果你有一些函數(shù)式編程的經(jīng)驗(yàn)的話,周末花一天時(shí)間就能看完。當(dāng)然要加深對(duì) RxJS 的理解還是得多多實(shí)戰(zhàn)。畢竟實(shí)踐出真知嘛!

本文不適合 未入門的新手已精通的高手。如果你覺(jué)得你對(duì) RxJS 有了初步的認(rèn)識(shí),但掌握程度不高,可能這篇文章就比較適合你了。你可以嘗試跟著本文的三個(gè)實(shí)例自己先做做看,再對(duì)比一下本文給出的解決方案,相信你能對(duì) RxJS 有更深入的理解。注意,本文給出的解決方案并不一定是最優(yōu)的解決方案,如果你有什么改進(jìn)的建議,可以在文末留言,謝謝!

1. 簡(jiǎn)單的拖拽

需求:給定一個(gè)小方塊,實(shí)現(xiàn)簡(jiǎn)單的拖拽功能,要求鼠標(biāo)在小方塊上按下后能夠拖著小方塊進(jìn)行移動(dòng);鼠標(biāo)放開(kāi)后,則運(yùn)動(dòng)停止。

要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的拖拽,需要對(duì) mousedown, mousemove, mouseup 等多個(gè)事件進(jìn)行觀察,并相應(yīng)地改變小方塊的位置。

首先分析一下,為了相應(yīng)地移動(dòng)小方塊,我們需要知道的信息有:1). 小方塊被拖拽時(shí)的初始位置;2). 小方塊在被拖拽著移動(dòng)時(shí),需要移動(dòng)到的新位置。通過(guò) Marble Diagram 來(lái)描述一下我們的原始流與想要得到的流,其中最下面這個(gè)流就是我們想要用于更新小方塊位置的流。

mousedown   : --d----------------------d---------
mousemove   : -m--m-m-m--m--m---m-m-------m-m-m--
mouseup     : ---------u---------------------u---

dragUpdate  : ----m-m-m-------------------m-m----

簡(jiǎn)而言之,就是在一次 mousedownmouseup 之間觸發(fā) mousemove 時(shí),更新小方塊的位置。要做到這一點(diǎn),最重要的操作符是 takeUntil,相關(guān)的偽代碼如下:

mousedown.switchMap(() => mousemove.takeUntil(mouseup))

switchMaptakeUntil 加入上面的 Marble Diagram:

mousedown  : --d----------------------d---------
mousemove  : -m--m-m-m--m--m---m-m-------m-m-m--
mouseup    : ---------u---------------------u---
     
   stream1$ = mousedown.map(() => mousemove.takeUntil(mouseup))

stream1$   : --d----------------------d---------
                                      
                 m-m-m|                 -m-m|
   
   dragUpdate = stream1$.switch()

dragUpdate : ----m-m-m-------------------m-m----

其實(shí) switchMap 就是 map + switch 組合的簡(jiǎn)寫形式。當(dāng)然,我們還需要同時(shí)記錄一下初始位置并根據(jù)鼠標(biāo)移動(dòng)的距離來(lái)更新小方塊的位置,實(shí)際的實(shí)現(xiàn)代碼如下:

const box = document.getElementById("box")
const mouseDown$ = Rx.Observable.fromEvent(box, "mousedown")
const mouseMove$ = Rx.Observable.fromEvent(document, "mousemove")
const mouseUp$ = Rx.Observable.fromEvent(document, "mouseup")

mouseDown$.map((event) => ({
  pos: getTranslate(box),
  event,
}))
.switchMap((initialState) => {
  const initialPos = initialState.pos
  const { clientX, clientY } = initialState.event
  return mouseMove$.map((moveEvent) => ({
    x: moveEvent.clientX - clientX + initialPos.x,
    y: moveEvent.clientY - clientY + initialPos.y,
  }))
  .takeUntil(mouseUp$)
})
.subscribe((pos) => {
  setTranslate(box, pos)
})

其中,getTranslatesetTranslate 主要作用就是獲取和更新小方塊的位置。具體實(shí)現(xiàn)可以參見(jiàn) Codepen

2. 添加初始延遲

需求:在拖拽的實(shí)際應(yīng)用中,有時(shí)會(huì)希望有個(gè)初始延遲。就像手機(jī)屏幕上的諸多 App 圖標(biāo),在你想要拖拽它們進(jìn)行排序時(shí),通常需要按住圖標(biāo)一小段時(shí)間,比如 200ms(如下圖所示),這時(shí)該如何操作呢?

為了演示方便,這里我們先定義一個(gè)簡(jiǎn)單的動(dòng)畫(huà),當(dāng)用戶鼠標(biāo)按下超過(guò)一定時(shí)間后,播放一個(gè)閃爍動(dòng)畫(huà):

.blink {
  animation: 0.4s linear blinking;
}

@keyframes blinking {
  0% { opacity: 1; }
  50% { opacity: 0; }
  100% { opacity: 1; }
}

此處我們只做一個(gè)簡(jiǎn)單的實(shí)現(xiàn):在用戶鼠標(biāo)按下時(shí)間超過(guò) 200ms 且在這 200ms 的時(shí)間內(nèi)沒(méi)有發(fā)生鼠標(biāo)移動(dòng)時(shí),認(rèn)為拖拽開(kāi)始。偽代碼如下:

mousedown.switchMap(() => $$.delay(200).takeUntil(mousemove))

其中,上面的 $$ 指的是一個(gè)新創(chuàng)建的流。為了得到更直觀的理解,使用多個(gè) Marble Diagram 來(lái)分段理解之前的偽代碼:

mousedown   : --d----------------------d---------
mousemove   : -m---m----m--------m-------------m-

   stream1$ = mousedown.map(() => $$.delay(200).takeUntil(mousemove))

stream1$    : --d----------------------d---------
                                       
                  -|                     ----s|

   dragStart = mousedown.switchMap(() => $$.delay(200).takeUntil(mousemove))

dragStart   : -------------------------------s----

在第一次鼠標(biāo)按下的 200ms 內(nèi),觸發(fā)了 mousemove 事件,所以第一次 mousedown 并沒(méi)有觸發(fā)一次 dragStart,而在第二次鼠標(biāo)按下的 200ms 內(nèi),并沒(méi)有觸發(fā) mousemove 事件,所以最后就引起了一次 dragStart。

結(jié)合之前的簡(jiǎn)單拖拽的實(shí)現(xiàn),代碼如下:

mouseDown$.switchMap((event) => {
  return Rx.Observable.of({
    pos: getTranslate(box),
    event,
  })
  .delay(200)
  .takeUntil(mouseMove$)
})
.switchMap((initialState) => {
  const initialPos = initialState.pos
  const { clientX, clientY } = initialState.event
  box.classList.add("blink")
  return mouseMove$.map((moveEvent) => ({
    x: moveEvent.clientX - clientX + initialPos.x,
    y: moveEvent.clientY - clientY + initialPos.y,
  }))
  .takeUntil(mouseUp$.do(() => box.classList.remove("blink")))
})
.subscribe((pos) => {
  setTranslate(box, pos)
})

其中,多了兩句操作 #box 的 classname 的代碼,主要就是用于觸發(fā)動(dòng)畫(huà)的。完整代碼見(jiàn) Codepen

3. 拖拽接龍

需求:給定 n 個(gè)小方塊,要求拖拽第一個(gè)小方塊進(jìn)行移動(dòng),后續(xù)的小方塊能夠以間隔 0.1s 的時(shí)間跟著之前的小方塊進(jìn)行延遲模仿運(yùn)動(dòng)。

此例中,我們不再要求“初始延遲”,因此針對(duì)正在拖拽著的紅色小方塊,只要沿用第一個(gè)例子中的簡(jiǎn)單拖拽的方法,即可獲取我們需要改變方塊位置的事件流:

mousedown.switchMap(() => mousemove.takeUntil(mouseup))

然而我們?cè)撊绾我来涡薷亩鄠€(gè)方塊的位置呢?首先,可以先構(gòu)造一個(gè)流來(lái)按延遲時(shí)間依次取得我們想要改變的小方塊:

// 獲取所有小方塊,圖示的例子中給出的是 7 個(gè)小方塊
const boxes = document.getElementsByClassName("box")

// 使用 zip 操作符構(gòu)造一個(gè)由 boxes 組成的流
const boxes$ = Rx.Observable.from([].slice.call(boxes, 0))
const delayBoxes$ = boxes$.zip(Rx.Observable.interval(100).startWith(0), (box) => box)

假定 7 個(gè) boxes 在 Marble Diagram 中分別表示為 a, b, c, d, e, f, g

boxes$          : (abcdefg)|
interval(100)   : 0---0---1---2---3---4---5---6---7---8---

   delayBoxes$ = boxes$.zip(Rx.Observable.interval(100).startWith(0), (box) => box)

delayBoxes$     : a---b---c---d---e---f---g|

只要將原本用于修改方塊位置的 mousemove 事件流 mergeMap 到上面例子中的 delayBoxes$ 上,即可完成“拖拽接龍”。偽代碼如下所示:

mousedown.switchMap(() => mousemove.takeUntil(mouseup))
  .mergeMap(() => delayBoxes$.do(() => { /* 此處更新各個(gè)小方塊的位置 */ }))

讓我們繼續(xù)著眼于 Marble Diagram:

delayBoxes$     : ---a---b---c---d---e---f---g|
dragUpdate$     : -----m--------m----------m-------

   stream1$ = dragUpdate$.map(() => delayBoxes$)

stream1$        : -----m-------m----------m-------
                                         
                                          a---b---c---d---e---f---g|
                                 a---b---c---d---e---f---g|
                           a---b---c---d---e---f---g|

   result$ = dragUpdate$.mergeMap(() => delayBoxes$)

result$         : ---------a---b--ac--bd--cea-dfb-egc-f-d-g-e---f---g|

正如上面 Marble Diagram 所示,我們可以借助流的力量從容地在合適的時(shí)機(jī)修改對(duì)應(yīng)的小方塊的位置。具體的實(shí)現(xiàn)代碼如下所示:

const headBox = document.getElementById("head")
const boxes = document.getElementsByClassName("box")
const mouseDown$ = Rx.Observable.fromEvent(headBox, "mousedown")
const mouseMove$ = Rx.Observable.fromEvent(document, "mousemove")
const mouseUp$ = Rx.Observable.fromEvent(document, "mouseup")
const delayBoxes$ = Rx.Observable.from([].slice.call(boxes, 0))
  .zip(Rx.Observable.interval(100).startWith(0), (box) => box)

mouseDown$.map((e) => {
  const pos = getTranslate(headBox)
  return {
    pos,
    event: e,
  }
})
.switchMap((initialState) => {
  const initialPos = initialState.pos
  const { clientX, clientY } = initialState.event
  return mouseMove$.map((moveEvent) => ({
    x: moveEvent.clientX - clientX + initialPos.x,
    y: moveEvent.clientY - clientY + initialPos.y,
  }))
  .takeUntil(mouseUp$)
})
.mergeMap((pos) => {
  return delayBoxes$.do((box) => {
    setTranslate(box, pos)
  })
})
.subscribe()

完整的實(shí)現(xiàn)代碼見(jiàn) Codepen

小結(jié)

這篇文章介紹了關(guān)于拖拽的三個(gè)實(shí)際場(chǎng)景:

在簡(jiǎn)單拖拽的實(shí)例中,使用到了 takeUntil, switchMap 操作符;

需要添加初始延遲時(shí),我們額外使用到 delay 操作符;

在最后的拖拽接龍實(shí)例中,mergeMap 操作符和 zip + interval 的組合發(fā)揮了很大的作用

相信看完本文以后,你們能夠深刻體會(huì)到:結(jié)合 Marble Diagram 來(lái)理解 RxJS 的流是一個(gè)非常棒的方法!

最后大家可以思考一下:在第三個(gè)例子中,如果把 mergeMap 改為 switchMap 或者 concatMap 會(huì)發(fā)生什么?這是課后作業(yè)。下課!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83611.html

相關(guān)文章

  • 學(xué)習(xí)實(shí)踐 - 收藏集 - 掘金

    摘要:官網(wǎng)地址聊天機(jī)器人插件開(kāi)發(fā)實(shí)例教程一創(chuàng)建插件在系統(tǒng)技巧使你的更加專業(yè)前端掘金一個(gè)幫你提升技巧的收藏集。我會(huì)簡(jiǎn)單基于的簡(jiǎn)潔視頻播放器組件前端掘金使用和實(shí)現(xiàn)購(gòu)物車場(chǎng)景前端掘金本文是上篇文章的序章,一直想有機(jī)會(huì)再次實(shí)踐下。 2道面試題:輸入U(xiǎn)RL按回車&HTTP2 - 掘金通過(guò)幾輪面試,我發(fā)現(xiàn)真正那種問(wèn)答的技術(shù)面,寫一堆項(xiàng)目真不如去刷技術(shù)文章作用大,因此刷了一段時(shí)間的博客和掘金,整理下曾經(jīng)被...

    mikyou 評(píng)論0 收藏0
  • RxJS基礎(chǔ)教程

    摘要:是一個(gè)基于可觀測(cè)數(shù)據(jù)流在異步編程應(yīng)用中的庫(kù)。正如官網(wǎng)所說(shuō),是基于觀察者模式,迭代器模式和函數(shù)式編程。它具有時(shí)間與事件響應(yīng)的概念。通知不再發(fā)送任何值。和通知可能只會(huì)在執(zhí)行期間發(fā)生一次,并且只會(huì)執(zhí)行其中的一個(gè)。 RxJS是一個(gè)基于可觀測(cè)數(shù)據(jù)流在異步編程應(yīng)用中的庫(kù)。 ReactiveX is a combination of the best ideas fromthe Observer p...

    defcon 評(píng)論0 收藏0
  • 【響應(yīng)式編程的思維藝術(shù)】 (1)Rxjs專題學(xué)習(xí)計(jì)劃

    摘要:由于技術(shù)棧的學(xué)習(xí),筆者需要在原來(lái)函數(shù)式編程知識(shí)的基礎(chǔ)上,學(xué)習(xí)的使用。筆者在社區(qū)發(fā)現(xiàn)了一個(gè)非常高質(zhì)量的響應(yīng)式編程系列教程共篇,從基礎(chǔ)概念到實(shí)際應(yīng)用講解的非常詳細(xì),有大量直觀的大理石圖來(lái)輔助理解流的處理,對(duì)培養(yǎng)響應(yīng)式編程的思維方式有很大幫助。 showImg(https://segmentfault.com/img/bVus8n); [TOC] 一. 響應(yīng)式編程 響應(yīng)式編程,也稱為流式編程...

    lscho 評(píng)論0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件開(kāi)發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開(kāi)發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。插....

    izhuhaodev 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<