30 天精通 RxJS (08):简易拖拉实作 - take, first, takeUntil, concatAll

RxJS Logo

如果是你会如何实作拖拉的功能?

我们今天要接着讲 take, first, takeUntil, concatAll 这四个 operators,并且实作一个简易的拖拉功能。

Operators

take

take 是一个很简单的 operator,顾名思义就是取前几个元素后就结束,范例如下

var source = Rx.Observable.interval(1000)
var example = source.take(3)
example.subscribe({
	next: (value) => {
		console.log(value)
	},
	error: (err) => {
		console.log('Error: ' + err)
	},
	complete: () => {
		console.log('complete')
	},
})
// 0
// 1
// 2
// complete

这里可以看到我们的source原本是会发出无限个元素的,但这里我们用take(3)就会只取前 3 个元素,取完后就直接结束(complete)。

用 Marble diagram 表示如下

source : -----0-----1-----2-----3--..
        take(3)
example: -----0-----1-----2|

first

first 会取 observable 送出的第 1 个元素之后就直接结束,行为跟 take(1) 一致。

var source = Rx.Observable.interval(1000)
var example = source.first()
example.subscribe({
	next: (value) => {
		console.log(value)
	},
	error: (err) => {
		console.log('Error: ' + err)
	},
	complete: () => {
		console.log('complete')
	},
})
// 0
// complete

用 Marble diagram 表示如下

source : -----0-----1-----2-----3--..
        first()
example: -----0|

takeUntil

在实务上 takeUntil 很常使用到,他可以在某件事情发生时,让一个 observable 直送出 完成(complete)讯息,范例如下

var source = Rx.Observable.interval(1000)
var click = Rx.Observable.fromEvent(document.body, 'click')
var example = source.takeUntil(click)

example.subscribe({
	next: (value) => {
		console.log(value)
	},
	error: (err) => {
		console.log('Error: ' + err)
	},
	complete: () => {
		console.log('complete')
	},
})
// 0
// 1
// 2
// 3
// complete (点击body了)

这里我们一开始先用interval建立一个 observable,这个 observable 每隔 1 秒会送出一个从 0 开始递增的数值,接着我们用takeUntil,传入另一个 observable。

当传入takeUntil的 observable 发送值时,原本的 observable 就会直接进入完成(complete)的状态,并且发送完成讯息。 也就是说上面这段代码的行为,会先每 1 秒印出一个数字(从 0 递增)直到我们点击 body 为止,他才会送出 complete 讯息。

如果画成 Marble Diagram 则会像下面这样

source : -----0-----1-----2------3--
click  : ----------------------c----
        takeUntil(click)
example: -----0-----1-----2----|

当 click 一发送元素的时候,observable 就会直接完成(complete)。

concatAll

有时我们的 Observable 送出的元素又是一个 observable,就像是二维阵列,阵列里面的元素是阵列,这时我们就可以用concatAll把它摊平成一维阵列,大家也可以直接把 concatAll 想成把所有元素 concat 起来。

var click = Rx.Observable.fromEvent(document.body, 'click')
var source = click.map((e) => Rx.Observable.of(1, 2, 3))
var example = source.concatAll()
example.subscribe({
	next: (value) => {
		console.log(value)
	},
	error: (err) => {
		console.log('Error: ' + err)
	},
	complete: () => {
		console.log('complete')
	},
})

这个范例我们每点击一次 body 就会立刻送出 1,2,3,如果用 Marble diagram 表示则如下

click  : ------c------------c--------
        map(e => Rx.Observable.of(1,2,3))
source : ------o------------o--------
                \            \
                 (123)|       (123)|
                   concatAll()
example: ------(123)--------(123)------------

这里可以看到 source observable 内部每次发送的值也是 observable,这时我们用 concatAll 就可以把 source 摊平成 example。

这里需要注意的是concatAll会处理 source 先发出来的 observable,必须等到这个 observable 结束,才会再处理下一个 source 发出来的 observable,让我们用下面这个范例说明

var obs1 = Rx.Observable.interval(1000).take(5)
var obs2 = Rx.Observable.interval(500).take(2)
var obs3 = Rx.Observable.interval(2000).take(1)
var source = Rx.Observable.of(obs1, obs2, obs3)
var example = source.concatAll()
example.subscribe({
	next: (value) => {
		console.log(value)
	},
	error: (err) => {
		console.log('Error: ' + err)
	},
	complete: () => {
		console.log('complete')
	},
})
// 0
// 1
// 2
// 3
// 4
// 0
// 1
// 0
// complete

这里可以看到source会送出 3 个 observable,但是concatAll后的行为永远都是先处理第一个 observable,等到当前处理的结束后才会再处理下一个。

source : (o1                 o2      o3)|
           \                  \       \
            --0--1--2--3--4|   -0-1|   ----0|

                concatAll()
