你就是函数响应式编程(FRP)啊?!【附 RxJS 实战】

简介: 什么是 FRP?英文全称是:Functional Reactive Programming,翻译过来就是:函数响应式编程。

image.png

前言



什么是 FRP?


英文全称是:Functional Reactive Programming,翻译过来就是:函数响应式编程


对于函数式编程,我们并不陌生,在 我的 JS 专栏 里面可以找到很多相关文章~~

这里不妨先对函数式编程特性做简要回顾:


  1. 函数是一等公民(意味着可以把函数赋值给变量或存储在数据结构中,也可以把函数作为其它函数的参数或者返回值)
  2. 高阶函数(接受函数作为参数或者返回一个函数的函数)
  3. 没有隐式输入、输出(输入通过函数入参传递,输出通过函数 return 进行返回)
  4. 值的不变性(指在程序状态改变时,不直接修改当前数据,而是创建并追踪一个新数据)
  5. 声明式编程风格,而不是命令式编程风格(关注“是什么”,而不是“做什么”)


用代码举个简单例子:


// 命令式编程
int factorial1(int x) {
    int result = 1;
    for (int i = 1; i <= x; i ++) {
        result *= i;
    }
    return result;
}
// 函数式编程
int factorial2(int x) {
    if (x == 1) return 1;
    return x * factorial2(x - 1);
}


以上代码用于实现计算阶乘。指令式编程,像机器一条条命令一样思考问题,一条条指令告诉计算机该怎么去处理这个问题。


而在函数式编程里面,思想是利用数学方法来思考问题。阶乘的数学表达式是:f(n) = n*f(n - 1)(n > 1)f(n) = 1(n = 1) ,利用递归解决问题。这个过程中基本上没有状态量,只有表达式,也没有赋值语句。


OK,说到这里,对函数式编程有了一个大体的回顾,下面就介绍今天的主角 —— 函数响应式编程


正文



从名字上来看,就是多了 响应 二字,什么是“响应”?

各位一定不陌生!


简答来说就是:当数据发生变动时,会自动触发告知我们:它发生变化了~

Vue.js 底层不就是这种响应式吗?Vue2 通过 definedProperty 的 getter/setter 收集数据变化(依赖收集);


当我们在使用 vue 开发时,只要一有绑定的数据发生改变,相关的数据及画面也会跟着变动,而开发者不需要写关于“如何通知发生变化”的代码,只需要关注发生变化时要做什么事,这就是典型的 Reactive Programming(响应编程) 。


所以,可以大致猜到:函数响应式编程 =  函数式编程 + 响应编程

事实上,它也确实如此~


一图胜千言:

image.png


编程范式关系图(部分)


如图,在声明式编程里,有 2 大家族,分别是函数式编程和数据流编程;数据流编程衍生出响应式编程;而函数响应式编程继承了函数式编程和响应式编程(各自的优点);

  • 响应式编程能在运行时改变事件源(随时间变化的数据输入)的绑定处理,但数据流编程的组织是一开始就确定了的。


事件流


函数响应式编程(FRP) 可以更加有效率地处理事件流,而无需管理状态。


举个栗子🌰

var a = function (b,c) {
    return b + c 
} // a = b + c


其中 a 实际代表 b 与 c 之和,如果 b 或 c 持续不断在被改变,如何触发 a 值也跟着变化呢?


也就是说,上述代码只是一种表达式,并没有指定 a 值的变化依赖 b 和 c 。

可以使用 Reactive 响应式思想将值的关系进行绑定:


//A = B + C
    var reactiveA = $R(function (b, c) { return b + c });
    var reactiveB = $R.state(2);
    var reactiveC = $R.state(1);
    reactiveA.bindTo(reactiveB, reactiveC);
    reactiveA();   //-> 3
    reactiveB(5);  //Set reactiveB to 5
    reactiveC(10); //Set reactiveC to 10
    reactiveA();   //-> 15
<p>


b 和 c 可以看成是被观察者,而 a 作为观察者,随着时间推移,b 和 c 的值不断变化,这种变化将传导到 a;


函数响应式编程(FRP)所做的就是:遍历整个事情流集合,将导致 b 和 c 变化的事情回放,并获得 a 的结果;


【事件流】被称为【被观察者序列】(observable sequences),其实被观察者是一种 Monads。

  • 说明:既然是一种 Monads,就意味着存在延迟计算,即只有当变量真正使用时才去计算,整个链式遍历的过程也是这样。更多


RxJS


在 JS 中,能体现 FRP 的第三方框架是 RxJS。借助 RxJS,我们可以感受函数响应式编程大致是怎样的:


在原生 JavaScript 中

var handler = (e) => {
  console.log(e);
  document.body.removeEventListener('click', handler); // 结束监听
}
// 注册监听
document.body.addEventListener('click', handler);


在 RXJS 中:


Rx.Observable
.fromEvent(document.body, 'click') // 注册监听
.take(1) // 只取一次
.subscribe(console.log);
  • RxJS 是一套由 Observable sequences 来组合 非同步行为事件基础 程序的 JS 库;可以把 RxJS 理解为处理 非同步行为 的 Lodash。


拖拽实战


再演示一个实战栗子🌰:

实现一个简单的拖拽功能;


拖拽功能,可理解为:对 mousedown, mousemove, mouseup 等多个事件进行观察,并相应地改变小方块的位置。


首先分析一下,为了相应地移动小方块,我们需要知道的信息有:


