还在等 React Forget ?你可能并不需要它!

1,989 阅读5分钟

大家好,我是 anuoua,之前我写了个前端框架 Unis,底层是 Vue 3 的运行机制,可以查看这篇文章了解。

最近一直在重构,最终利用 React 机制重写了,用 Fiber & Tree 复合结构实现了虚拟 DOM,同时支持了时间分片,实现了可中断执行的架构,最重要的是解决了 React 的缺陷(重大创新),最终实现的 Unis 让 Dan 直呼 React 药丸,有图为证!

image.png

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 这种重量级编译不同的是,我的编译手段非常薄,而且很通用!

image.png

全新的组件形态

我之前写过一篇文章,提出过一种 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 等,一步步都会实现,时间问题。

感觉阅读!