学习Rxjs是两个月前的事了,但没有用到在一个实际需求上。近日收到一个需求,正好是一个可以抽象为由数据流驱动的应用,于是我欣然用Rxjs结合React写之。

需求说明

需求原始说明为:

60个英文单词被分为三个组系,每个组系包括2-6个单词一组的五组单词。在测试中,测试单词以1词/秒的速率依次呈现在电脑屏幕上,被试大声朗读屏幕上的单词,当一组单词呈现结束时,屏幕上出现与该组数目相等的问号,提示被试用刚才呈现的单词造句。 两个一组时,屏幕上以1词/秒的速率依次出现“price”,“week”,两个单词后会出现??,受试用price造句,造完句后点下鼠标,屏幕上出现一个问号,受试用week造句,之后点鼠标进入三个一组,屏幕上以1词/秒的速率依次出现“bird”,“game”,“ star”,三个单词后会出现???,受试用bird造句,造完句后点下鼠标, 屏幕上出现两个问号,受试用game造句,再点下鼠标, 屏幕上出现一个问号,受试用star造句,之后点鼠标进入四个一组…以此类推。

全程录音,计算机记录受试每个单词造句所用时间。

需求原始说明可能有些不易看明,经过讨论,我归总如下:

  1. 数据格式为:(word.组系序号.组单词数.组内序号)

    Test1 Test2 Test3
    word.1.2.1 word.2.2.1 word.3.2.1
    word.1.2.2 word.2.2.2 word.3.2.2
    word.1.3.1 word.2.3.1 word.3.3.1
    word.1.3.2 word.2.3.2 word.3.3.2
    word.1.3.3 word.2.3.3 word.3.3.3
    ... ... ...
    word.1.6.6 word.2.6.6 word.3.6.6

    由此,抽象为以下数据类型:

    // 代表一个组系
    interface TestItem {name: string; // 组系名,如:Test1, Test2, Test3/*二维数组表示的组系内数据项[word.1.2.1, word.1.2.2][word.1.3.1, word.1.3.2, word.1.3.3]...*/wordGroups: string[][];
    }
    复制代码

    并将数据放入全局store:

    class Store {// 组系items: TestItem[] = [];// 每组最小单词数minWordsCount = 2;// 每组最大单词数maxWordsCount = 6;// 实验开始前倒计时秒数countdown = 5;
    }
    复制代码
  2. 流程:

    1. 倒计时

    2. 对每个组系:

      1. 对每一组:

        1. 依次显示单词,每个单词显示一秒
        2. 对每个单词:
          1. 显示此组单词数 - i个问号
          2. 如果点击事件发生,子流程结束
        3. 子流程结束
      2. 子流程结束

    3. 流程结束

读者有意,可以试想一下普通做法如何实现。

数据流抽象

为了方便一些操作,我封装了一个immediateInterval,因为interval不是即时开始发射数据的:

function immediateInterval(period?: number | undefined,scheduler?: SchedulerLike | undefined
): Observable<number> {return new Observable(subscriber => {subscriber.next(0);const interval$ = interval(period, scheduler);const next = subscriber.next; subscriber.next = function(this: Subscriber<number>, i: number) {next.bind(this)(i + 1);}.bind(subscriber);interval$.subscribe(subscriber);});
}
复制代码

由上节的流程,得到如下数据流:

  1. 倒计时:使用immediateInterval发出store.countdown个值,每个值携带当前倒计时数,然后

  2. 迭代item of store.items

    1. 迭代group of item.wordGroups:

      1. 显示单词:使用immediateInterval发出group.length个值,每个值携带当前单词,然后

      2. 迭代for(let i = 0; i < group.length; i++)

        1. 发出值,携带group.length - i(问号数)
        2. 订阅全局event busclick事件,当click激发时,continue此迭代

数据流实现