1).  小方块被拖拽时的初始位置;


2).  小方块在被拖拽着移动时,需要移动到的新位置。


数据流如下:

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


问题解析为:在每一次 mousedownmouseup 之间触发 mousemove 时,更新小方块的位置。


// 伪代码(核心)
mousedown.switchMap(() => mousemove.takeUntil(mouseup))
// RxJS 实现拖拽方块
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) // 其中,getTranslate 和 setTranslate 主要作用就是获取和更新小方块的位置
})

codepen 体验地址


如果是用常见命令式风格 JS 原生写:

window.onload = function() {
    var dragCircle = document.getElementById('dragCircle');
    // 获取鼠标点击时在div中的相对位置
    dragCircle.onmousedown = function(ev) {
        var ev = ev || window.event; 
        var relaX = ev.clientX - this.offsetLeft;
        var relaY = ev.clientY - this.offsetTop;
        // 获取当前鼠标位置,减去与div的相对位置得到当前div应该被拖拽的位置
        document.onmousemove = function(ev) {
            var ev = ev || window.event;
            dragCircle.style.left = ev.clientX - relaX + 'px';
            dragCircle.style.top = ev.clientY - relaY + 'px';
        };
        document.onmouseup = function(ev) {
          var ev = ev || window.event;
          document.onmousemove = null;
          document.onmouseup = null;
         }
     }
}

codepen 体验地址


对比二者似乎不难发现:监听事件流的方式更符合对事物变化的通常理解,并且代码的组织方式也更清晰,还有扩展性也更高(有兴趣阅读:RxJS 实战篇(一)拖拽,对于拖拽功能还有更多升级操作);


小结



OK,通过本文,我们了解了函数式编程、响应式编程、函数响应式编程的基本概念、特点、以及相互之间的关系;也借助 RxJS 了解了函数响应式编程的代码实现;

后续还将带来更多关于 RxJS 的相关内容~


如果觉得还不错的话,不如点个👍吧 O(∩_∩)O

我是掘金安东尼,输出暴露输入,技术洞见生活,再会~~


参考:


相关文章
|
存储 缓存 JavaScript
深入浅出 RxJS 核心原理(响应式编程篇)
在最近的项目中,我们面临了一个需求:监听异步数据的更新,并及时通知相关的组件模块进行相应的处理。传统的事件监听和回调函数方式可能无法满足我们的需求,因此决定采用响应式编程的方法来解决这个问题。在实现过程中发现 RxJS 这个响应式编程库,可以很高效、可维护地实现数据的监听和组件通知。
380 0
深入浅出 RxJS 核心原理(响应式编程篇)
|
2月前
|
监控 JavaScript 前端开发
前端的混合之路Meteor篇(六):发布订阅示例代码及如何将Meteor的响应数据映射到vue3的reactive系统
本文介绍了 Meteor 3.0 中的发布-订阅模型,详细讲解了如何在服务器端通过 `Meteor.publish` 发布数据,包括简单发布和自定义发布。客户端则通过 `Meteor.subscribe` 订阅数据,并使用 MiniMongo 实现实时数据同步。此外,还展示了如何在 Vue 3 中将 MiniMongo 的 `cursor` 转化为响应式数组,实现数据的自动更新。
|
3月前
|
存储 移动开发 前端开发
探秘react,一文弄懂react的基本使用和高级特性
该文章全面介绍了React的基本使用方法与高级特性,包括JSX语法、组件化设计、状态管理、生命周期方法、Hooks使用、性能优化策略等内容,并探讨了Redux和React Router在项目中的集成与应用。
探秘react,一文弄懂react的基本使用和高级特性
|
7月前
|
消息中间件 Web App开发 前端开发
前端秘法进阶篇之事件循环
前端秘法进阶篇之事件循环
|
JavaScript 前端开发 API
Vue.js入门指南:从基础到进阶,掌握现代JavaScript框架的核心概念与高级特性(2W字小白教程)
Vue.js入门指南:从基础到进阶,掌握现代JavaScript框架的核心概念与高级特性(2W字小白教程)
187 0
|
前端开发 JavaScript API
RxJS系列01:响应式编程与异步
RxJS系列01:响应式编程与异步
200 0
|
缓存 JavaScript 前端开发
揭开Vue异步组件的神秘面纱
揭开Vue异步组件的神秘面纱
166 0
揭开Vue异步组件的神秘面纱
|
前端开发 JavaScript
React快速入门,一文弄懂react的基本使用和高级特性(一)
在本文中,融合大量案例🌰和动图🕹️进行展示。可以把它当成是 react 的入门宝库,有不懂的语法知识点时或许在这里可以寻找到你的答案并且通过例子运用起来。 叮,废话不多说,下面来开始探索 react 的奥秘吧👏
React快速入门,一文弄懂react的基本使用和高级特性(一)
|
存储 移动开发 前端开发
React快速入门,一文弄懂react的基本使用和高级特性(二)
在本文中,融合大量案例🌰和动图🕹️进行展示。可以把它当成是 react 的入门宝库,有不懂的语法知识点时或许在这里可以寻找到你的答案并且通过例子运用起来。 叮,废话不多说,下面来开始探索 react 的奥秘吧👏
React快速入门,一文弄懂react的基本使用和高级特性(二)
|
前端开发 JavaScript
ReactJS实战之事件处理
ReactJS实战之事件处理
120 0
ReactJS实战之事件处理
下一篇
DataWorks