前端框架 Unis 带着它的生态来了!

5,944 阅读7分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

大家好,我是 anuoua ,我写了一个框架叫 unis,之前没有正式发布,但是我在之前的文章中或多或少提到了它,可能部分掘友看到过。半年过去了,unis 仅仅收获了 24 个 star,相当的冷清,可是想到我在半年内对它做的各种改进和优化,以及搭配我自己的使用体验,觉得有点可惜。所以今天我要正式发布 unis,为它打个广告。

Github: Unis 欢迎点 Star

image.png

背景

2022 年的前端框架已经空前的成熟,React vs Vue vs Svelet vs ...,成熟的同时框架的设计也开始趋同,而这一切都是源自 React Hook 的诞生,改变了整个前端。

如今 React 社区空前强大,开源社区的活跃度令其他框架羡慕。这其中就诞生了一堆 react like 的开源框架,比较出名的有 preact 和 solid.js。

image.png

preact 是 react 的精简复刻,得益于它诞生的时间(早)和小巧的体积、更优的性能也赢得了一些用户,遗憾的是它后续没有进行 fiber 改造,停留在人们脑海里 react 备胎的认知中。

solid.js 则是完全改变了运行机制,它通过编译手段将 jsx 直接编译成 DOM,放弃了虚拟 DOM,使得性能直逼原生 js ,牛皮!但是 solid.js 的遗憾是它只支持 jsx 的子集,一些更加灵活的 jsx 用法受限,本质上是因为 solid.js 把 jsx 当模版用,一小部分 jsx 的用法依赖运行时,编译阶段无法分析完全。

我们发现每个框架卖点都不相同,各有优缺点,完美的框架并不存在,想要代替 react 更是不切实际。

在这种前端框架百花齐放的背景之下,再复刻一个框架的意义就显得不是很合理了,怎么着你也得有点新东西才行。

Unis 同样是一款 react like 框架,那么 unis 带来了什么新思路呢?

起源

最早,要写 unis 的想法源自于我个人对 react hook 的不满,react hook 的心智负担很重,哪怕我现在能够很熟练的运用这套 API,但是当我要封装一个复杂 hook 的时候,我仍然觉得头疼。lodash 大量函数不能直接用让我很不爽,所以萌生了写一个和 React 类似但是能够显著降低心智负担的框架的想法。

于是我开始调研各种框架的实现方式,比如我实验性质的写了一个基于 @vue/reactive 并且融合了 vue & react 的合体框架,函数组件 + JSX + composition API 的组合简直是异端,vue 粉丝看了锤腿,react 粉丝看了咬牙,愿世界和平。

虽然响应式已经足够好了,但是我意识到基于 proxy 的响应式它会带来总是需要判断原始数据的心智负担,比如说嵌套数据下,获取到的内部对象是经过 proxy 包装的,这我还是有点不舒服。

难道就没有两全其美的办法吗?

有,于是,一种搭配轻量编译的全新组件 API 诞生了!

Unis 核心

我之前写过文章专门介绍过这种全新的 API ,但是没几个人看,借此机会,我需要重新解释一下 unis 的核心特征。

核心特征一:unis 的组件是高阶函数形态

它的特点是可以隔离组件 API 和视图区 jsx 的执行,组件函数仅执行一次,组件函数返回的高阶函数可以在需要的时候多次执行返回最新的视图给组件,组件的闭包可用于存放组件数据。

这样就完全避免了 react 中由于多次执行组件函数导致的闭包问题,react hook 的心智负担也就不存在了,再也不需要 useCallback ,无需再处理烦人的 deps,完美?

const App = () => {
    let [msg, setMsg] = useState('hello'); // 执行一次
    const handleClick = () => {} // 不再需要 useCallback
    return () => ( // 创建/更新,可执行多次
        <div onClick={handleClick}>{msg}</div>
    )
}

核心特征二:unis 特殊的 API 编译策略

细心的你可能已经发现了,msg 是没有办法靠 setMsg 更新的 (当然也不能直接赋值,这里涉及到自定义 hook 和变量依赖问题,以后有机会展开讲)。

所以我们把代码改成这样:

// useState 的实现
const useState = (value, cb) => {
    let state = value;  
    const setValue = (newValue) => {
        state = newValue;
        cb([state, setValue]);
        triggerRender(); // 触发组件更新
    }
    return [state, setValue]
}

