React Native 三端同构在雪球的实践 - 策划百科 - 上海活动策划公司

React Native 三端同构在雪球的实践

16 0 2024-05-15
React Native 三端同构在雪球的实践

随着移动互联网的迅猛发展,目前市面上「端」的形态多种多样,iOS、Android 、H5、微信小程序等各种端大行其道,同一个业务需求往往又需要在多端上去实现,针对不同端去编写多套代码的成本显然非常高。雪球大前端团队将今年在跨端能力建设上的演进和推广工作整理成系列文章。

本文为第三部分,介绍今年雪球大前端团队在三端同构建设上相关工作:

  • RN / H5 同构的能力及效果
React Native 三端同构在雪球的实践

随着移动互联网的迅猛发展,目前市面上「端」的形态多种多样,iOS、Android 、H5、微信小程序等各种端大行其道,同一个业务需求往往又需要在多端上去实现,针对不同端去编写多套代码的成本显然非常高。雪球大前端团队将今年在跨端能力建设上的演进和推广工作整理成系列文章。

本文为第三部分,介绍今年雪球大前端团队在三端同构建设上相关工作:

  • RN / H5 同构的能力及效果
  • 样式组件系统的优势及定制实现
  • 同构 H5 的实现及服务端渲染 SSR
  • 同构的 CICD、单元测试及开发测试流程
  • 同构的 D2C 代码智能生成

一、背景

雪球前端的业务场景除了雪球 App 外,还有重要的一环是微信私域 H5。各需求优先在客户端 App 内实现后再同步到微信 H5,至少要投入 iOS、安卓、H5 三端的开发资源。在人力资源有限的情况下,经常会出现多需求和多开发岗位相互交叉的错综复杂关系,多岗位协同难免会造成效率降低和资源浪费。

那么有没有一种技术,能在效率、体验、成本、通用性上取得最大化的平衡,甚至打破传统的技术栈划分,进而实现三端融合同构呢?

放眼业界,各大公司不约而同也在探索跨端融合。Facebook 最早推出 React Native 支持开发双端,谷歌后来居上推出 Flutter 并不断迭代,阿里则以闲鱼为代表在推动大前端的融合,字节&快手都在大力推进自己的跨双端开发框架,比如 lynx 等。目前市面较成熟的方案是双端的融合。其中 React Native 是中小型公司较好的选择,能以较低成本为客户端提供双端同构和动态化能力。

但 RN 开发有门槛难上手、代码繁琐、多端需单独适配,并且在雪球的业务场景下,优先用 RN 开发完客户端功能后,还要再次投入资源开发微信中的 H5 。

带着上面的问题,雪球大前端 FE 团队今年着力于三端同构的建设,实现了 RN / H5 同构的落地方案,极大程度上抹平了 Native、RN、H5 的技术割裂。目前三端同构已经在雪球的私募基金、公募基金、雪盈、社区等多个业务的复杂场景下进行了应用实践。

RN / H5 同构方案最适合以 App 开发为主,并需要把 App 内功能同步到微信 H5 生态的业务场景。对于想实现 App 动态化、提升人效的中小型公司有借鉴意义,也提供了可落地的实操指南。

二、 RN / H5 同构的能力与方案

2.1 同构关键能力

1. ? 一套代码三端运行

一套代码 iOS / Android / H5 三端运行,通过 RN? 为客户端提供动态化能力,并能同构生成微信 H5。三端代码一致,告别三端单独开发或者兼容适配。

2. ? 样式组件系统和同构组件库

对 styled system 进行了最简化的雪球定制实现,封装颜色 token、屏幕适配、日夜间模式等,提升 UI 开发体验,样式代码量大幅降低,试过就再也不想写 css。封装雪球同构组件库 snowbox,提供开箱即用的丰富业务组件,提升开发效率并抹平三端差异。

3. ? 改善 RN 开发体验

将 RN 开发门槛从客户端降低为 Web 开发,初期可在浏览器以 Web 开发方式进行业务逻辑开发和接口联调等,无需启动模拟器或连接真机,中后期也可三端同步开发。

2.2%20同构的整体方案

通过三端同构组件库 snowbox 和样式组件系统 styled system 实现 RN 业务代码的快速编写。

