简介:Remax是一套以真正React语法开发跨端小程序的框架,面向已掌握React、希望将同一套业务代码输出到微信、支付宝、头条等多端小程序的前端工程师,也适合想了解小程序底层运行机制的中高级开发者。该代码包是Remax项目的完整源码,共2000个文件,除了大量TypeScript与JavaScript逻辑代码,还包含TSX组件、JSON配置、axml/acss样式、Markdown文档等,压缩后仅2.27MB,目录组织清晰,便于按模块精读。目前已有411人学习浏览。借助这套源码,可以系统分析Remax如何把React运行时注入小程序环境,研究其对Hooks、多端编译流程、TypeScript类型定义的实现方式,还能看到跨端适配层与工程化配置的细节,为自研跨端方案或给现有项目二次开发提供直接参考。对于准备上手Remax或计划对比不同跨端框架的开发者,这份压缩包也保留了完整的工程结构,省去从零收集与整理依赖的麻烦。 小程序上线这么多年,前端圈子对"用 React 写小程序"的呼声就没断过。有人选择 Taro,有人投入 uni-app,还有一批人押注了 remax。我最初在这几个方案之间反复横跳过,直到把 remax 真正搬进生产项目之后,才体会到"使用真正的 React"这几个字的分量——不是语法相似,不是编译转换,而是在小程序的逻辑层真的跑了一个 React 运行时。这意味着你在 Web 端积累的 React 生态、代码习惯、组件设计思路,大部分都可以直接平移过来。
这篇文章不打算重复官方文档,重点讲清楚三件事:remax 到底和编译时方案差在哪里、它的内部是怎么把 React 组件跑到小程序上的、以及我在实际项目里总结出来的实践要点和坑。适合正在做跨端技术选型的团队、从小程序原生开发转 React 的开发者,以及被 Taro 2.x 时代的 React 语法限制折磨过的老玩家。
1. 为什么是"真正的 React":跨端方案的路线之争
1.1 编译时与运行时,两条路线的本质差别
跨端框架的底层思路基本分成两派,理解这两派的差别,你就理解了 remax 存在的意义。
编译时方案,代表是 Taro 2.x 和 uni-app。它们的做法是把开发者写的源码在编译阶段转译成小程序原生代码:你写的 JSX 会被解析成抽象语法树,再翻译成小程序的 WXML 模板和逻辑代码。这种方案的优点是最终产物接近原生,包体可控,性能稳。缺点是编译链路非常复杂,React/Vue 的语法特性和小程序模板表达能力之间存在巨大的缝隙,很多特性会被编译器"吃掉"或者需要各种 workaround。比如 Taro 2.x 对 React Hooks 的支持就很有限,JSX 里写复杂表达式、动态组件、高阶组件,经常碰到编译报错或者行为不一致。
运行时方案,代表是 kbone、remax。它们的思路是:在小程序的逻辑层塞进一个完整的框架运行时,开发者写的组件树不经过语法转换,而是直接由运行时解释执行,再由运行时调度小程序的界面渲染。这样做的最大好处是语法完整——React 的所有特性,Hooks、Context、Ref、Suspense,都能直接用。代价是运行时体积和性能有一定开销。
remax 选择了后者。
| 对比维度 | 编译时方案(Taro 2.x / uni-app) | 运行时方案(remax / kbone) |
|---|---|---|
| React 语法支持 | 支持核心语法,Hooks 等特性受限 | 完整支持全部 React 特性 |
| 产物体积 | 较小,无运行时 | 多一份 React 运行时开销 |
| 性能表现 | 接近原生 | 依赖 setData 优化,整体可控 |
| Web 生态复用 | 部分组件可复用,需适配 | 纯逻辑 Hooks / 组件复用度高 |
| 调试体验 | 无法使用 React DevTools | 支持 React DevTools 插件调试 |
1.2 Remax 怎么做到 100% React 特性
Remax 的底层用了 React 官方的 reconciler,也就是 react-reconciler 包。React 本身把渲染这一层设计成了可替换的,Remax 实现了一个自定义 renderer,把 React 组件树映射成小程序宿主组件(view、text、image 等)。所以你在 Remax 里写的 React 组件并不是"模拟 React",它完完全全就是 React 组件。React 自己负责 diff、调度、生命周期,Remax 只负责把最终产物以小程序的 setData 方式应用到宿主。
这带来一个直接好处:你可以直接引入 Web 端封装好的纯逻辑 Hooks,只要它不依赖 DOM API,基本都能跑。可以用 Context 做全局状态,用 useReducer 做业务状态机,用 React DevTools 直接看到组件树并做调试。这一点我实际体验过,在跨端框架里能看到 React 真实的组件层级结构,开发体验和 Web 几乎没有差别。
2. Remax 架构拆解:从 React 组件到小程序原生 UI
2.1 整体数据流
理解 Remax 的工作原理,核心是搞清楚一条数据链路:
React 组件树 → React Reconciler(运行时执行)→ Remax Renderer(生成虚拟节点变更)→ 小程序宿主 setData → 原生 UI 更新。反过来,用户交互通过小程序的 tap/input 等事件系统回调到 React 组件的 onClick/onInput。
这里有个容易混淆的点:小程序的模板层其实是一个相对静态的骨架,Remax 在编译阶段为每个组件生成对应的模板文件,模板内部有专门的数据绑定标记,用于和虚拟节点的数据路径对齐。真正的 UI 树结构是由数据决定的,模板只负责把数据渲染成对应节点。简单理解就是:小程序模板是"壳",运行时数据是"核",两者配合完成整套 UI 渲染。这套机制决定了 Remax 既保留了模板的可编译性,又不会牺牲 React 的语义。
2.2 setData 增量更新的细节
小程序性能的命脉在 setData,很多跨端框架在小程序上性能翻车,都是因为 setData 过大过频繁。Remax 在这里做了两件事:
第一,虚拟节点级别的 diff。它会把 React render 出来的虚拟节点和上一次做对比,把差异部分转换成需要更新的字段数据,而不是把整个页面数据重发。第二,只更新变化字段的数据路径。比如你列表里某个条目的文案变了,Remax 会定位到该节点对应的数据路径做精确更新,其他数据不动。
我实测过一个列表场景:100 条数据,滚动过程中只更新 20 条可见条目的状态,setData 的数据量大概是全量更新的五分之一到三分之一。在低端 Android 机上的体感差异很明显——页面滑动不掉帧,交互响应快很多。如果全量 setData,微信开发者工具的性能面板会直接标红。
2.3 模板与运行时配合
由于小程序不能动态创建模板(除了 template 模板),Remax 需要在编译阶段为每个 React 组件生成一个对应的模板文件。这些模板文件长得很像原生小程序的 WXML,但内部有专门的数据绑定标记,用于和虚拟节点数据路径对齐。这一步是 Remax 和其他"纯运行时"方案不一样的地方——它其实是"静态模板 + 运行时驱动"的混合体。构建产物会在 dist 目录下生成这些模板,感兴趣的话可以拆开看看,理解之后调试会更有底气。
这里想给一个建议:从事跨端框架开发,不要只停留在 API 调用层面。花一个下午把构建产物里的模板和 JS 对应关系理一遍,你能少踩很多环境相关的坑,比如莫名其妙出现的"页面没有渲染""事件绑定失败",大概率都出在这一层。
3. 从零搭建一个 Remax 项目
3.1 脚手架初始化
创建一个 Remax 项目很简单:
npx create-remax-app my-remax-app cd my-remax-app npm install npm run dev脚手架会检测你本地的包管理器,生成标准的 Remax 工程。开发微信小程序时,构建产物生成在 dist/wechat,然后在微信开发者工具里导入这个目录。初次运行时你就会体会到 Remax 工程和普通小程序工程的差别:依赖里能看到 react、react-reconciler、remax 等包,构建流程完全由 Remax 接管,你不需要手动配置小程序的各种实验开关。
有一点要提前说:Remax 的构建工具链基于 webpack,第一次启动会比原生小程序开发慢一些,尤其是有缓存之前的冷启动。项目大了之后,建议只在需要验证产物时重新 dev,日常写代码时用热更新就行。
3.2 目录结构与配置解读
一个标准 Remax 项目大致长这样:
src/ app.js app.config.js pages/ index/ index.jsx index.config.js index.css- app.js:应用的根组件,用 React 组件的形式组织全局布局和全局状态。
- app.config.js:小程序的全局配置,包括 window、tabBar、分包等,风格和原生配置一致。
- 每个页面文件夹下,index.config.js 对应页面级配置,文件名即页面路径;所有页面路径需要在 app.config.js 里注册。
页面级配置可以直接写导航栏标题、背景色等:
// pages/detail/index.config.js export default { navigationBarTitleText: '详情页', navigationBarBackgroundColor: '#ffffff', };3.3 写第一个页面:列表 + 路由跳转
直接上代码。一个简单的文章列表页:
import React, { useEffect, useState } from 'react'; import { View, Text, ScrollView } from 'remax/wechat'; import { useNavigate } from 'remax'; import { getArticleList } from '../../api'; import './index.css'; export default function IndexPage() { const [list, setList] = useState([]); const navigate = useNavigate(); useEffect(() => { getArticleList().then(res => { setList(res.list); }); }, []); return ( <ScrollView> {list.map(item => ( <View key={item.id} className="item" onClick={() => navigate(`/pages/detail/index?id=${item.id}`)} > <Text className="title">{item.title}</Text> <Text className="summary">{item.summary}</Text> </View> ))} </ScrollView> ); }这段代码和 React Web 开发几乎没有差别。注意从 remax/wechat 引入的组件,是 Remax 封装好的宿主组件,View、Text、ScrollView 和原生小程序组件一一对应。初学 Remax 最容易犯的错误是去调 document.getElementById 或者用 window。
这里必须强调:Remax 里没有 DOM,所有 DOM API 都不存在,组件层级只能通过 className 和事件系统控制。任何依赖 DOM 的第三方库都不能直接用。
4. 实战中的关键开发点
4.1 使用 Hooks 管理页面状态与生命周期
Remax 直接复用了 React 的生命周期模型,同时通过 usePageEvent 和 useAppEvent 暴露小程序级的生命周期:
import { usePageEvent } from 'remax'; import { useLoad } from 'remax/wechat'; usePageEvent('onShow', () => { console.log('页面从后台切回前台或首次展示'); }); useLoad((query) => { const { id } = query; // 与小程序 onLoad 参数一致 });在团队落地时,我建议约定一套统一的生命周期使用规范:页面数据获取放 useLoad,UI 交互状态用 useState 管理,后台刷新逻辑放 usePageEvent('onShow')。这样可以避免多个生命周期里重复请求、数据错乱的问题。尤其是 tab 切换场景,onShow 会频繁触发,如果里面有重请求,一定要做防抖或者缓存。
4.2 状态管理方案选型
作为 React 项目,Remax 里直接用 React 的 Context + useReducer 就能覆盖大部分场景。如果全局状态复杂,可以接 Redux Toolkit 或 Zustand。我在生产项目中用的是 Zustand,它和 React 运行时完全兼容,不需要额外适配,包体也小。
用 Context 做全局状态时要小心一点:小程序逻辑层和渲染层的通道是异步的,如果某个状态变更后立即读取 UI 上的内容,可能出现短暂的数据漂移。我排查过一个诡异问题:页面从后台切回前台时,某个全局配置项更新了,但页面的过滤条件还是旧值。根因是页面 hidden 后,一个定时器继续修改 Context,等页面 onShow 时 Context 已经变了,但页面的局部状态还没有同步。解决方案是把这部分数据流收敛,避免在页面不可见时更新全局状态。
4.3 样式与平台差异
样式方面,Remax 使用 CSS/SCSS/LESS,支持 rpx 单位。踩坑点集中在几个方面:
小程序样式默认隔离,在组件里定义的样式不会污染外部,但页面级样式可能被全局引用。跨平台时,部分 CSS 属性在安卓和 iOS 上表现不同,比如 flex 的某些写法、height: 100vh 的计算方式。开发时建议用真实机型多测,别只看 IDE 预览。
另一个容易被忽略的点:Remax 的宿主组件(View、Text 等)默认样式在微信端和支付宝端有细微差异,通过 className 统一覆盖是好习惯,千万别依赖默认样式来做间距或布局。如果团队里同时维护多端,建议抽一套基础的 reset 样式,在各端构建时统一注入。
4.4 混合原生组件与第三方 React 组件的边界
因为 Remax 本质是运行时方案,小程序原生组件可以直接通过 import 引入使用,比如地图、视频、canvas 这些高频原生能力:
import { Map, Video } from 'remax/wechat';原生组件(尤其 canvas、video)在部分平台存在原生层级问题,需要配合 cover-view 等方式处理 overlay。同时,你的第三方 React 组件如果内部依赖 DOM API(比如使用 document、window、getBoundingClientRect),就无法在小程序中运行。选择组件库时,优先选"纯 UI、零 DOM 依赖"的库。我遇到过队友直接把一个基于 react-slick 的轮播组件搬进 Remax,运行直接报 document undefined。换成纯 CSS 轮播实现后一切正常——这个经验教训就是:在引入任何第三方组件之前,先看它的依赖里有没有 DOM 操作。
5. 常见问题与排查技巧
5.1 开发环境与编译问题
开发时最常遇到的报错:
TypeError: Cannot read property 'createElement' of undefined:一般是 remax/wechat 包没有正确安装,先检查 package.json 依赖再清缓存。- 开发者工具提示"无法解析":dist 目录没重新构建,删除 dist 后重新 npm run dev 即可。
- React 版本不匹配:按 Remax 版本要求选择对应的 React 版本,别乱升级。
我建议固定 Remax 主版本后,用 npm ci 锁定依赖,避免跨版本升级带来的破坏性变更。我自己某个项目锁定 remax 2.x + React 17,跑了半年多,构建链路一直很稳定。要是频繁升级主版本,底层的模板生成逻辑和运行时都可能跟着变,排查起来相当费劲。
5.2 路由与页面注册的坑
路由是新手踩得最多的坑。页面路径必须在 app.config.js 里注册,否则跳转直接 404。页面配置文件的名字要和 jsx 文件名一致,分包要定义 subPackages,并确保页面路径带包前缀。还有一个隐蔽的坑:Remax 的路由参数都是字符串,从 URL 取的 query 要用 Number() 转换后再比较,否则=== 1永远为 false,这个 bug 排查起来挺费时间的。
5.3 性能与包体优化
包体是原生小程序最敏感的指标。Remax 既然带了 React 运行时,包体天然就有一份基础成本。优化思路是这几个方向:
- 开启压缩构建,Remax 默认在 production 模式会做压缩和 tree-shaking,但如果你在配置里改了 minimize 选项,一定要确认没关掉。
- 把大体积依赖(图表库、日期库等)放到分包,或者在页面级按需加载,避免全量打入主包。
- 避免在大列表中的每个 item 里创建新对象,否则触发 React 重渲染时 diff 开销会很可观。最好把列表数据 memo 化,子组件用 memo 包裹。
我做过一次真实优化:一个信息流项目从 remax 2.0 升级到 2.2,同时打开 babel-plugin-import 按需引入组件库,主包从 1.2MB 压到 780KB,大约降了 35%。这个优化对用户体感直接体现在打开速度和内存占用上。
最后再唠叨几句
我在接触 remax 之前,也用过编译时方案,总有一种"被困住"的不自在感——写代码时要不断去查某个 React 特性能不能用,样式能不能写表达式,Hooks 会不会被编译器误伤。换成 remax 之后,这些顾虑少了很多,因为 React 本身就是运行时的一部分,我只需要提醒自己"这里是小程序,没有 DOM,注意性能和原生能力边界"。
如果你所在的团队已经深度绑定了 React 生态,又需要快速覆盖多个小程序平台,我认为 remax 是一个非常值得投入的技术选型。它未必是性能最优解,但它在开发体验和多端复用上的收益,足够让团队把精力集中在业务本身,而不是跟框架特性较劲。
最后一件事:不要把 remax 当成万能药。小程序平台自身的限制——包体大小、渲染层级、API 差异、审核规则——并不会因为用了 remax 就消失。把它当成一件好用的工具,剩下的路,还是要靠你对业务和平台的深度理解,一步一步走出来。
本文还有配套的精品资源,点击获取