const App = () => {
    let [msg, setMsg] = useState(
        'hello',
        ([$0, $1]) => { msg = $0; setMsg = $1 }
    );
    const handleClick = () => {}
    return () => (
        <div onClick={handleClick}>{msg}</div>
    )
}

这样 msg 就可以获得更新了,但是区别是 useState 它有第二个参数,这个 callback 函数需要我们自己补上...。

这太扯淡了,我用个 useState 居然还要补这么一长串的代码,累不累!

所以这部分咱们就交给编译了,这个编译策略叫做 callback reassign(我自己取的名),我已经写好插件了 rollup-plugin-reassign,您直接用便是。

核心总结

现在的 unis 已经可以完全避免 react hook 的心智负担,同时也不会有响应式的槽点,不知道这样的核心设计有没有打动你?

相比 React Forget 那样的重编译,unis 通过高阶函数组件和轻量的编译策略就解决了心智负担问题,这一套创新的机制,足够说明 unis 在一众 react like 框架中的与众不同。

再复刻一个框架的意义就显得不是很合理了,怎么着你也得有点新东西才行。

现在有答案了,unis 真的有新东西!

Unis 实现

Unis 的原理和 react 基本一致,但是 unis 的实现仍然颇有特色,react 的 Fiber、中断,unis 都有,同时 unis 还做了其他的探索。

Fiber & Tree 复合结构

熟悉 fiber 的都知道,fiber 说到底也只是个链表结构,并没有什么神奇的地方,甚至连中断都不必然依赖 fiber,tree 也可以轻松实现中断。有时候我有点怀疑 react team 用 fiber 是因为团队里某些人的偏好。

react fiber 的问题是它同级节点的链表结构是单向的,意味着 diff 只能从头开始,想要进行双端 diff 就不可能了。而 Unis 则实现了一个 fiber & tree 复合结构,unis 内部仍然使用双端 diff,所以性能可以很出色。

具体跑分我也没跑过,但是之前我用同样的demo对比过 react 和 unis 在1000个元素列表的操作表现,unis表现占优,毕竟我花了很大的力气搞优化。

Toktik 虚拟宏/微任务

Unis 内部实现了一组虚拟宏/微任务,用于协调阶段的调度、中断,保证 unis 在多实例下异步调度不会出问题。

组件状态 API

Unis 同样实现了和 react 类似的 memorizeState,但是由于 react hook 和 unis 的组件 API 本质上的不一样,内部实现也完全不一致,unis 需要处理闭包内 fiber 节点的切换(从 currentFiber 到workingFiber),还有新状态 dispatch 后重新协调的机制也和 react 不一致。

体积

unis 麻雀虽小,五脏俱全,体积只有 6kb (gzip)

Unis 生态

如何让人来用一个陌生的框架?我想最重要的一点就是“生态”。我一个人没办法提供过多的生态,但是基础的开发生态我肝一段时间也就出来了。

为了让大家爽快的在 vite 下开发,我提供了 @unis/vite-preset

为了让大家爽快的使用过渡动画,我提供了 @unis/transition

为了让大家爽快的使用路由,我提供了 @unis/router

为了让大家爽快的使用UI控件,你提供了 ....

最终还是需要大家的共建,UI库这种体力活,我个人目前的状态是没精力做的,但是 unis 可以比较容易的从 react 迁移代码,arco design? ant design?,希望有大佬能够加入到 unis 的生态中。

Unis 目前的生态还不适合大(中?)型生产项目,但是自己做点东西已经完全没问题了,想自己写点东西又想尝尝鲜的朋友,完全可以试试 unis,不会让你失望的。

使用 unis

安装

npm i @unis/unis

Vite 开发

查看项目文档:Unis

具体例子可以查看 Example

在线尝试可以点击 Stackbliz

Unis API

  • Core

    • h
    • h2 (for jsx2)
    • Fragment
    • FGMT: it is Fragment alias, will be removed when vite support jsxImportSource
    • createPortal
    • createContext
    • render
    • memo
  • Hooks

    • use
    • useProps
    • useState
    • useReducer
    • useContext
    • useMemo
    • useEffect
    • useRef
    • useId

最后

都看到这里了,看来你是感兴趣的:),帮忙点个 Start 吧,谢谢!

Github: Unis

感谢阅读