客户端部分:通过 React Native 生成双端代码,并为 App 提供动态化能力。同时借助 RN/H5 同构,改善 RN 开发方式,提高开发体验。

H5 部分:通过 React Native Web 将 RN 代码编译为 H5,并实现服务端渲染,将雪球客户端里的 RN 功能和体验完整复刻同步到微信体系。

三、同构的效果展示

结合实际业务场景,从三方面详细展示同构的效果与优势,分别是实际业务页面的同构效果及 H5 的性能分析;采用同构技术栈后,如何改善传统 RN 开发方式和开发体验;介绍了同构组件库的功能和效果。

3.1 业务页面效果

私募基金商品页需求是雪球三端同构方案的第一个落地尝试,针对页面复杂(二十余个复杂模块+6 个二级页)、工期紧张、技术积累不足(开发同学没有 RN 开发经验)等等问题,在需求开发过程中设计并完善了同构方案,引入样式组件系统并封装同构组件库。

同构页面在 APP 中打开速度媲美原生,同时能实现线上热更新,开发人效相比于传统开发提升一倍以上,高效响应业务变化。并且能同步部署 H5,将原本客户端内才有的丰富功能和优秀体验复刻到微信生态,同时赋能业务,客户经理可以很方便在微信内分享传播,客户也能在微信中使用 H5 完成跟雪球客户端内一致的业务全流程。

在使用 RN/H5 同构开发后的三端一致性演示视频如下,包括加载速度、复杂曲线的交互、日夜间主题切换、同构与原生交互、二级页效果等。

同构%20H5%20的性能

用%20React%20Native%20写%20Web%20会不会像%20Flutter%20一样无法在生产使用呢?

同设备同网络环境下同构%20H5%20页面和纯%20H5%20页面的技术指标对比:(测试采用%20m1,横向滑动展示%20js%20资源加载情况)

项目技术栈lighthouse首次加载%20js总%20js
组件库页RN%20同构%20SSR96205k205k
私募商品页RN%20同构%20SSR80270k270k
私募持仓页react%20ssr82208k208k
私募首页react%20ssr68227k227k
投顾商品页vue65340k485k
基金首页vue51340k359k
基金持仓页vue74340k369k

可以看到,使用%20RN/H5%20进行三端同构技术后,页面性能相比于纯%20web%20开发页面基本持平,在代码分割按需加载方面也没有劣势,不像%20Flutter%20web%20那样动辄几%20MB%20的%20js%20资源,完全可以在生产环境使用。目前也已在雪球微信服务号、雪球私募、雪球基金及投顾等场景广泛使用。

3.2%20RN%20开发体验改善

对于大部分同学来说%20RN%20开发非常不方便。RN%20的样式开发、元素审查要一直盯着手机屏幕,RN%20页面状态管理、网络请求查看、接口联调没有方便的官方工具,甚至没有最佳实践,大部分是通过打%20log%20来判断,所以开发调试困难、效率低。

在实现%20RN%20/%20H5%20同构后,大幅改善了%20RN%20的开发体验。

初期%20-%20用浏览器开发%20RN

RN%20/%20H5%20同构后,需求开发初期,完全可以用常规%20H5%20的开发方式进行逻辑开发、UI%20初步编写和接口联调。使用%20vscode%20+%20chrome%20+%20snowbox%20组件库%20,即可快速完成页面基本逻辑和初步样式框架,降低开发难度。

样式初步%20-?chrome%20选择%20iphone%20se%20宽度%20375,可直接与设计稿%201:1%20对照。

页面状态数据查看%20-?%20可通过%20React%20Devtool%20查看页面内部%20state、hooks、store%20数据查看

接口调试%20-?%20直接在%20chrome%20网络调试%20tab%20进行接口联调开发,简单高效。

演示视频:

中后期%20-%20三端同步开发

页面基本逻辑&UI%20初步开发完成后,可以进入客户端内同构开发&样式微调阶段。

同时开启三端开发%20?-?%20开启两个%20terminal%20窗口分别运行%20RN%20调试命令%20yarn%20dev%20和%20web%20调试命令%20yarn%20web-dev。

UI?微调%20-?%20同时连接%20web%20android%20iOS,一次改动三端同步热更新,进行三端%20UI%20微调。

演示视频

