前言

这篇主要是给一些简单例子, 从中体会 RxJS 在管理上的思路.

Slide Down Effect with Dynamic Content

我在这篇 CSS & JS Effect – FAQ Accordion & Slide Down 讲过如何实现 slide down with dynamic content.

效果大概是这样的

RxJS 的思路步骤

元素 > 事件 > 状态 > 渲染

1. 先把涉及到的元素找出来. 比如上面的 open button, close button, add more button, card 等等

2. 把监听的事件找出来, 比如 click, transitionend 等等

3. 抽象出状态, 然后通过事件改变状态. 比如 Status: 'opening' | 'opened' | 'closing' | 'closed'

opening 表示正要打开, opened 表示已经打开, closing 表示正要关闭, closed 表示已经关了.

依据上面的操作那么就是 closed > opening > opened > closing > closed, 当然依据用户的操作也可以是 closed > opening > closing > closed (在还没有完全打开的时候 user 就点击了关闭)

4. 依据状态对元素进行渲染 (修改 DOM)

RxJS 之管理

从上面几个步骤可以发现它带有 mvvm 的思想, 也有 redux 那种 state management 的味道.

拆分步骤有几个好处

1. 出现 bug 的时候容易排查定位.

2. 实现的时候可以一步一步一来.

坏处就是复杂了一些. 所以这其实是一个取舍. 如果你的项目没有遇到 bug 难定位, 不用分段实现 (为了休息, 缓口气) 的话, 其实不用 RxJS 也是 ok 的.

Without RxJS 版本

const openBtn = document.querySelector('.open-btn')!;
const cardWrapper = document.querySelector<HTMLElement>('.card-wrapper')!;
openBtn.addEventListener('click', () => {
cardWrapper.style.height = `${cardWrapper.scrollHeight}px`;
}); const closeBtn = document.querySelector('.close-btn')!;
closeBtn.addEventListener('click', () => {
if (cardWrapper.style.height === 'auto') {
cardWrapper.style.height = `${cardWrapper.scrollHeight}px`;
requestAnimationFrame(() => {
cardWrapper.style.removeProperty('height');
});
} else {
cardWrapper.style.removeProperty('height');
}
}); cardWrapper.addEventListener('transitionend', () => {
if (cardWrapper.style.height !== '') {
cardWrapper.style.height = 'auto';
}
}); const addMoreBtn = document.querySelector('.add-more-btn')!;
const description = document.querySelector('.description')!;
addMoreBtn.addEventListener('click', () => {
description.textContent = `${description.textContent}\n${description.textContent}`;
});

简单明了, 就是监听然后操作 DOM, 需要判断的地方直接读取 DOM 当前的状态.

RxJS 版本

先把 element 抓出来

import { fromEvent, map, merge, pairwise, startWith, withLatestFrom } from 'rxjs';

const openBtn = document.querySelector('.open-btn')!;
const closeBtn = document.querySelector('.close-btn')!;
const cardWrapper = document.querySelector<HTMLElement>('.card-wrapper')!;
const description = document.querySelector('.description')!;

监听 event 并转换成 state (状态)

type Status = 'opening' | 'opened' | 'closing' | 'closed';

const opening$ = fromEvent(openBtn, 'click').pipe(map<Event, Status>(() => 'opening'));
const closing$ = fromEvent(closeBtn, 'click').pipe(map<Event, Status>(() => 'closing'));
const openingOrClosing$ = merge(opening$, closing$);

const transitionend$ = fromEvent(cardWrapper, 'transitionend');

const openedOrClosed$ = transitionend$.pipe(
withLatestFrom(openingOrClosing$),
map(([_event, openingOrClosing]) => (openingOrClosing === 'opening' ? 'opened' : 'closed'))
);

const status$ = merge(openingOrClosing$, openedOrClosed$).pipe(
startWith<Status>('closed'),
pairwise()
);

这里是考功夫的地方, 需要懂多一点 RxJS 的操作. 很多 stream 都是通过组合搞出来的.

渲染

status$.subscribe(([prevStatus, currStatus]) => {
switch (currStatus) {
case 'opening':
cardWrapper.style.height = `${cardWrapper.scrollHeight}px`;
break;
case 'opened':
cardWrapper.style.height = 'auto';
break;
case 'closing':
{
if (prevStatus === 'opening') {
cardWrapper.style.height = '0';
} else {
cardWrapper.style.height = `${cardWrapper.scrollHeight}px`;
requestAnimationFrame(() => {
cardWrapper.style.height = '0';
});
}
}
break;
}
});

这里和 pure JS 最大的不同是, 它不通过读取 DOM 发现当前是什么状态的, 而是通过 RxJS 把之前的状态缓存了起来.