以下代码的一些设计点:

  • 使用event busemit实现流程钩子,用来向外报告进行到了哪一步
  • 使用async await语法实现异步迭代:
    async () => {for(...){await new Promise(async (resolve) => {someAsyncCode(// 结束此Promise,以continue该for循环resolve(); )})}
    }
    复制代码
// event bus
const bus = new EventEmitter();const stream$ = new Observable<| { type: "倒计时"; count: number }| {type: "显示单词";word: string;}| {type: "显示问号";count: number;}
>(subscriber => {immediateInterval(1000).pipe(take(store.countdown + 1)).subscribe(i => {if (i !== store.countdown)subscriber.next({ type: "倒计时", count: store.countdown - i });},console.error,async () => {bus.emit("start", { type: "full" }); // 全程开始for (let item of store.items) {await new Promise(async (resolve, reject) => {bus.emit("start", { type: "item", item }); // 一组系开始for (let groupIndex = 0;groupIndex < item.wordGroups.length;groupIndex++) {const group = item.wordGroups[groupIndex];// 一组开始bus.emit("start", { type: "group", item, groupIndex }); // 一组开始await new Promise(async (resolve, reject) => {// 显示单词immediateInterval(1000).pipe(take(group.length)).subscribe(i => {subscriber.next({ type: "显示单词", word: group[i] });},console.error,() => {// 显示问号rxjs.timer(1000).subscribe(async () => {for (let i = group.length, wordIndex = 0;i > 0;i--, wordIndex++) {// 开始造句bus.emit("start", {type: "sentence",index: wordIndex,item,groupIndex});await new Promise(async (resolve, reject) => {subscriber.next({ type: "显示问号", count: i });const onClick = () => {resolve(); // next 问号bus.removeListener("click", onClick);};bus.addListener("click", onClick);});// 造句结束bus.emit("end", {type: "sentence",index: wordIndex,item,groupIndex});}resolve(); // next group});});});bus.emit("end", { type: "group", item, groupIndex }); // 一组结束}bus.emit("end", { type: "item", item }); // 一组系结束resolve(); // next test});}bus.emit("end", { type: "full" }); // 全程结束subscriber.complete();});
});
复制代码

连接到React组件(Hooks):

const Play = () => {const action = useObservable(() => stream$);let comp = null;if (!action) {comp = null;} else if (action.type === "倒计时") {comp = ...;} else if (action.type === "显示单词") {comp = ...;} else if (action.type === "显示问号") {comp = ...;}return ...;
};
复制代码

总结

Rxjs使得一些事情更容易(:

涉及到的一些库

rxjs

rxjs-hooks(rxjs与react hooks的结合库)

wolfy87-eventemitter(浏览器端的高效的EventEmitter)

转载于:https://juejin.im/post/5c54f00ef265da2de165761b

Rxjs初体验:制作语音测试工具相关推荐

  1. 体验Vs2005 beta2 测试工具

    在Vs2005中加入了单元测试工具,使用与NUnit差不多.但功能更加丰富了,而且使用更加方便,有利于项目的协调工作.而且还支持调试测试,(不知道NUnit支不支持,我是没用过.)方便我们调试出错代码 ...

  2. memsql 落地mysql_MemSQL初体验 - (2)初始化测试环境

    2.配置测试环境 创建一个用户,方便后续使用: MemSQL> grant all on *.* to jss identified by "jss"  with grant ...

  3. MemSQL初体验 - (2)初始化测试环境

    2.配置测试环境 创建一个用户,方便后续使用: MemSQL> grant all on *.* to jss identified by "jss"  with grant ...

  4. NSIS 之初体验 制作打印机i5100windows安装程序

    1.简介 NSIS(Nullsoft Scriptable Install System)是一个开源的 Windows 系统下安装程序制作程序.它提供了安装.卸载.系统设置.文件解压缩等功能.这如其名 ...

  5. CentOS 初体验六:登录工具PuTTY使用

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78961584 本文出自[赵彦军的博客] PuTTY 简介 PuTTY是一个Teln ...

  6. 【第二趴】uni-app开发工具(手把手带你安装HBuilderX、搭建第一个多端项目初体验)

    文章目录 写在前面 HBuilderX HBuilderX 优势 HBuilderX 安装 uni-app 初体验 写在最后 写在前面 聚沙成塔--每天进步一点点,大家好我是几何心凉,不难发现越来越多 ...

  7. FlashCache初体验

    FlashCache初体验 注意: 测试用的是CentOS6.5 内核版本2.6.32-431.el6.x86_64 步骤: 上传CentOS6.5做本地yum源,安装以下包. yum install ...

  8. 【聆思CSK6视觉AI开发套件试用】CSK6系列头肩识别初体验

    本篇文章来自极术社区与聆思科技组织的CSK6 视觉AI开发套件活动,更多开发板试用活动请关注极术社区网站.作者:張弩拔劍 背景 前言 聆思CSK4002以先进的AI算法, 出色的性价比, 以及优越的头 ...

  9. 腾讯内部测试软件,腾讯性能测试工具——PerfDog使用初体验

    官网地址:https://perfdog.qq.com/ 使用说明:https://perfdog.qq.com/support 测试机型:锤子坚果pro2s (前几天得知我浩哥上了老赖名单,现在看着 ...

  10. OCR 工具tesseract初体验

    OCR 工具tesseract初体验 @(工具使用)[工具使用, python] OCR即图片上文字识别 安装tesseract github地址 tesseract是一个命令行程序,后面安装的pyt ...

最新文章

  1. ERICA:提升预训练语言模型实体与关系理解的统一框架
  2. Windows中的命令行提示符里的Start命令执行路径包含空格时的问题
  3. NioEventLoop 的实例化过程
  4. 注解配置声明式事务控制解析
  5. 【ubuntu-qt-dlib】 配置问题 (二) terminate called after throwing an instance of 'dlib::image_load_error'
  6. node 版本升级_Node-RED: 自动化事件触发工具的安装与介绍
  7. php node.js django,Vue.js和Django搭建前后端分离项目示例详解
  8. django 1.8 官方文档翻译:7-2 管理操作
  9. 男性加入防晒大军 购买遮阳伞比例同比增长23.54%
  10. delphi idtcpclient 发送十六进制00_25656红单足球预测 中超 20:00 山东鲁能泰山 VS 大连人...
  11. [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)...
  12. 添加游戏到游戏浏览器中的小工具
  13. 别用Date了,Java8新特性之日期处理,现在学会也不迟!
  14. maven安装及配置
  15. 偏微分方程数值解的matlab程序,偏微分方程数值解法的MATLAB源码
  16. vue v-for循环表格 希望第四个<th>或<td>标签自动换到下一行应该怎么做?
  17. 如何在线下载哔哩哔哩上的视频
  18. 智能暖风机——8.云端控制
  19. Control Egress TCP Traffic
  20. async-profiler的使用与RocketMQ性能优化案例

热门文章

  1. Linux-Windows 端口转发
  2. Java中单例设计模式总结
  3. .Net MVC缓存
  4. WPF实现竖向排列并换行显示
  5. [原创]spring及springmvc精简版--继承数据源,声明式事物
  6. Bootstrap如何禁止响应式布局 不适配
  7. 4月18日会议总结(整理—祁子梁)
  8. sharepoint 在Visual Studio设置其他页面的加载标签
  9. 链表排序 Sort List
  10. C/C++心得-从内存开始