3.3%20三端同构组件库展示

在业务需求开发中,雪球%20FE%20团队联合设计团队对雪球%20Design%20设计组件进行三端同构的工程封装,并搭建文档网站,对同构进行了更详细的介绍。罗列每个组件的参数,为每个组件的增加了在线沙盒演示。

除了在%20RN%20开发中使用外,同构组件库也可在纯前端%20CRA、vite、next%20等项目中使用,实现了真正意义上的跨端组件封装。

React Native 三端同构在雪球的实践

snowbox.js.org/

组件参数文档

四、样式组件系统 - styled system

4.1 常规的 RN 和 web 样式开发有何问题?

前端工程师开发中很大一部分时间是在写 css 样式,以往 Web 和 RN 的样式写法很繁琐,比如写样式时,要给每个元素想一个有意义的 classname;css js 两个文件要来回切换;css 样式需隔离,css 样式冗余,属性重复定义,css 体积不断增大 ;RN 样式写法繁琐冗余,每个元素都要重复设置日夜间主题颜色、布局方式、文字大小、屏幕适配;RN 双端样式有细微差异等等。

以往的 RN 业务代码:

我们对比结合市面多种方案和实践案例,最终受 styled system 启发,结合雪球实际需求,设计并实现了定制化的样式组件系统。封装屏幕适配、日夜主题适配等通用样式逻辑,将兼容三端差异的代码整合到样式组件中。

雪球样式组件系统具备以下优势:

  • 编程思路顺畅,无需绞尽脑汁给样式起名,无需 js css 来回切换,编写迅速。
  • 代码精简,减低 90% 的样式代码量,RN 包体积减少,同构 CSS 代码量逐渐收敛。
  • 自带主题切换、屏幕适配、样式隔离、兼容三端差异。

4.2 样式组件的核心实现

我们采用两个基础组件来实现这个系统,分别是盒子组件 Box 和 文字组件 Txt,并通过 Typescript 进行规范和提示。

如下图例子所示,分别将盒模型中边距、颜色、大小、定位等属性定义成属性和 Token,用代码化的语言,将组件的每一部分属性进行描述,也就是最方便的声明式 UI。

解析流程:

Box

盒子组件,相当于 web 的 div 和 RN 里的 View 。实现盒模型,定位,样式属性简写,颜色系统,主题切换,屏幕大小自适应等功能。

  • 盒模型相关:m: margin; p:padding; ?br: border; ?flex: flex
  • 定位相关:l:left; r: right; t: top; b: button; ab: absolute; c: center
  • 颜色相关:cl: color; bg: background;? 雪球颜色 token

Txt

文字组件, 支持字号、字重、颜色、雪球常用 DIN 字体等,封装行内占位等。

  • 文字属性:f: font size fw: font weight lh: line hight cl: color ls: letter spacing DIN:din 字体

颜色 token

与每个样式单独写颜色色值不同,规范的设计系统,要有一套颜色系统的,将 UI 颜色和规范进行收敛控制,实现多主题切换,并用语义化的描述。比如雪球设计规范中,T010 是指一级文字颜色,B010 指一级背景颜色,并且同时包含日夜间主题的对应的颜色色号。

样式组件系统对该颜色进行封装,每种颜色值直接转变为一个前端变量,将前端编程语言与设计语言统一,使组件和设计系统可以被快速实现。比如直接写 cl="T010"即可。样式组件系统会自动根据用户主题渲染为不同的颜色。

通过采用 Typescript 对两个组件 types 规范定义,包含每个 props 值的类型和枚举值,实现书写的提示和规范性。并进一步实现 Design Token。

屏幕适配

屏幕适配我们采用以 iphone8 375 宽度为标准,按宽度进行比例缩放。样式组件系统,所有的尺寸样式自带屏幕适配,无需给每个样式写屏幕适配代码。三端的屏幕方式有些差异,后文会详细介绍实现方式。

抹平三端差异

由于业务代码会在三端使用,部分属性在不同客户端会出现差异。比如 RN 没有行内元素的概念,在客户端内较难实现行内的 Tag;? 比如字重 500 在安卓系统中不会生效;比如 DIN 字体,安卓客户端里需要使用 DIN-medium 与 iOS 不同等等。

