大家好,我是 anuoua,之前我写了个前端框架 Unis,底层是 Vue 3 的运行机制,可以查看这篇文章了解。
最近一直在重构,最终利用 React 机制重写了,用 Fiber & Tree 复合结构实现了虚拟 DOM,同时支持了时间分片,实现了可中断执行的架构,最重要的是解决了 React 的缺陷(重大创新),最终实现的 Unis 让 Dan 直呼 React 药丸,有图为证!
Reactivity 的缺陷
之前 Unis 是借助 Vue 3 的 @vue/reactivity 包实现的,这个响应式机制除了一些众所周知的限制之外,我比较在意的是它会篡改数据对象,在一些深度的复杂数据下,会导致一些奇奇怪怪的心智负担,虽然 Vue 3 机制下的 Unis 已经很好用了,但是不完美总是让人觉得有点遗憾。
基于 React 机制的增强方案
由于上述的缺点,Reactivity 方案必然是要放弃了,接下来只剩 React 方案了,首先要说明我的目标不是复刻 React,网上有很多的 React Like 框架,没有做任何改进,再做一个对我来说意义不大,至少得干点什么。
React 的缺点并不多,我认为现在 React 的缺点最大的莫过于 Hooks 的心智负担问题,显然 Facebook 自己也认识到了,所以在上次 React 分享会上,黄弦介绍了 React Forget 编译器,让大家摆脱手写 memo 和 deps。
所以我的目标也是解决 React Hooks 的心智负担(或者说改变 React 的心智模型),同样也是借助编译手段,但是和 React Forget 这种重量级编译不同的是,我的编译手段非常薄,而且很通用!
全新的组件形态
我之前写过一篇文章,提出过一种 React 的全新组件形态,高阶函数组件形态,这种形态天生没有 memo 和 deps 的问题,组件函数形成闭包后用于存放组件数据,无需像 React 一样一直重刷组件函数,也就不需要为 callback 函数进行 memo 操作,当然也不再需要处理 deps 问题。
function App() {
// 组件函数只运行一次
const [hello, setHello] = useState('hello')
const handleClick = () => {}
return () => ( // 返回一个函数,组件更新的时候再次运行
<div onClick={handleClick}>{hello}</div>
)
}
但是这种形态有缺陷,基础类型的值(上述代码中 hello 变量)无法更新,详细可以看这篇文章。没有好的办法可以解决,但是后来我想了想,办法还是有的,那就是上编译手段。
怒上编译手段
首先说一个无法改变的事实,那就是目前整个业界写 web 应用,根本逃不过编译!所以不需要排斥编译手段,毕竟 JSX 也是一个逃不过的编译,那么如果把这个作为前提,用编译手段是否能够解决上述值的更新问题呢?
来,上例子,这是全新形态下的 React 组件,问题在哪儿?
function App() {
const [hello, setHello] = useState('hello')
return () => (
<div>{hello}</div>
)
}
首先要注意到组件的形态改变后,心智模型也变了,不要再用 React 的思维去看这个组件,useState 只会执行一次哦,那么意味着 hello 永远等于 'hello',组件永远不会更新,如何才能更新这个值呢?这里介绍一种创新的 callback 重赋值法,你可能没见过这种用法。
function App() {
let [hello, setHello] = useState('hello', ([$0, $1] }) => { hello = $0; setHello = $1 })
return () => (
<div>{hello}</div>
)
}
function useState(init, cb) {
let state = init
const setValue = (newValue) => {
state = newValue
cb([state, setValue])
triggerComponentRender();
}
return [state, setValue]
}
代码应该很清晰了,const 改成 let,在 useState 后面补上一个回调函数,在 setHello 执行的时候,hello 的值就会更新,完美解决!
但是,这样太丑太麻烦了,谁会愿意去补这个 callback 呢?
你不愿意,那就交给编译啊!哈哈!
callback 补充的规则非常简单,但是框架的 API 不一定是如上述这么用的,我想了想大概有这么几种:直接赋值、数组解构、对象解构。
let a = useContext(someCtx, $0 => a = $0)
let [a, setA] = useState('a', ([$0, $1]) => { a = $0; setA = $1 })
let { b, c } = useSomeThing('b', ({ b: $0, c: $1 }) => { b = $0; c = $1 })
好像也不多,而且解析规则相比 React Forget 编译器来说简单到爆炸,那么我们完全可以通过 babel 或者 rollup 插件,把它给补全了!
插件我已经写好了 rollup-plugin-reassign,欢迎 Start ,这个插件是通用的,不只能用于 Unis 框架,任何需要用到 callback 重赋值策略的地方,都可以用!
组件的完美形态
经过编译手段,我们的组件变的非常简约,无需各种 memo 处理,比如下面的 handleClick ,引用并不会改变,div 就可以跳过更新,默认就可以达到性能优化的目的。
简直完美,再也不需要手写烦人的 memo 和 deps,是我们熟悉的心智模型!
function App() {
let [hello, setHello] = useState('hello') // useState('hello', ([$0, $1] }) => { hello = $0; setHello = $1 })
const handleClick = () => {}
return () => <div onClick={handleClick}>{hello}</div>
}
众所周知在 React 下, memo 处理很头疼,像 lodash 这种优秀的库都没办法直接使用,简简单单一个 debounce 都被各路神仙玩出花了,这个算是 “React税” 吧,要是 Vue 推这种心智模型范式,早就被骂死了,哈哈。
有人说了可以等 React Forget ,但是我不是很看好,因为 js 过于动态化,有些情况靠分析 ast 是分析不出来的,大概可以解决 80% ~ 90% 的问题,嗯~貌似也够了。
function App() {
const [hello, setHello] = useState('hello')
const handleClick = useCallback(
() => {},
[someValue]
)
return () => <div onClick={handleClick}>{hello}</div>
}
Unis 框架
最后,这个全新的框架,就是 Unis,废话不多说,上链接,赏个 Star 吧。
Github: github.com/anuoua/unis
尝鲜:stackblitz.com/edit/vitejs…
- 特点
- 完美解决 memo deps 问题
- Fiber & Tree 复合结构
- 时间分片
- 高阶函数组件
- 去除合成事件
- 内置 class 支持,无需再使用 classnames
- ts 支持良好
- 性能优异
- 体积小巧
- 缺点
- 生态无
如果你对 React 同样抱有不满,不妨试试 Unis,我自己用的时候觉得很爽,比 React 爽,在一些不重度依赖生态的小工程中用 Unis ,体验已经非常好了。
这个框架我会一直维护下去,后续的一些特性比如 Suspense 等,一步步都会实现,时间问题。
感觉阅读!