Closed uinz closed 3 years ago
这是一个取舍和权衡的结果,本质上是在模拟 Algebraic Effects and Handlers
的一个基本应用——Dependent Injection。
react-hooks
和 vue-composition-api
实现的是 sync function hooks
,基本思路是在 call function
前,把 currentDispatcher
或 context container
切换成当前需要的,called function
之后,再重置回 prev
那个。
可以理解为一种局部的 context switching
,useState
, useReducer
内部共享的一个闭包变量,在调用过程中,不断切换。
基本思路演示如下:
let currentDispatcher = null
let useState = (init) => {
return currentDispatcher.useState(init)
}
let useReducer = (reducer, init) => {
return currentDispatcher.useReducer(reducer, init)
}
let withImpl = (f, impl) => {
let prev = currentDispatcher
try {
currentDispatcher = impl
return f()
} finally {
currentDispatcher = prev
}
}
let test = () => {
let [state, setState] = useState(0)
let [state0, dispatch] = useReducer(() => {}, 0)
}
// useState 的 offset/index 递进实现略……
let result = withImpl(() => test(props), {
useState: init => {
return [init, () => {}]
},
useReducer: (reducer, init) => {
return [init, () => {}]
}
})
可以理解为 interface/implements
是在 call-site
里实现,而非 definition
在定义阶段去 implements,并且这个 call-site 是跨 caller 的。也就是说,它像事件冒泡,去向上一个 caller 不断传递,直到找到一个 handlers/implements。这是我们能够编写 custom-hooks
的原因所在,它是跨 caller 的,不管封装多少层函数调用,hooks 访问的都是 withHandlers 所在的层次。
如上,withImpl
是同步的 try-finally 切换,一旦 f() 是异步,整个机制就不成立了。
为了支持异步,我们发现:
async function 执行时,function body 里第一个 await 之前的代码,其实是同步执行的。farrow-hooks without async_hooks 使用了这个机制,嵌套的异步 custom hooks,需要通过特殊的函数包装技巧去处理,见 issue 里这个回复。
async_hooks 可以让我们以 AOP 的方式,在所有 await/promise 的 init/destroy 阶段执行代码。基于这个特性,我们可以自行把 withImpl 里因异步断开的 impl 切换给连接起来。这是目前 farrow-hooks with async_hooks 的做法。
async generator function 支持 await 和 yield,我们也可以基于它实现当前的 farrow-hooks with async_hooks 的机制,但写法更为 verbose
。farrow 在 prototype 阶段,实现过 async generator-based 的 hooks。后面废弃,改为第一种;然后结合 async_hooks 目前 farrow hooks 为第二种。
这类 hooks
实践,只是 Algebraic Effects and Handlers
的极为小的、极为基础的应用,已然能够给我们带来很大的便利。未来可以挖掘更多应用场景,提升开发体验。
关于 Algebraic Effects and Handlers
实战的更多内容,可以访问 koka-lang 的 Effect Handlers 部分,直接感受 first-class 的语言特性,比通过模拟的方式实现的,更加清晰一些。
如果能完整学习和实践一下 koka 语言更佳。
async_hooks
里的黑魔法气息,可能来源于它相比 Algebraic-Effects
而言,有点 ad hoc
。
通过 Algebraic-Effects
,我们可以将 async/await
从带语法的语言特性,降级为 library。异步处理是 Algebraic-Effects
的其中一个应用。
如果我们看到了通用特性,我们了解到 async_hooks
是一种模拟和补充,我们可以从 Algebraic-Effects
视角感受到统一,可以更容易接受 async_hooks
的某些用法。
不管底层如何 ad-hoc,如何 dirty,只要我们封装的抽象,满足某些理论上更干净和一致的特性要求即可。
safe/clean abstraction based on unsafe/dirty layer
十分感谢, 非常好的解决了我的疑惑. 🤙🏻
如果不利用 async_hook 是不是就很难实现当前 hook 的效果?
那么在不提供 async_hook 环境里, 要如何实现 react hook like 且 Promise 友好这套 farrow hook 的机制呢?
其实我是想知道 这套设计的背后还有哪些知识点
蟹蟹