以往遇到这种情况,需要单独做兼容,每个元素都要写一遍。采用样式组件系统后,将兼容代码进行封装,业务代码不用关心细微差异,降低代码量。

五、同构 H5 的实现

本节着重介绍同构 H5 的具体实现方案,先后介绍了同构 H5 关键库:React Native Web 及其先进理念;如何对已有 RN 项目进行改造;同构项目的常见兼容处理方式,实现同构和代码共存;如何通过服务端渲染提高 H5 性能,以及如何处理服务渲染后的屏幕适配等等问题。

5.1 React Native Web 介绍

React Native Web 由前 Twitter 前端主管 Necolas 开发,并使用该技术方案在 2017 年重构了 Twitter 网页 (Twitter Web App) 并沿用至今。

React Native Web 在国内知名度不高,但是放眼世界 Twitter Web、Expo、NativeBase、React Native 官网等众多项目全部使用的 React Native Web 能力。

虽然是 5 年前就开始的项目,如今看依旧有非常前瞻性的理念,因为 Necolas 也是 normalize.css 库的作者,对 CSS 和前端工程化有独到的理解,其在 React Native Web 中开创性实现了诸如原子化 CSS、css-in-js、前端组件化、Web 的容器化封装、手势系统等等。具体可看参考文献中的第一个视频。

在 div 上能看到大量"r-"开头的单个 css 规则,是否令你联想起如今火热的 Tailwind css、UnoCSS?

原子化 CSS 处理

以往前端项目中,随着样式的增多,CSS? 逐渐冗余、重复打包、难以维护。现在很多库的前瞻性原子化 ?CSS? 思路,其实 React Native Web 5 年前已实现。

这种方式在使得 CSS 代码量不会随着页面的日益增多而无限扩展,而是会逐步趋于收敛,并且在服务端渲染后,能做到真正的 CSS 按需加载。

再加上客户端不支持 CSS 渲染,所以抛弃 CSS 的历史包袱之后,Web 能跟客户端样式代码一致,开发才能真正的实现跨端组件封装,有助于我们实现三端同构的组件系统。

后来 Necolas 加入 Facebook,从事 React 相关开发。近期 Facebook 对 React Native Web 这种原子化 CSS 的处理方式进行封装,取名 stylex,Facebook 和 Whatsapp 等官网都使用这种 CSS 处理方式重写。stylex 目前还未开源,不过其基本理念来源于 React Native Web ,可见 React Native Web 的前瞻性,再加上 React 是 Facebook 出品,在可预见的未来,这种处理方式可能会成为 React 的默认样式写法与解析方法。

随着雪球前端对 React Native Web 的深入理解和使用,我们也积极参与 React Native Web 的开源建设,为 React Native Web 增加多个 feature,核心开发者也展示在了 React Native Web 项目首页。

5.2 RN 项目的同构改造

同构的 web 架构

我们在已有的 RN 项目里引入 React Native Web,并选取 Next.js 作为服务端渲染的项目脚手架,同时使用 koa.js 做 node 服务端自定义 server。

Next.js 是 Vercel 明星开发团队出品的成熟 react 服务端渲染框架,在开发体验、代码分割、服务端渲染、优化方面做的很好,开箱即用,所以我们使用 Next.js 来处理 web 的编译,通过服务端渲染能力解决 React Native Web css-in-js 方案的首屏渲染问题。

为了能复用雪球前端通用的 node 中间件,比如 snb-lib-token 等, 所以服务端采用 ?Next.js + 自定义 server 的方式(custom-server)

React Native 三端同构在雪球的实践

  • 前端代码由 Next 进行服务端渲染
  • 服务端代码由自定义 server koa 实现,进而复用雪球 node 中间件。

同构的路由注册

雪球 App 里是为每个 RN 页面注册一个 URL 路由,该规则通过 json 动态下发,客户端读取后将 RN 页面注册到 URL 路由上,进行匹配和跳转,并且能控制 RN、H5、原生的优先级逻辑。

同构 H5 的路由跟 RN 的路由注册保持一致,实现同一个 URL 在客户端内打开 RN 页面,在微信中打开 H5。这样做还有一个优势,由于保持了 RN / H5 路由一致,H5 还会为客户端 RN 页做兜底,比如 RN 未覆盖到的低版本客户端或者 RN 包出问题时,点击 URL 会打开对应的同构 H5 页面做降级。

