在最近结束的“京东订单鸿蒙-回跨iOS/安卓”的项目中,NutUI 作为京东移动端的开源组件库,再次承担起一环重要的工作,为布局搭建、设计交互、无障碍、多语言、多模式实现等,提供了完备的基础能力,并出色的完成了三端的工作,基本实现了基于 Taro 框架的一码五端能力,开启了业内组件库跨端能力的新篇章。
基于业务与基建能力的重组
在2024年到2025年,NutUI 基于鸿蒙和 iOS/安卓做了基本的适配工作,不过整体项目是实验中状态,最早在2024年发布了 DongDesign UI 的一码多端版本,并开放了鸿蒙支持的能力,整合了 Taro Components 及 NutUI 组件库,期望给开发者提供在 Taro 跨端中引用一套组件的便捷方式。这是一个好的开端,让 Taro Components 和 NutUI 作为整合整体出现,这为在黄流跨端实现中提供了更直接的技术支持,并及时响应黄流业务侧提出的核心诉求——希望可以通过引用一个组件库,让整个项目在使用组件库的能力上更统一、规范、无痛。很快,这件事达成了一致,并推动了第一版 黄流业务的基础组件库 落地——@hlfe/ui 的第一个版本的出现。@hlfe/ui 主要是为了赋能黄流业务侧,覆盖黄流订单、商详、购物车、结算,在优先考虑适配鸿蒙、iOS和安卓的前提下,初步整合了 Taro Components 公共组件及 NutUI 的 30+ 组件。组件的数量及能力,会继续随着业务侧的需求及基建适配能力陆续开放。考虑到这一年多的时间内,突然出现了许多组件库的名字,这里首先对它们的关系做个厘清。首先,我们可以简单粗暴的把它们的关系理解为上图的包含关系。Taro 作为框架,jdtaro/ui 是 Taro 提供的对外的基础组件,包含容器类、Form表单等基础能力,可看做是原来的 @tarojs/components 在 jd 域内的别名(目前这两个都可以引用,可关注 Taro 官网)。第二,NutUI 在跨端能力是基于 Taro 框架来实现的, @jdtaro/ui (@tarojs/components) 是整个 NutUI 组件库的实现跨端的基座。第三,@hlfe/ui 是面向黄流的开发者,包含前端、原生同学,为了让大家用起来省事、统一,将 NutUI 和 Taro 基础组件整合在一起,为大家提供了一个统一的引用入口。未来,在其他业务场景里,如果实现一码五端,大家可以使用 hlfe/ui 来统一处理;也可以继续保留原引用逻辑。hlfe/ui 的核心诉求是希望在项目中引用组件库的方式统一、集中,不用引多个 UI 组件库。所以,在实现中,也是以核心诉求为准,将项目中涉及的组件库收口处理。基于 Taro 的跨端项目中,项目依赖的组件主要来源于 Taro 基础组件库 jdtaro/ui 及基于主站实现的高阶组件库 NutUI,需要整合这两个组件库。在整合中,我们主要关注以下能力,以更好的服务业务。容器组件与通用组件的割裂,是导入两个组件库的根本原因。所以,在组件结构上,直接将两者可用组件导出。在跨端的交付中,希望在满足交互复杂性的同时,会关注性能最优导出的策略。比如 Image ,我们优先会导出 jdtaro/ui 原组件,确保在各个端上都有实现此类组件的前提下,保障此类组件的性能最优;也会保留 NutUI 组件,并以 NutImage 导,以在基于端的性能优化下,保障组件功能的丰富性。1)类型定义:在跨端项目中,基本都使用了 TS,为了更兼容此类支持,在 hlfe/ui 中导出了组件定义相关的类型。2)公共方法,在组件库中沉淀的一些高效公共方法,也一并为开发者提供,便于大家快速交付。3)主题定义,Design Token 在组件库中的应用,方便大家可以快速的切换主题,包括正在支持的 京东到家、京犀 等主题;并直接为开发者提供相应的暗黑定义。通过构建脚本,完成了对组件最新能力的拉取、合并及导出。// 导出 jdtaro/uiindexContent.push( `import { ${com.name} as T${com.name} } from '@jdtaro/ui' const ${com.name} = T${com.name} export {${com.name}} `)// 导出 NutUI 公共组件indexContent.push(`export { default as ${com.name} } from '@nutui/nutui-react-taro/dist/es/packages/${com.name.toLowerCase()}'`)// 导出公共方法等indexContent.push(`export * from '@nutui/nutui-react-taro/dist/es/utils'`)indexContent.push(`export * from '@nutui/nutui-react-taro/dist/es/hooks'`)
使用 jd-init搭建项目后,可选用 NutUI-React 模板,在完成项目后,进入项目配置。taro jd-init my-taro-project
1、在 package.json 中,确保已包含以下内容:"@hlfe/ui": "0.0.3-cpp.ios.18-beta.10","@jdtaro/ui": "4.0.3-beta.34","@nutui/icons-react-taro": "3.0.2-cpp.3.beta.4","@nutui/nutui-react-taro": "3.0.19-cpp.23",
2、回跨到 iOS/安卓 项目时,需要关注 config/index.ts 中的配置,避免编译时异常:esnextModules: [ 'jdtaro', '@hlfe/ui', '@nutui/nutui-react-taro', '@nutui/icons-react-taro',],
import { Popup } from '@hlfe/ui'return ( <Popup visible={visible} title={title} position={position || `${isBigScreen ? 'right' : 'bottom'}`} // 大屏处理 closeable={parameter?.closeable ?? true} {...rest} // 透传原组件属性 style={{ height, ...parameter.style, ...bigScreenRect, maxHeight: '100%' }} closeAriaLabel={intl.formatMessage({ id: 'xxx_id' }) || '关闭'} // 无障碍读取配置 onClose={() => { closePop() parameter?.onClose?.(parameter?.outProps) }}> {content} </Popup>)
hlfe/ui 的核心能力,是落在 Taro 的基础组件与 NutUI 的跨端适配上,本篇主要关注 NutUI 的跨端适配及能力建设。在鸿蒙和回跨到 iOS/安卓项目中,最大的问题在于曾经在 web 和小程序端表现良好的布局与样式,会以各种奇怪的方式呈现,比如从中间弹出的弹框,会从右下方弹出;比如 Tabs 切换时,TabPane 成了白屏;比如主站不支持 SVG,Checkbox 就无法展示。这样的问题在项目中层出不穷,就需要一个一个的修改,并push上下游建设。区别于黄流业务的鸿蒙、iOS、安卓适配,作为基建,需要更多的关注在兼容已有能力的基础上实现新的适配。Taro 基建为无法支持的属性与API 提供了工具,可以快速的识别到当前项目中,在 iOS 与 安卓上无法识别的能力。在运行以下命令后,需要特别关注 errors 信息,这些会阻塞业务项目的编译。pnpm run build:dynamic --disable-upgrade --pages=pages/index/index --atom-collection --generate-source
在解决这些无法支持的属性与API时,主要考虑几点:1)基础适配,如将原来的 div、span 等 web 端标签改为 Taro 标签。/* #ifdef harmony dynamic*/width: 80px;/* #endif *//* #ifndef harmony dynamic*/width: auto;/* #endif */
// 不建议left: -var(--nut-spacing);align-items: start;// 建议:left: calc(-1 * var(--nut-spacing));align-items: flex-start;
4)废弃 API 移除,如 getAppBaseInfo等。在交付黄流的项目中,会对组件库提出更精确的需求,为此,对部分组件进行了功能扩充。1)Popup 组件:支持拖拽变更半弹层的高度、支持 top 属性可供展示营销位等;2)SearchBar 组件:增加 inputProps 属性,暴露更多的 input props 内置属性;3)Input 组件:增加 style 属性透传至 native Input 组件;为了更好的回跨,APP侧也对无障碍输出了统一的规范,基于整体规范,NutUI 也对无障碍进行了完善。ariaLabel→value→ariaRole→hint的顺序组合朗读。在大部分的组件中,View 和 Text 读取是正常的,新增了如有交互行为的组件的无障碍属性,如 CheckBox、Radio等,支持 ariaLabel 的自定义。在无障碍的实现中,iOS/安卓的表现相对来说较好。鸿蒙端,如弹出弹层时,无法自动获取焦点,需要手动触发可读取内容。类似问题会影响 Dialog、Toast、Popup 等弹层。Icon 是组件中不可获取的部分,在实现鸿蒙/iOS/安卓的适配中,采用了 Image src 引入 svg 链接的方式。import React from "react";import "@tarojs/components";const IconSVG = (props) => { const realProps = { ...defaultProps, ...props }; return React.createElement(Icon2, { ...realProps, name: realProps.name || "Add", svgSrc: "https://storage.360buyimg.com/imgtools/81adb71e35-159c76f0-a824-11f0-8300-eb1b11b7ce6f.svg" }));};
在实现中,难点在于推动主站 SVG 的基建,比如 无法展示 SVG,SVG 无法变更颜色,SVG 对于导出图片大小有要求。可喜的是,主站基建 SVG 的能力,目前正在向真实的 SVG 落地,对于图片库导出方来说,成本会显著降低。同时,Icon 库也补齐了无障碍的能力,这样方便了开发者在使用 icon 时,可以做读取屏蔽或读取设置,大大方便了在项目中处理无障碍的设置。比如在 CheckBox 组件中,对于 Icon 需要禁用可读。设置如下:<CheckedclassName={color()}ariaHidden />
而在组件维度,可以这样设置,保障组件的无障碍的支持:<View className={classNames( classPrefix, className )} {...rest} onClick={handleClick} ariaRole="checkbox" ariaLabel={ariaLabel} ariaChecked={innerIndeterminate ? 'mixed' : innerChecked} > <CheckedclassName={color()}ariaHidden /> </View>
<Checkbox className={styles.checkbox} checked={shipShopData?.venderShipmentVO?.selected} ariaLabel='读取文案' // 或读取文案 // ariaHidden={true} // 或禁用掉/>
如今,@hlfe/ui 已开放了 30+ 组件,持续支持了 订单鸿蒙、订单iOS/安卓回跨、商详鸿蒙、结算鸿蒙、购物车鸿蒙;当前还在对商详、结算和购物车的回跨交付中,未来会对更多组件进行开放。回顾这段时间,是与业务侧、Taro侧、基建能力紧密协作的一段历程。对于组件库与业务来说,都是一个快速迭代过程,其中也遇到了一些痛点,为此,我们希望可以在“提交issue-评审-提交PR-发布版本”的共建模式下,加快整个流程的执行落地,以快速响应业务侧的诉求,并给开发者提供更多共建的可能性。邀请共建:欢迎更多的伙伴们,加入我们,一起共建,在一码五端的世界里遨游~Github: https://github.com/jdf2e/nutui-react