fre2 will come soon!

微信扫一扫,分享到朋友圈

fre2 will come soon!

哇咔咔,经过长达两周的时间,我终于!把 react 搞了两年的新 feature 给抄好了!

新 feature:可恢复异常

concurrent 其实主要包括两部分,一个是「时间切片(time slicing)」,这个大家都耳熟能详了,它解决的是「js行为」对「浏览器行为」的阻塞……比较常见的是 css 动画,有了时间切片,可以让 css 丝毫顺滑

而另外一个就是「可恢复异常(resumable exception)」,这个名字是我取的,它来自 algebraic effects,属于一种学术概念,说白了就是可以同步地写代码,然后其他逻辑又不会被阻塞

它实现同步 API 的原理是使用 throw catch,解决的自然是「throw-catch」对其他「js 行为」的阻塞

这里有几篇文章,可以帮助理解:

伊撒尔:fre2 到底在搞啥:再谈 algebraic effects

Algebraic Effects for the Rest of Us

One-shot Delimited Continuations with Effect Handlers

因为属于学术概念,所以从文字上还是有很大的心智负担的,因为实际上落实到具体的语言和框架上,差距是很大的,接下来我会用一个 demo 来阐述清楚,fre2 / react17 中是怎样实现的

const reducer = (action, state) => {
switch(action.type){
case '@throw'
return wrapPromise()
}
}
const App = () => {
const [resource, dispatch] = useReducer(reducer, null)
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => setCount((c) => c + 1), 1000)
return () => clearInterval(id)
}, [])
return (
<div>
<button onClick={() => dispatch({ type: '@throw' })}>CLICK ME</button>
<pre>{JSON.stringify({ count }, null, 2)}</pre>
{resource ? resource.read() : 'Initial state'}
</div>
)
}
const wrapPromise = () => {
const promise = new Promise((r) => setTimeout(r, 3000)).then(() => 'finished'))
let result
promise.then((value) => {
result = { type: 'success', value }
})
return {
read() {
if (!result) throw promise
return result.value
},
}
}

这段代码有一点长,这个是 fre 最终敲定的 API,已经比 react17 简单太多了(react 需要 Suspense,useTransiton 等)

我们来逐步分析一下:

const wrapPromise = () => {
const promise = new Promise((r) => setTimeout(r, 3000)).then(() => 'Done'))
let result
promise.then((value) => {
result = { type: 'success', value }
})
return {
read() {
if (!result) throw promise
return result.value
},
}
}

首先这个函数,是属于库作者自己封装的,它和 lazy 函数是一样的,它做的事情很简单,就是在合适的时机 throw promise

然后我们需要一个 state 去控制它是否进行 throw

const [resource, dispatch] = useReducer(reducer, null)
<button onClick={() => dispatch({ type: '@throw' })}>CLICK ME</button>
{resource ? resource.read() : 'Initial state'}

这三行很重要的,首先是使用了 useReducer,因为在 fre 中,useReducer 是一个底层 API,它不如 useState 常用,所以可以用来做拓展

注意,可恢复异常这个功能,我是给库作者准备的,他们可以用来方便的封装出 suspense/lazy/useTranstion 等 API

不提倡普通用户在组件内肆意 throw,很容易失控

然后,我们需要一个约定/标记,去标记这个 state 是普通 state 还是需要控制 throw 的 state

dispatch({ type: '@throw' })

这里用了一个约定,就是 dispatch 的 type 使用了 ‘@’ 前缀……这种约定我是不太喜欢说实话,但是现在还没找到更好的方式

艾特的符号来自其他语言的处理,熟悉 algebraic effects 应该不会陌生

至此,我们已经知道了大概的模型,那就是,设置一个特殊的 state 去控制这个组件是否 throw promise

然后最重要的一步就是……

组件需要异步 render 两次

其中一次是有 state 的,然后去 throw promise,另外一次是没有 throw 的,用来更新被阻塞的 js 逻辑

反正最终的效果就是,通过标记一个 state,然后组件渲染两次,最终实现了 throw-catch 和其他 js 行为的并发运行……

在 react 中,除了 API 不一样,react 选择了不对外暴露 API,而是自己封装组件,但内部的实现是一样的……你可以把 useTransition 当作一个 state 的标记

以上,这是 fre2 的主要 feature,除了这个 feature,距离 fre2 正式版,还需要一些事情:

  1. 重构 scheduler,减少 message channel 的浪费
  2. 重构 useReducer 和内部优先级计算,争取继续压缩代码
  3. jsx2
  4. webpack5 的 ie8 兼容

这些都是小 case 啦,哈哈哈哈,等我实在搞的差不多了,大概国庆发 rc 版本哈

另外,fre2 除了「可恢复异常」这个主要 feature,还有一些非核心的改动

  1. 全面 typescript

包括 demo,test,全部都换成了 ts,说实话对我个人来说,ts 对我的作用不大,但随波逐流嘛,有了 ts 可以收到更多的 pr

另外,删掉了 babel 也让我感到非常爽,缺点是 ts 暂时不支持 jsx2

2. 后置 state 更新

因为是异步渲染,所以 state 马上更新和最后更新是无所谓的,但是后置 state 代码会更少,心智负担也会小很多

3. 新的 effect 调度

现在 effect 和时间切片可以共用一个调度器了,都不会造成阻塞,性能更好,代码更少

总结

前阵子 vue 发布 3.0,前前阵子 react 发 16.7,然后俺马上也要发 fre2 啦,2020 年真的很奇妙,框架一个接一个得发版

当然,每个框架都有它独特的思考和权衡,可以感受到,像 fre 中时间切片也好,可恢复异常也好,这些阻塞行为,其实在 vue 等微任务框架中会更加明显,但他们并不 care

同理,在 react 中还有事件调度这个大头,我也不是很 care

所以我才说,框架作者的本职工作就是「瞎扯」,我们彼此都有不同的理解,然后再去进行嘴遁,如是而已

fre 有很多我自己的追求,之后有机会我会写一篇文章,说一下我的权衡的角度和理由

最后放一下 gitHub 地址,感兴趣的可以看看,记得一定要跑 demo,不然很难理解的:

yisar/fre github.com

米娜再贱~

微信扫一扫,分享到朋友圈

fre2 will come soon!

NASA“毅力号”开启火星之旅 或助人类找到古代“化石”

上一篇

Yii2学习笔记

下一篇

你也可能喜欢

fre2 will come soon!

长按储存图像,分享给朋友