OpenBuild 官网重构
将 OpenBuild 官网项目前端工程重构为可面向社区开源的状态,即拿出去不至于丢脸,甚至要让他人看到后惊呼赞叹!😂😂😂
因而,所要满足的条件大致是:
我会最大限度复用自己过往的经验与积累,在这过程中值得记录的点都会写在《前端铲💩日记》中。😁😁😁
需求分析
了解项目代码与梳理官网功能。
当前技术栈
整个工程以 React、Next.js 作为底层依赖,其他用到的库主要有:
类别 | 库/框架 | 备注 |
---|---|---|
工具 | Lodash | |
validator.js | 字符串模式校验,极少用到,可低成本移除 | |
Nano ID | 随机 ID 生成器,极少用到,可低成本移除 | |
数据处理 | React Redux | 全局状态管理,使用 useReducer 替代或完全不用状态管理 |
React Hook Form | 表单数据处理 | |
UI | Tailwind CSS | 原子 CSS 工具类 |
Heroicons | SVG 图标 | |
Headless UI | 无样式组件 | |
daisyUI | 基于 Tailwind CSS 的类 Bootstrap CSS 组件 | |
styled-components | 利用 ES 模板字符串写样式的组件,极少用到,可低成本移除 | |
NextUI | UI 组件 | |
ByteMD | Markdown 内容编辑 | |
SurveyJS | 问卷表单 | |
动画 | aos | 滚动动画 |
Animate.css | ||
Framer Motion | ||
Web3 | RainbowKit | |
Wagmi |
其中,有些很少用到或职责有冲突,考虑移除;并且,原代码跟 Next.js 耦合较紧,不便于日后迁出,亟需松绑。
工程目录结构
目前,前端工程的目录结构大体如下:
|
这是妥妥的「野生」模式,可以说该模式所带来的问题在项目里几乎都具备了……😩😩😩
代码编写问题
除了上述「野生」模式所带来的常见问题外,还存在一些其他问题:
- 页面文件中有 HTTP API 请求的拼装逻辑;
- 界面渲染的代码中充斥着 Tailwind CSS 的类名;
- 很多非必要的重新渲染,同时导致发起多次重复请求;
- 列表加载与翻页逻辑大量冗余;
- 通用的 UI 组件无法直观地区分出是 client only、server only 还是两者兼容的;
- 哪些部分该服务端渲染,哪些该客户端渲染,并没有很好地划分与优化;
- ……
总而言之,滥用 React Hooks 机制,几乎没有抽象,封装性极差,无论是开发者体验还是用户体验都很烂!
业务功能模块
TODO
设计
基于规范、约定、接口等「共识」编程。
调整目录结构
在「模块化」模式的基础上,根据 Next.js 的限制进行些许兼容适配:
|
其中,app
除了定义页面路由之外,还取代了 entry
文件夹作为页面渲染的入口。
文件位置挪动
将 app
文件夹下的文件尽可能向外移:
- 与业务逻辑强相关的弄到
domain
中; - 与页面渲染强相关的弄到
entry
中。
原则上 app
下只有受 Next.js 限制的兼容适配类文件。
通用性较差的全局文件也要挪到上述两个文件夹中。
文件引用规则
在文件引用层面有所限制:
shared
文件夹下的文件不可引用除public
之外的任何其他外部文件夹下的文件;domain
文件夹下的文件仅可引用public
、shared
和其他domain
文件夹下的文件;app
文件夹下的文件可引用public
、shared
、domain
及同文件夹下的文件。
使用 @/*
引用 shared
下的文件。
拆分业务模块
TODO
重塑技术栈
通用 UI 开发以 Tailwind CSS、daisyUI、Headless UI 这三者搭配使用为主,交互上的不足之处由 NextUI 等原子组件弥补,但 styled-components 这种从「思想」层面相悖的坚决移除。
以「组件」而非「CSS 类」的形式向外透出,基于它们封装出「项目级」的通用 UI 组件放在 src/shared/components/control
文件夹中,API 尽量兼容 Petals 中定义的。
图标使用 Heroicons 和自定义的,优先用 SVG React 组件的形式,可用 Font Awesome 作为候补。
请求管理
对 HTTP API 和智能合约 ABI 进行统一控制。
HTTP API
参考 Axios 封装拥有便捷方法的构造函数以发起 HTTP API 请求,并创建两个实例去支持以 /v1
和 /ts/v1
开头的请求地址:
|
智能合约 ABI
对智能合约 ABI 的调用使用 obj.method(params)
的形式替代 readContract(config, { address, abi, functionName, args })
,这样更符合直觉且更简洁:
|
请求返回结构
无论是用哪种途径发起请求,都将返回结果转换为 Organik 中定义的结构,即:
|
然后根据该结构统一处理请求「成功」与「失败」的反馈,而不必在业务模块中挨个单独处理:
|
业务模块请求
业务模块中的请求使用简单定义的异步函数:
|
如此一来,在表现层中无需感知到请求数据的具体来源。