前言
如果你是 React 技术栈,就会发现其对新手其实是不太友好的,会导致新人写出很多重复渲染的组件和 BUG,而且排查难度高(当然 React 依然是最优秀的框架,很多理念的提出者和先行者)。
当我看到 SolidJS 后,我感觉这才是真的 react(响应式),它既包含了 React 的语法和天生的 TS 支持,又拥有比 Vue 还彻底的响应式设计,让你不用为 Deps
烦恼。
import { createSignal } from 'solid-js'
const [count, setCount] = useState(0);
createEffect(() => {
console.log('count', count()) // 当 count 变化时,会自动触发
}) // 不需要 deps
SolidJS 的优秀之处更多的是在于它的高性能,感兴趣的同学可以去其 官网 了解一下。
综上,我个人很喜欢它的设计,也相信好的事物经过时间的发展最终会展露锋芒。
但现实情况是其生态很缺失,所以经过思考后和调研后,决定写一个工具函数库,一方面希望能参与到开源建设来,另一方面也希望能提高一下自己的能力,毕竟最好的学习技术方式就是马上用起来。
当然做开源也不仅是热爱,也有功利目的,希望能有一个国内外都认可的项目经验。
由于自己能力有限以及社区已经有很多优秀的项目,所以走的是搬运路线,而不是从 0 到 1 的创新。
在调研了 ahooks
、react-use
和 VueUse
等库后,决定采用 VueUse 作为蓝本,后面经过 2 个月的业余时间开发,最终完成并开源了 solidjs-use。
本文主要是记录一下在搬运过程中的部分技术实践和思考,希望对你的前端开发和开源有一些启发。
技术实践
Typescript 函数重载和联合类型
虽然 TS 也使用了很久但是有些东西还是傻傻不明白,其中就包含了错误的使用联合类型解决函数重载的问题。
当一个函数出入参数为 A
类型时,返回为 M
类型;输入参数为 B
类型时,返回为 N
类型。如果是以前我会这样写:
function foo(arg: A | B): M | N
但这样其实是不准的,因为我们明确知道 A
类型对应的是 M
类型,但是现在 A
类型却对应了 M | N
类型,这样我们在后续只能使用 any
或者 as
大法。
通过在搬砖的过程中,我对 TS 的函数重载有了正确的认知:不同参数个数或者不同参数类型,对应的返回值类型也不同时,需要使用函数重载,而不是联合类型。
function foo(arg: A): M;
function foo(arg: B): N;
function foo(arg: A | B) {
// ...
}
多包项目引用管理
在写 monorepo 项目时,a 项目引用 b 项目,当 b 项目发生改变,我们需要重新 build 出 b 产物,这样 A 拿到的才是变化后的 b 项目。
如果仅改一次还好,如果改多次 b 项目就需要启动 watch 模式,但如果 a 同时引入了 b 和 c 两个项目,那么就需要同时 watch 多个项目了,有点麻烦。
看了 VueUse 的源码,发现这个问题也很简单,只需要使用 resolve.alias
到依赖的 src 目录,而不是按照普通 node modules 的解析方式,就可以不用 watch 依赖了。
虽然这个点也很小,但是确确实实能提升开发幸福感。
PS:当然这个方案也有缺点,当依赖的源码过多,启动会变慢。
方法论思考
如何跨技术栈?
想要将 VueUse 转为 SolidJS 框架可能大部分人的想法是直接撸起袖子,对照着搬运就可以了(虽然最后的结果可能是这种方式)。
但当你把第一版拷贝过去,那么 VueUse 后续的更新怎么办?所以这其实并不是一个简单直白的问题,需要综合考虑到实现难度、后期可维护性。
如果把这个问题抽象一下,本质上是一个跨技术栈问题,那么如何解决跨技术栈问题,结合社区和自己的思考可以有以下方案供大家思考:
编译路线
我们假设需要将 Python 编译为 JS:
- 两者相同点有很多:例如 if/else、函数、循环等概念都是相通的
当然也有很多不同点,核心体现在:
- 语法层面:同样是函数,但 JS 中定义函数和 Python 定义函数写法是不同
- 运行时层面:Python 中的某个内置函数,JS 中没有对应功能的函数
针对不同点,有以下解决方案:
- 语法层面的不同:我们通过将 Python 代码结构化为 AST,然后修改及转化为 JS 的 AST 树,最后再将 JS AST 树转为 JS 代码
- 运行时层面:注入由 JS 实现的相同功能的函数即可
上面仅是说的 Python 转 JS,但在计算机中,涉及 2 个相似事物的转化,都是可以采用这套转化逻辑,其实就是编译原理的应用。
案例
前端最熟悉的 Babel 就是将最新的 JS 规范编译成老版本的 JS 规范,其中:
- 语法层面:将新语法转为老语法,例如
let
编译成var
- 运行时层面:使用 corse-js 完成 polyfill,例如 Promise 类
方案优缺点
优点: 当写好了转换代码,则无论项目的多少或者大小,都能 cover
缺点: 难度大,工作量大
solidjs-use 在最初设计的时候也是考虑过将 VueUse 通过语法转换的方式实现 solidjs-use,但由于部分逻辑实现难度大(太菜),而最终放弃。
抽象层路线
抽象层是自己先制定一套规范/语法,然后在这套规范进行完成实际代码编写,最后通过编译,编译为其他的语言/框架。
本质上依然是方案1,但是要比方案 1 的难度低很多,因为在设计之初就考虑到不同框架之间的共性以及尽量避免其他框架无做到的事情。
案例
- mitosis:Write components once, run everywhere. Compiles to Vue, React, Solid, Angular, Svelte, and more.
- 跨端框架:比如大家比较熟悉的 uniapp 和 taro,就是将小程序的规范编译成各种平台
- 低代码中的 Schema:《低代码引擎搭建协议规范》 | Low-Code Engine (lowcode-engine.cn)
优缺点
优点:整体方案难度中等,成本可控
缺点:需要提前设计,并按照抽象层的规范开发、需要不断维护和对接各个框架特性
因为 VueUse 是针对 Vue 开发的,所以在开发 solidjs-use 时此方案废弃。
分层
这种方式是,实际代码通常写在底层,并在上层进行很薄的一层封装和调用。
案例
- WebAssembly:支持 WebAssembly 的语言都可以直接调用 WebAssembly 写好的函数,直接实现跨语言
- Web Components:底层是 Web Components 写的 UI 框架,上层简单进行封装,就可以支持各个前端框架,具体案例如 ionic-ui。
优缺点
优点:难度低
缺点:需要提前设计,且基础层方案需要成熟
因为 VueUse 是针对 Vue 开发的,所以在开发 solidjs-use 时此方案废弃。
手动搬砖路线
手动搬砖路线是通过识别不同框架的差异,手动一点一点把代码敲出来。
虽然这种方式是最 low 的,但由于绝大多数框架和库最开始设计都没有考虑到跨技术栈的需求,却是目前最常用的社区解决方案。
当然 solidjs-use 不完全是搬砖,还结合了编译路线中的 运行时层面 进行了一层封装,得到了一个胶水代码包 solid-to-vue,其重要作用是使用 solid API 实现的 Vue API,可大大减少 vueuse 转 solidjs-use 过程中代码主体的变更。
优缺点
优点:难度很低
缺点:需要不断跟进项目的变化,进行手动搬砖
solidjs-use 最终采用此方案开发。
开源项目何来?
有人把开源看成很高大上的事情,必须是大佬才能做,其实不然,开源的出发点可以是帮助他人或者是炫技、或者纯粹好玩,和项目大小以及难易无关。
当然很多人苦于没有一个好的想法,本小节就是为了解决此问题,分享一些常见的开源思路。
思路1:工作/生活中的吐槽,我来工具化它/优化它
工作中我们时常吐槽某某工具做的辣鸡,你如果当作者面吐槽,可能就会被怼 “你行你来呀!”。没错,这就是你的开源机会。
案例1:ChatGPT 语音化
ChatGPT 很火,但只能文字交流,能不能做一款语音输入和输出的项目,能不能把这个项目搞到智能音箱里,能不能把她设计为英语口语助手/老师?
案例2:Vite
Webpack 打包和启动很慢,能不能做一款在速度上吊打它的产品。
思路2:创意性思考 & 前瞻性思考
案例1:无界
微前端最大的难点,也是容易出 bug 点就在于 JS 沙箱问题,以往大家的思路在于通过拦截和代理的方式使得子应用访问的不是真正的 window 对象和 document 对象等,但这种方式在实践过程中,发现漏洞很多,总是在修修补补。
而 iframe 本身具有完美沙箱,那么 iframe 做底层,上层把路由、通信等能力封装完善,是不是就可以完美解决沙箱问题了呢?
腾讯开源的 无界 就是这样的一个微前端解决方案。
案例2:WebContainers
我们都知道 CodeSandbox 的功能是一个云端的开发平台,它需要在服务端启动一个容器来实现其功能。
能不能利用最新的 WebAssembly 的能力,在浏览器虚拟化出 node 运行环境,直接把浏览器当成 server,不再需要服务端通信,这样就可大大节省服务端资源了。
思路3: 多框架迁移
前端目前是 3 超(vue/react/angular)1 强(svelte)1 中(solidjs)的局面。在你没有能力从 0 到 1 造轮子的情况下,把一个优秀项目从一个生态迁移到另一个生态不失为一个策略。
案例:ant-design-vue、solidjs-use 都是这种思路
其他
付费软件、系统,开源替代方案
桌面系统 Web 化
- Web 化办公软件(在线文档/EXCEL/PPT)
- Web 化设计软件
- Web 化 IDE
知识概念产品化
- 《番茄工作法》 -> 番茄时钟
- 《海龟交易法》 -> 量化交易软件
- 《卡片笔记法》 -> flomo
- AI +
- 低代码 +
- Web3 +
- ...
i + 100
学习法
前几天看到一个 UP 主讲如何学习英语,其中一个方法论就是 i + 100 学习法。
所谓的 i + 100
是相对于 i + 1
而言的,i + 1
是指找到一个比自己能力稍难一点的工作、任务去做,让自己能跳一跳能做到。而 i + 100
是指直接上自己最终要达到的目标的那个难度。比如:
- 想出国,练口语:应该直接在网上找一个外教或者国际尬聊软件一天聊 2 个小时,而不是先学元音、辅音,然后连读,然后学习单词,背背新概念。
- 想做开源:在有一个开源思路后,应该直接开干,而不是先考虑自己是不是经验不足,这个技术不会,那个技术不会,先学一两个月,学透彻,再开始,应该边做边学,且仅学对你项目有用的部分,其他的留给日后研究。
之所以要 i + 100
而不是 i + 1
核心在于奖励机制问题,你越快从你所做的事中获得奖励,你就越容易把这件事做下去。如果你想学习英语,立一个计划,先从高中单词复习,然后逐步大学单词、雅思单词,那你大概率会失败,因为正反馈周期太长了。
PS:确实存在数十年如一日做着没有反馈的事的人,那是真的热爱或者牛皮
结束语
如果你看完本文对你有所帮助,且对 solidjs 有一些兴趣,那么请给 solidjs-use 点一个 star,日后你会用得到。
最后,祝大家在这个越来越魔幻的世界找到自己的路。