这样代码就很直观好理解了.

最后补上

const addMoreBtn = document.querySelector('.add-more-btn')!;
addMoreBtn.addEventListener('click', () => {
description.textContent = `${description.textContent}\n${description.textContent}`;
});

由于这个很简单所以不需要用 RxJS 来实现.

显示公告体验

当用户游览网站到 “价格” 这个部分时, 需要显示公司的公告.

有一个体验要求是, 用户必须 "静止" 在这个部分超过 2 秒钟才能显示公告. 如果用户只是轻轻滑过, 那么是不显示公告的.

单侧版本

先简化成一个测试版本

HTML

<body>
<div class="container">
<div class="prev"></div>
<div class="box">box</div>
<div class="next"></div>
</div>
</body>

CSS Style

body {
display: flex;
justify-content: center;
align-items: center; .container {
display: flex;
flex-direction: column;
gap: 2rem; .box {
width: 300px;
height: 600px;
border: 1px solid black;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
color: red;
} .next,
.prev {
height: 100vh;
background-color: lightblue;
}
}
}

效果

思路与实现

首先看看需求描述

当 box 出现后 + 没有 scrolling 2 秒后 = 显示公告

虽然 RxJS standard 的实现步骤是 元素 > 事件 > 状态 > 渲染, 但这一题比较简单.

元素就只有 box, 也谈不上什么状态. 渲染更不用说. 所以只要专注在事件就行了.

box 显示事件

const intersected$ = new Observable<boolean>(subscriber => {
const box = document.querySelector('.box')!;
const io = new IntersectionObserver(
entries => {
subscriber.next(entries[0].intersectionRatio >= 0.2 && entries[0].isIntersecting);
},
{
threshold: [0, 0.2],
}
);
io.observe(box);
}).pipe(distinctUntilChanged(), share());
const [boxShowed$, boxHidden$] = partition(intersected$, showed => showed);

利用 Intersection Observer 监听 box 的显示和隐藏. 当隐藏触发, 我们就必须停止后续的行为.

scrolling 事件

const scrolling$ = fromEventPattern(
handler => {
document.addEventListener('scroll', handler, { passive: true, capture: true });
},
handler => {
document.removeEventListener('scroll', handler, { capture: true });
}
);
const noScrollingMoreThanTwoSecond$ = scrolling$.pipe(startWith(null), debounceTime(2000), take(1));

我们需要知道用户没有 scrolling 超过 2 秒. 这里用了 debounceTime, 如果用户一直 scroll 就重来, 直到它停止超过 2 秒

事件结合和渲染

最后就是监听 boxShowed$ > 然后监听 2 秒没有 scrolling > 然后显示. 中途如果 box hidden 那就作废.

boxShowed$
.pipe(switchMap(() => noScrollingMoreThanTwoSecond$.pipe(takeUntil(boxHidden$))))
.subscribe(() => {
const h1 = document.createElement('h1');
h1.textContent = '显示公告';
h1.style.position = 'fixed';
h1.style.top = '50%';
h1.style.left = '50%';
h1.style.transform = 'translate(-50%, -50%)';
h1.style.fontSize = '3rem';
h1.style.color = 'red';
document.body.appendChild(h1);
});

这种一小步, 一小步, 最后大组合的实现手法体验是不是很棒呢? 如果你喜欢, 那就多用 RxJS 呗.

其它实战例子

用 wheel 模拟 scroll