example: --0--1--2--3--4-0-1----0|

简易拖拉

当学完前面几个 operator 后,我们就很轻松地做出拖拉的功能,先让我们来看一下需求

  1. 首先画面上有一个部件(#drag)
  2. 当鼠标在组件(#drag)上按下左键(mousedown)时,开始监听鼠标移动(mousemove)的位置
  3. 当鼠标左键放掉(mouseup)时,结束监听鼠标移动
  4. 当鼠标移动(mousemove)被监听时,跟着修改组件的样式属性

第一步我已经完成了,大家可以直接到以下两个链接做练习

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width" />
		<title>JS Drag</title>
		<style>
			html,
			body {
				height: 100%;
				background-color: tomato;
				position: relative;
			}

			#drag {
				position: absolute;
				display: inline-block;
				width: 100px;
				height: 100px;
				background-color: #fff;
				cursor: all-scroll;
			}
		</style>
	</head>
	<body>
		<div id="drag"></div>
	</body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.8/Rx.js"></script>
</html>

第二步我们要先取得各个 DOM 对象,组件(#drag)跟 body。

const dragDOM = document.getElementById('drag')
const body = document.body

要获取 body 的原因是因为鼠标移动(mousemove)跟鼠标左键放掉(mouseup)都应该是在整个 body 监听。
第三步我们写出各个会用到的监听事件,并用fromEvent来取得各个 observable。

  • 对 #drag 监听 mousedown
  • 对 body 监听 mouseup
  • 对 body 监听 mousemove
const mouseDown = Rx.Observable.fromEvent(dragDOM, 'mousedown')
const mouseUp = Rx.Observable.fromEvent(body, 'mouseup')
const mouseMove = Rx.Observable.fromEvent(body, 'mousemove')

记得还没subscribe之前都不会开始监听,一定会等到 subscribe 之后 observable 才会开始送值。

第四步开始写逻辑

当 mouseDown 时,转成 mouseMove 事件

const source = mouseDown.map((event) => mouseMove)

mouseMove 要在 mouseUp 后结束
加上 takeUntil(mouseUp)

const source = mouseDown.map((event) => mouseMove.takeUntil(mouseUp))

这时 source 大概长像这样

source: -------e--------------e-----
                \              \
                  --m-m-m-m|     -m--m-m--m-m|

m 代表 mousemove event

concatAll()摊平 source 成一维。

const source = mouseDown
	.map((event) => mouseMove.takeUntil(mouseUp))
	.concatAll()

用 map 把 mousemove event 转成 x,y 的位置,并且订阅。

source
	.map((m) => {
		return {
			x: m.clientX,
			y: m.clientY,
		}
	})
	.subscribe((pos) => {
		dragDOM.style.left = pos.x + 'px'
		dragDOM.style.top = pos.y + 'px'
	})

到这里我们就已经完成了简易的拖拉功能了!完整的代码如下

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width" />
		<title>JS Drag</title>
		<style>
			html,
			body {
				height: 100%;
				background-color: tomato;
				position: relative;
			}

			#drag {
				position: absolute;
				display: inline-block;
				width: 100px;
				height: 100px;
				background-color: #fff;
				cursor: all-scroll;
			}
		</style>
	</head>
	<body>
		<div id="drag"></div>
	</body>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.8/Rx.js"></script>
	<script>
		const dragDOM = document.getElementById('drag')
		const body = document.body
		const mouseDown = Rx.Observable.fromEvent(dragDOM, 'mousedown')
		const mouseUp = Rx.Observable.fromEvent(body, 'mouseup')
		const mouseMove = Rx.Observable.fromEvent(body, 'mousemove')
		mouseDown
			.map((event) => mouseMove.takeUntil(mouseUp))
			.concatAll()
			.map((event) => ({ x: event.clientX, y: event.clientY }))
			.subscribe((pos) => {
				dragDOM.style.left = pos.x + 'px'
				dragDOM.style.top = pos.y + 'px'
			})
	</script>
</html>

不知道读者有没有感受到,我们整个代码不到 15 行,而且只要能够看懂各个 operators,我们程序可读性是非常的高。

虽然这只是一个简单的拖拉实现,但已经展示出 RxJS 带来的威力,它让我们的代码更加的简洁,也更好的维护!

今日小结

我们今天介绍了四个 operators 分别是 take, first, takeUntil, concatAll,并且完成了一个简易的拖拉功能,我们之后会把这个拖拉功能做得更完整,并且整合其他功能!

不知道读者今天有没有收获?如果有任何问题,欢迎在下方留言给我! 如果你喜欢这篇文章,请至标题旁帮我按个 星星+like,谢谢。

本系列仅作为学习记录所用,摘录自30天精通Rxjs!强烈推荐!膜拜大佬!

posted @   楚小九  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示