专栏首页魔术师卡颂代数效应与React

代数效应与React

React核心团队成员Sebastian Markbåge[1]React Hooks的发明者)曾说:我们在React中做的就是践行代数效应(Algebraic Effects)。

那么,代数效应是什么呢?他和React有什么关系呢。

什么是代数效应

代数效应函数式编程中的一个概念,用于将副作用函数调用中分离。

接下来我们用虚构的语法来解释。

假设我们有一个函数getTotalPicNum,传入2个用户名称后,分别查找该用户在平台保存的图片数量,最后将图片数量相加后返回。

function getTotalPicNum(user1, user2) {
  const num1 = getPicNum(user1);
  const num2 = getPicNum(user2);

  return picNum1 + picNum2;
}

getTotalPicNum中,我们不关注getPicNum的实现,只在乎“获取到两个数字后将他们相加的结果返回”这一过程。

接下来我们来实现getPicNum

"用户在平台保存的图片数量"是保存在服务器中的。所以,为了获取该值,我们需要发起异步请求。

为了尽量保持getTotalPicNum的调用方式不变,我们首先想到了使用async await

async function getTotalPicNum(user1, user2) {
  const num1 = await getPicNum(user1);
  const num2 = await getPicNum(user2);

  return picNum1 + picNum2;
}

但是,async await是有传染性的 —— 当一个函数变为async后,这意味着调用他的函数也需要是async,这破坏了getTotalPicNum的同步特性。

有没有什么办法能保持getTotalPicNum保持现有调用方式不变的情况下实现异步请求呢?

没有。不过我们可以虚构一个。

我们虚构一个类似try...catch的语法 —— try...handle与两个操作符performresume

function getPicNum(name) {
  const picNum = perform name;
  return picNum;
}

try {
  getTotalPicNum('kaSong', 'xiaoMing');
} handle (who) {
  switch (who) {
    case 'kaSong':
      resume with 230;
    case 'xiaoMing':
      resume with 122;
    default:
      resume with 0;
  }
}

当执行到getTotalPicNum内部的getPicNum方法时,会执行perform name

此时函数调用栈会从getPicNum方法内跳出,被最近一个try...handle捕获。类似throw Error后被最近一个try...catch捕获。

类似throw ErrorError会作为catch的参数,perform namename会作为handle的参数。

try...catch最大的不同在于:当Errorcatch捕获后,之前的调用栈就销毁了。而handle执行resume后会回到之前perform的调用栈。

对于case 'kaSong',执行完resume with 230;后调用栈会回到getPicNum,此时picNum === 230

再次申明,try...handle的语法是虚构的,只是为了演示代数效应的思想。

总结一下:代数效应能够将副作用(例子中为请求图片数量)从函数逻辑中分离,使函数关注点保持纯粹。

并且,从例子中可以看出,perform resume不需要区分同步异步。

代数效应在React中的应用

那么代数效应React有什么关系呢?最明显的例子就是Hooks

对于类似useStateuseReduceruseRef这样的Hook,我们不需要关注FunctionComponentstateHook中是如何保存的,React会为我们处理。

我们只需要假设useState返回的是我们想要的state,并编写业务逻辑就行。

function App() {
  const [num, updateNum] = useState(0);
  
  return (
    <button onClick={() => updateNum(num => num + 1)}>{num}</button>  
  )
}

如果这个例子还不够明显,可以看看官方的Suspense Demo[2]

DemoProfileDetails用于展示用户名称。而用户名称异步请求的。

但是Demo中完全是同步的写法。