RxJS 系列 – 实战练习的更多相关文章

  1. webpack 多页应用架构系列实战

    阅读目录 1.webpack配置了解 2.webpack CommonsChunkPlugin公共代码剥离 3.了解ProvidePlugin的用途 回到顶部 1.webpack配置了解 webpac ...

  2. [徐培成系列实战课程]docker篇

    [徐培成系列实战课程]docker篇 如何利用docker快速构建Spark独立模式的集群 1.介绍 利用docker容器技术快速构建跨节点的独立模型的Spark大数据集群.Spark是时下非常热门的 ...

  3. 【SpringBoot基础系列-实战】如何指定 bean 最先加载(应用篇)

    [基础系列-实战]如何指定 bean 最先加载(应用篇) 在日常的业务开发中,绝大多数我们都是不关注 bean 的加载顺序,然而如果在某些场景下,当我们希望某个 bean 优于其他的 bean 被实例 ...

  4. Code First开发系列实战之使用EF搭建小型博客平台

    返回<8天掌握EF的Code First开发>总目录 本篇目录 理解应用需求 数据库设计 创建实体数据模型 创建实体类 创建关系和导航属性 实现DbContext类 执行数据访问 理解仓储 ...

  5. RocketMQ系列实战

    RocketMQ实战(一)RocketMQ实战(二)RocketMQ实战(三):分布式事务RocketMQ实战(四)

  6. Elasticsearch系列---实战搜索语法

    概要 本篇介绍Query DSL的语法案例,查询语句的调试,以及排序的相关内容. 基本语法 空查询 最简单的搜索命令,不指定索引和类型的空搜索,它将返回集群下所有索引的所有文档(默认显示10条): G ...

  7. Elasticsearch系列---实战零停机重建索引

    前言 我们使用Elasticsearch索引文档时,最理想的情况是文档JSON结构是确定的,数据源源不断地灌进来即可,但实际情况中,没人能够阻拦需求的变更,在项目的某个版本,可能会对原有的文档结构造成 ...

  8. 参悟python元类(又称metaclass)系列实战(一)

    写在前面 之前在看廖雪峰python系列的教程时,对元类的章节一直头大,总在思考我到底适不适合学习python,咋这么难,尤其是ORM的部分,倍受打击:后来从0到1手撸了一套ORM,才稍微进阶了一点理 ...

  9. Aibabelx-shop 大型微服务架构系列实战之技术选型

    一.本项目涉及编程语言java,scala,python,涉及的技术如下: 1.微服务架构: springboot springcloud mybatisplus shiro 2.全文检索技术 sol ...

  10. (二 -3-1) 天猫精灵接入Home Assistant-自动发现Mqtt设备--灯系列 实战

    #本片教程介绍了具体如何实现天猫精灵控制一个灯. 前提: HASS平台 你已经搭建一个可以在公网IP访问到的HASS平台--- 我用的是租了阿里云服务器,买了个域名,ubuntu1604系统 你已经搭 ...

随机推荐

  1. windows服务开发demo

    0.写在前面 windows7开始,windows服务运行在session 0, 用户程序都运行在session x (x >= 1) 而session之间是有隔离的,实践发现是无法在服务中直接 ...

  2. Go微服务开发指南

    在这篇深入探讨Go语言在微服务架构中的应用的文章中,我们介绍了选择Go构建微服务的优势.详细分析了主要的Go微服务框架,并探讨了服务发现与注册和API网关的实现及应用. 关注TechLead,复旦博士 ...

  3. TIER 2: Archetype

    TIER 2: Archetype 扫描 nmap 使用 nmap 进行扫描目标 IP,发现目标是 Windows 服务器,开放 SMB 和 SQL Server 服务. SMB SMB 之前已经接触 ...

  4. 关于MultipartFile

    首先,他来自spring框架,用于处理文件上传的问题 一般来讲,这个接口主要是实现以表单形式上传文件的功能 常用方法: getOriginalFileName:获取文件名+拓展名 getContent ...

  5. 关于npm ERR! 的一个解决方案

    最近在网上找了一个js写的项目,npm下载某些组件总是失败,后经学习了解到了cnpm.使用cnpm时就都可以正常下载,但是下载完成之后程序无法正常启动,所以cnpm下载也是失败的. 后面我经过自己手动 ...

  6. 基于Hive的大数据分析系统

    1.概述 在构建大数据分析系统的过程中,我们面对着海量.多源的数据挑战,如何有效地解决这些零散数据的分析问题一直是大数据领域研究的核心关注点.大数据分析处理平台作为应对这一挑战的利器,致力于整合当前主 ...

  7. BI 工具如何助力市政设计公司实现数字化转型?

    一.前言 近年来,国家出台多个政策文件来鼓励和发展数字化和智能化,如<十四五规划>提出要推进产业数字化转型.<交通强国建设纲要>提出要大力发展智慧交通.上海市发布的<关于 ...

  8. BI 工具助力企业解锁数字化工厂,开启工业智能新视界

    背景 在 2022 年公布的<"十四五"数字经济发展规划>中,政府不断增加对制造业数字化转型的政策支持力度,积极倡导制造企业采用最新技术,提升自动化.数字化和智能化水平 ...

  9. 清华镜像源、阿里镜像源全部失效后怎么办 —— conda 服务器代理配置 —— Jax框架的安装

    相关: conda 服务器代理配置 最近在用anaconda安装Jax框架,发现直接使用官方源下载的速度十分的慢,估计要需20个小时才能下载完成,对于这种情况第一个感觉就是使用镜像源来进行下载. 但是 ...

  10. Python Pillow(PIL 第三方模块)和 cv2 (opencv第三方模块)对图片的 resize 操作 (缩放图片大小)

    PIL 模块的 resize  操作: 1.  从文件中读取图片,然后  resize  大小: import matplotlib.pyplot as plt import numpy as np ...