回归传统:原生 JS + React 混合开发的实践与思考
引子
针对现代的前端技术栈来说,我不是一个专业的前端程序员,但是从初中就开始接触网页开发,还记得那时候的网页三剑客,用 Table 做页面布局的年代。草率的说也算是全栈吧。想到写这个文章主要是想能收到一些反馈,关于使用 React、Vue 等现代的前端技术栈过程中觉得不爽或者麻烦的地方,尤其是那些让人有冲动想去做些改变的痛点。
先说说我的痛点吧,应该是 2018 年接触 React,那时候有前端的同事做一些小的项目在用 UmiJS,前端 UI 组件库用的 AntDesign,UmiJS 的路由我理解的就是,路由切换的过程就是给页面渲染一个新的 React Component,这个 React Component 就是一个页面,是一个组件树。这种模式下,页面所有的组件都需要使用 React 方式和思维进行开发,大量使用 useEffect 等。
对于这种模式的开发我会觉得整个思维会慢慢变得混乱,回头再看代码的时候想要梳理整个页面的逻辑很难,对于新接手的同事甚至当时开发这个页面的同事都是这样。这种模式做的不好的话页面可能会有太多意外的 Rerender 带来额外性能消耗,页面上可能还会用到大量通过依赖引入的第三方工具,导致本身页面代码在构建后体积会很大。题外话:对于依赖的版本维护问题过个一两年就会出现各种 warning,想要处理更新依赖也要费一番功夫。
不知道什么时候,我开始怀念使用原始 js 开发前端的方式了。使用传统前端开发方式,我一直觉得是最符合直觉,符合思维逻辑的方式,先考虑页面设计,然后通过 js 顺序执行,逐步加载数据渲染,然后绑定事件实现交互。对于传统方式开发的代码回头进行维护或者交接的时候,都能让人更容易接受。
我的理解是,大多数项目使用 React 是为了数据绑定带来的便利和实现组件的复用。那为什么不让传统原生 js 方式和 React 方式配合使用呢,毕竟 React 就是用来做模板化渲染的,可以使用 React 提供的方法,通过原生 js 渲染到指定的容器内。这样不是就可以按更符合直觉,思维的方式进行开发并同时可以利用到 React 的便利了?我感觉对于提升开发效率和保持长期可维护性都会有很大帮助,不知道有没有哪些同学的项目是应用了这种方式的。
想的更深入一些,对于公司招聘可以分为初、中级前端,高级前端,初、中级可以负责页面逻辑的开发(使用传统方式),高级前端可以负责复用组件的开发(使用React)。
从想法到实践
第一步:让 React 组件可以被原生 JS 调用
首先要解决的问题是:怎么让原生 JS 能够方便地使用 React 组件?
传统的 React 开发中,组件之间通过 props 传递数据,通过回调函数传递事件。但在原生 JS 的环境下,我们更习惯直接调用方法。所以我想是不是可以利用 React 的 useImperativeHandle 这个 Hook,让 React 组件暴露出一些方法供外部调用。
typescript
// packages/front-ui/src/Table/SearchBarTable/index.tsx
const SearchBarTable = React.forwardRef((props, ref) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
// 暴露给外部的方法
useImperativeHandle(ref, () => ({
// 刷新表格
reloadTable: (resetPagination = false) => {
if (resetPagination) {
setPagination({ current: 1, pageSize: 10 });
}
fetchData();
},
// 获取选中的行
getSelectedRows: () => selectedRows,
// 清空选择
clearSelection: () => setSelectedRows([])
}));
// 组件内部的数据获取逻辑
function fetchData() {
setLoading(true);
const apiMethod = window.uniends.api[props.config.method.name];
apiMethod(searchParams, (response) => {
if (window.uniends.res.isSuccess(response)) {
setData(window.uniends.res.data(response));
}
setLoading(false);
});
}
useEffect(() => {
fetchData();
}, []);
return (
<>
<SearchBar
fields={props.config.searchFields}
onSearch={handleSearch}
/>
<Table
dataSource={data}
columns={props.config.columns}
loading={loading}
pagination={pagination}
onChange={handleTableChange}
/>
</>
);
});
这样,React 组件依然保持自己的状态管理,但是对外暴露了一些简单的方法。外部的原生 JS 不需要关心组件内部怎么实现的,只需要调用这些方法就行。
第二步:用原生 JS 编写页面逻辑
有了可以被调用的 React 组件之后,页面的开发就变得非常直观了。
- 先写页面,html里夹杂着React Component,通过js加载渲染。
- 页面上一些原生html元素可以绑定事件,可以通过Coponent的ref来调用React组件的方法
- 其他的js逻辑...
对于业务代码没有 useEffect 的依赖地狱,没有各种 Context 的追踪,React 组件内部维护着组件的逻辑,页面代码维护着页面逻辑,一些服用性不高的部分,一些简单的部分使用html就可以
好的方面
1. 新人上手速度显著提升
纯 React:新同事需要理解 React 的各种概念(hooks、生命周期、状态管理、路由等),通常需要 3-4 周才能独立开发页面。
混合方式:新同事只需要看懂原生 JS 就能开始改简单页面,通常 2-3 天就能上手。想要开发复杂组件时再学 React 也不迟。
2. 代码维护变得轻松
最明显的是,半年前写的代码,现在打开依然能很快理解逻辑,使用ai进行代码理解效果也更好
从上到下读一遍,就知道这个页面做了什么。不需要理解复杂的状态管理。
3. 性能问题基本消失
之前用纯 React 时,经常会遇到:
- 某个 useEffect 写得不好,导致无限循环
- Context 变化导致大范围组件重新渲染
- 不必要的 re-render 影响性能
现在这些问题基本不存在了,因为:
- 页面逻辑是过程式的,执行一次就结束了
- React 组件只负责自己的渲染,不会相互影响
- 没有复杂的依赖关系需要管理
4. 团队协作更高效
- 初级开发者(1-2年经验):负责页面逻辑开发,用原生 JS
- 中级开发者(2-3年经验):负责复杂页面和简单组件
- 高级开发者(3年以上):负责复杂的可复用组件开发,用 React
这种分工让每个人都能在自己擅长的领域发挥作用,不会因为"这个页面用了 React 所以只有高级开发者能改"而产生瓶颈。
和 Astro Islands Architecture 的异同
后来我了解到 Astro 框架也在做类似的事情 ------ Islands Architecture(孤岛架构),让我觉得很有意思。
相似之处:
- 都认为"不是所有东西都需要 React"
- 都把 React 组件当作可选的交互单元
- 都追求更少的 JavaScript 传输
不同之处:
- Astro:构建时预渲染 HTML,适合内容网站(博客、营销页)
- 我的想法:运行时动态渲染,适合数据驱动的管理后台
可以说,我们从不同的方向走向了同一个认知:React 是优秀的组件库,但不一定要成为整个应用的架构基础。
你的看法?
写到这里,我最想知道的是:
-
你是否也遇到过类似的痛点?
React/Vue 项目维护久了,是否也觉得代码逻辑越来越难理清?
-
你觉得这种混合方式可行吗?
还是说我只是在"怀旧",实际上并没有解决真正的问题?
-
有更好的解决方案吗?
也许有我不知道的工具或框架已经解决了这个问题?
-
如果你的项目也采用这种方式,会遇到什么问题?
我可能还没看到的坑是什么?
-
对于团队分工的思路,你怎么看?
初级用原生 JS,高级用 React,这样的分工合理吗?
非常期待听到不同的声音,尤其是反对意见。只有通过讨论,才能知道这个思路是否真的有价值,还是只是我的一厢情愿。
结语
从网页三剑客到现在的前端工程化,我经历了很多技术的变迁。每个时代都有它的最佳实践,但也都有它的局限性。
我不是在说 React 不好,也不是在鼓吹"传统方式万岁"。我只是在探索:在不同的场景下,是否有更合适的方式?
这篇文章只是一个开始,一次分享,一个引子。我更希望能听到大家的想法和经验。
最后以上思路我已经进行了基本的实现,主要使用gulp配合webpack配合进行构建实现,等有时间分享出来,有需要可以交流