function ProfileDetails() {
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

代数效应与Generator

React15React16,协调器(Reconciler)重构的一大目的是:将老的同步更新的架构变为异步可中断更新

异步可中断更新可以理解为:更新在执行过程中可能会被打断(浏览器时间分片用尽或有更高优任务插队),当可以继续执行时恢复之前执行的中间状态。

这就是代数效应try...handle的作用。

其实,浏览器原生就支持类似的实现,这就是Generator

但是Generator的一些缺陷使React团队放弃了他:

  • 类似asyncGenerator也是传染性的,使用了Generator则上下文的其他函数也需要作出改变。这样心智负担比较重。
  • Generator执行的中间状态是上下文关联的。

考虑如下例子:

function* doWork(A, B, C) {
  var x = doExpensiveWorkA(A);
  yield;
  var y = x + doExpensiveWorkB(B);
  yield;
  var z = y + doExpensiveWorkC(C);
  return z;
}

每当浏览器有空闲时间都会依次执行其中一个doExpensiveWork,当时间用尽则会中断,当再次恢复时会从中断位置继续执行。

只考虑“单一优先级任务的中断与继续”情况下Generator可以很好的实现异步可中断更新

但是当我们考虑“高优先级任务插队”的情况,如果此时已经完成doExpensiveWorkAdoExpensiveWorkB计算出xy

此时B组件接收到一个高优更新,由于Generator执行的中间状态是上下文关联的的,所以重新计算y时无法复用之前已经计算出的x,需要重新计算。

如果通过全局变量保存之前执行的中间状态,又会引入新的复杂度。

更详细的解释可以参考这个issue[3]

基于这些原因,React没有采用Generator实现协调器

代数效应与Fiber

Fiber并不是计算机术语中的新名词,他的中文翻译叫做纤程,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。

在很多文章中将纤程理解为协程的一种实现。在JS中,协程的实现便是Generator

所以,我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。

React Fiber可以理解为:

React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态

其中每个任务更新单元为React Element对应的Fiber节点

参考资料

[1]

Sebastian Markbåge: https://github.com/sebmarkbage/

[2]

Suspense Demo: https://codesandbox.io/s/frosty-hermann-bztrp?file=/src/index.js:152-160

[3]

这个issue: https://github.com/facebook/react/issues/7942#issuecomment-254987818

本文分享自微信公众号 - 魔术师卡颂(gh_bc4b6cbdf9ad),作者:卡颂

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 唐朝人更懂React

    你投靠在礼部当差的大哥已有数年,在烟火处谋得一份差事。这几日正是一年中最忙碌的时候。

    公众号@魔术师卡颂
  • timeTaken

    使用console.time() 和 console.timeEnd() 测量函数执行的时长。

    公众号@魔术师卡颂
  • 一次性清除“知乎”所有DIV

    知乎首页是React写的,我们可以覆写React.createElement方法,在运行时将所有div节点渲染为React.Fragment。

    公众号@魔术师卡颂
  • Quartz.net官方开发指南 第二课:Jobs And Triggers

    正如前面所提到的那样,通过实现IJob接口来使你的.NET组件可以很简单地被scheduler执行。下面是IJob接口: namespace Quartz ...

    张善友
  • 曼哈顿距离与切比雪夫距离及其相互转化

    本文只讨论二维空间中的曼哈顿距离与切比雪夫距离 曼哈顿距离 定义 设平面空间内存在两点,它们的坐标为 则 即两点横纵坐标差之和 煮个栗子 ? 如图所...

    attack
  • python基础

    学习前准备,了解 基础环境部署,工作原理,发展和历史 pycharm,notepad++等工具 交互模式 命令行模式 废话不多说,直接上分 第一个脚本 打印 l...

    企鹅号小编
  • MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN

    在Membership系列的最后一篇引入了ASP.NET Identity,看到大家对它还是挺感兴趣的,于是来一篇详解登录原理的文章。本文会涉及到Claims...

    用户1153966
  • 软件测试经验图谱硬技能之系统知识

    上周的华为发布会,大家应该都听说了吧,这么受关注,主要是因为发布了万众期待的鸿蒙操作系统,不过随后就有人质疑它使用微内核的性能问题了,技术性分析的文章可以看看大...

    sylan215
  • 厉害了!华为将发布国产编程语言,打破国外垄断!

    据微博博主@IT工程师 称,华为正在研发新的自主编程语言仓颉,领头人是南京大学的计算机科学与技术系博士生导师,冯新宇。

    Leetcode名企之路
  • opencv 5 -- 图像阈值

    二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓

    wust小吴

扫码关注云+社区

领取腾讯云代金券