同构的差异化处理

同构改造还涉及对 RN 和 H5 的差异化处理,总结主要有三种方式:由代码处理、由 Metro 处理、由 Next.js 处理,分别以三端兼容代码、jsbridge、H5 路由注册为例介绍。

1. 由代码处理差异 ?- 以兼容代码为例

需对平台进行逻辑判断,比如微信分享的 jssdk 在客户端打包时无需引入,比如端上的逻辑无需在 node 上执行。

Platform.OS 有 "iOS" | "android" | "windows" | "macos" | "web" 5 种,在同构项目中 web 其实还分为两种,一种是纯 web,一种是 node 服务端渲染。所以我们进行封装增加了 node 标识。

OS? : ?"iOS" | "android" | "windows" | "macos" | "web" | "node"

//?只有web才引入weixin-js-sdk?
if?(OS?===?'web')?{??wx?=?require('weixin-js-sdk');
}
//?只有在服务端?才进行mobx?的?服务端??useStaticRendering?设置
if?(OS?===?'node')?{
??useStaticRendering(true);
}

2. 由 Metro 打包工具处理差异 - 以 jsbridge 为例

jsbridge 等跟各端有关联,在客户端内需调用 RN 的方法, 在 web 端需调用 web 的方法。

同构写法为通过 native.js 后缀区分,由 React Native Metro 打包工具默认提供。

RNBridge
├──?index.js?#?由前端工具打包的文件
├──?index.native.js?#?由?React?Native?自带打包工具(Metro)?打包的文件

3. 由 Next.js 处理 - 以 H5 路由注册为例

因为雪球的 RN 是按照路由注册,与 web 相通,所以同构 H5 实现对应的 URL 路由注册即可。

得益于 Next.js 的自动注册路由,服务会根据 pages 目录结构,生成对应的 url 路由。但是默认 RN 项目的页面目录也是 pages,所以 next.js 会打包全部 pages 中的页面,部分没有经同构处理的页面会报错,需要控制只打包同构的页面。我们的处理方式是添加自定义标识 ".web.js",设置 Next.js 只处理 "web.js" 后缀的文件作为入口文件,实现跟 RN 原生代码的共存。

module.exports?=?{
??//?入口只选web.js后缀的??pageExtensions:?['web.jsx',?'web.js',?'web.tsx',?'web.ts'],};

同一目录下的 RN 入口 与 ? 同构入口:

.
├──?index.js
├──?index.web.js

index.web.js 中内容也很简单,使用封装好的 Wrapper 包装下 RN 入口文件即可,里面有全局的参数、样式等处理,把 Web 和 node 端也当做容器来处理,使 H5 保持跟 RN 一致。

import?{?Wrapper?}?from?'snowbox';
import?Page?from?'.';

export?default?Wrapper(Page);

5.3 同构的服务端渲染

服务端渲染(SSR),是指由服务侧(server side)完成页面的 DOM 结构拼接和样式生成,降低页面渲染的白屏时间。

我们发现复杂页面同构生成的 H5,在 iphone6 等低端手机上渲染较慢,对同构页面进行 lighthouse 跑分也证实了这个问题。其中有一部分原因是因为 React Native Web 的样式解析采用的 css-in-js 方案,所以需要在 js 加载完后,动态计算后才能得到,在低端手机上必然会造成白屏问题。

对此我们的优化方案是采用服务端渲染,并修改 Web 的屏幕适配逻辑,提升同构网页性能。

采用 RN 同构服务端渲染后的性能提升:(测试采用 m1,将 cpu 性能降低 4 倍)

lighthouse
项目服务端渲染非服务端
私募新商品页8067
xueqiu.com/rn9893
组件库展示页9686

RN 服务端渲染演示

服务端渲染屏幕适配

前端页面需做移动端适配,根据不同的屏幕宽度进行相应的比例缩放。

在客户端 React Native 中,我们使用的是 Dimensions.get('window').width 获取屏幕宽度,并按照比例对每个元素进行尺寸计算,得出适配大小。

但是在 H5 服务端渲染时,服务端的 Dimensions 获取不到用户的屏幕宽度,尺寸计算失败,导致服务端渲染的 H5 初始样式展示有问题,页面渲染会有抖动的现象。

根据前端项目的服务端渲染经验,我们做了两部分处理:

  • 使用 rem+vw 布局来做屏幕适配: H5 使用 rem+vw 适配, 替代 RN 里面 js 的动态计算方案。由于 rem 和 vw 都是相对大小,无需获取屏幕的实际宽度,所以能实现服务端渲染,并能自动适配不同的屏幕大小。也因为无需 js 动态计算样式,所以提升了页面性能。
  • 不支持 rem 的默认 375: ? 对于一些不支持 rem 的地方,比如 svg 的大小,依旧保留默认的计算方案,Dimensions.get('window').width 给的默认值 375,保持元素比例正确。不进行屏幕适配。

服务端渲染改造点还包括**「服务端样式提取」、「OS 封装」、「Window 封装」、「Wrapper 通用参数处理」、「全局变量封装」**等,具体可移步组件库官网了解详情。

六、同构的 CICD 流程

6.1 持续集成

“持续集成”(CI) 通常指的是持续地将代码更改集成到代码的主分支中。具体来说,CI 服务通过自动化测试、构建和发布过程来帮助用户频繁地集成更改。对于基于 pull-request 的工作流,每次都会运行所有测试和 lint 检查,打包,下发。

雪球三端同构的 CI 由 Gitlab CI 实现,包含 commit 校验、代码 lint 校验、单元测试、RN 打包上传等等。

而持续部署( CD )分为两部分,分别是端侧 RN 发布和 Web 服务的部署。

端侧 RN

采用 Gitlab CI + 自研 mPaaS 实现。所有 feature 分支 PR 合并后自动打 RN 包,由自研 mPaaS 实现 RN 发布客户端版本控制、多阶段控制、代码上传、分包下发、切包等功能。

Web 服务

采用 Drone CI + 自研 Rolling Docker 部署。由 sit、sep、staging、prod 四个特定分支来承载 Web 服务的分环境部署。PR 合并后触发 Drone CI 的 Docker 镜像构建,随后通过 Rolling Docker 部署前端 node 服务 。

同构的开发、测试、上线整体流程如下图所示:

6.2 同构的单元测试

CI 流程中的关键环节是单元测试,因为涉及到客户端,所以同构代码的测试与以往前端测试有所不同,我们对 RN 开源项目的单元测试进行了对比调研。

RN 组件库使用测试工具对比

组件库地址工具及类库
react-nativegithub.com/facebook/re…Jest react-test-renderer
React Native Elementsreactnativeelements.com/docs/testin…Jest React Native Testing Library
tamaguigithub.com/tamagui/tam…Jest
nativebasedocs.nativebase.io/testingJest jest-expo

方案对比

  1. Jest react native 作为 jest preset 配置 + RNTL 库
  • 使用者多,官网推荐,大多数 RN 库都使用这套配置来测试
  • 使用 node 环境模拟 react native runtime,所以不能测试 native feature,比如 fireEvent.scroll 可以调用 onScroll,但是不能模拟原生侧调用的onMomentumScrollBegin方法
  • platform.os 默认为 iOS

    2.Jest-expo-enzyme 作为 jest preset 配置,即集成了 enzyme(也是 react 测试库)的 api

  • 可以模拟 web、iOS 和 Android 环境分别运行,platform.os 正确

  • 无法解决 RNTL 库提到的 native feature 测试不到的问题
  • jest-expo-enzyme 项目属于 expo 项目内小分支,对应资料少,enzyme 使用比 rntl 体验稍差

选用方案

Jest + React Native Testing Library:测试库(基于 react-test-renderer 和 React Testing Library)

  • 优点
    • 允许测试常规 React Native 程序的大部分逻辑
    • 允许在 Jest 或其他测试运行器支持的任何操作系统上运行测试,比如 CI
    • 使用的资源比完整的运行时模拟器少得多
    • 可以使用 Jest 模拟计时器

代码覆盖率

jest 集成了代码覆盖率计算的功能,通过jest --coverage可以获得代码覆盖率相关结果,以下是 test case 的示例结果,通常认为 80%

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

展览设计 展台设计 品牌发布 场地布置 婚礼策划 会务论坛