本文记录把 react-native-dropdown-picker 适配到 HarmonyOS 的完整过程。

这个库是一个功能很全的下拉选择器:单选/多选、可分类、可搜索、可换主题、可多语言、支持 RTL。它是纯 JS 库且零运行时依赖,所以适配本身很轻——难点不在"能不能跑",而在"怎么证明它跑对了"。

因为它有两处结论只能靠跑起来才知道:

  1. 源码里方向映射 DROPDOWN_DIRECTION.TOP → 'bottom' 看着是反的,而两个 README 对这个 prop 一个字都没写;
  2. 它的默认列表模式会在可滚动页面里弹出一条红屏警告。

这两条如果只读代码,很容易写出错误结论。下面把它们都测成事实。

在这里插入图片描述


一、先说结论

项结果
上游最新版5.4.6(MIT);npm gitHead = d25287361f0b2391078a3f2517b4f5e60cd6b6cf
库类型纯 JS 组件库(下拉选择器)
是否需要原生适配不需要(无 harmony/、无 NativeModules、无 requireNativeComponent)
运行时依赖零(dependencies 是空对象)
图标资源自带 8 个 PNG(light/dark 各 4),不依赖 vector-icons / svg
与 npm 上游的差异25 / 26 个文件去掉 CR 后逐字符一致;唯一差异是 package.json
编译assembleHap 首次 5 分 26 秒;HAP 81,984,706 字节(增量 +189,498 ≈ 185 KB)
打包资源Copied 13 assets(原 5 个 + 本库 8 个 PNG = 13,精确对上)
设备侧断言✅ 48 / 48 全部通过
外部协议✅ 收起态 50vp;展开不推挤(绝对定位浮层);列表项间距恒 40vp;强制 TOP/BOTTOM 方向语义正确;点选生效并自动收起;搜索过滤生效
主动否定的疑点GET_DROPDOWN_DIRECTION 的"反向映射"不是缺陷(返回值是"锚定边",实测语义正确)
必须知道的约束⚠️ 默认 listMode = FLATLIST 在 ScrollView 内会报 VirtualizedLists should never be nested inside plain ScrollViews…;库自带 LIST_MODE.SCROLLVIEW / MODAL 规避

一句话结论:接入只需三处、零依赖、渲染与交互都正常;但它的默认列表模式和方向 prop 都需要额外注意,而这两条文档里都没写。


二、判定过程:三步走完,确认是纯 JS 库

判断一个 RN 库要不要做原生适配,固定走三步:

步做法这个库的结果
①package.json 里有没有 harmony.autolinking没有
②仓库里有没有 harmony/没有
③代码里有没有 NativeModules / requireNativeComponent / TurboModuleRegistry全无

再补两项这个库特有的确认(很多"看着纯 JS"的库其实栽在这里):

检查结果
dependencies 内容空对象 {} ⇒ 不存在"传递依赖装不上"的问题
图标怎么来的require('./icons/arrow-down.png') 这类自带 PNG,走 Metro 的 assetExts(RN 0.84 默认已含 png)⇒ 不需要 vector-icons、也不需要 svg

⇒ 纯 JS 库,直接可用:没有六步原生接线,只需要在宿主里装包、加 watchFolders、注册测试页。

一个便宜又有效的自检:数资源。 打包日志里 Copied 13 assets——此前是 5 个,本库带 8 个 PNG,5 + 8 = 13 精确对上,一条命令就确认了 8 张图片全部被正确打包。凡是有二进制资源的库,都值得做这个算术。


三、交付包:实现零改动,但文档漏了三条关键约束

3.1 与 npm 上游逐文件比对(先去 CR)

类别结果
一致25 个文件(index.js、index.d.ts、src/** 全部实现、8 个 PNG、README.md、LICENSE)
不同1 个:package.json
交付包缺少0
交付包多出6 个:.gitignore、两个 README.OpenHarmony*、代码检查报告、spec.json、契约测试

package.json 的变化只有:description 追加鸿蒙字样、repository / homepage 指向 AtomGit 适配仓库、新增 scripts.test。

实现零改动(含类型声明和全部主题资源),所以下面所有结论都只涉及使用方式,与实现质量无关。

3.2 做得好的

  • 实现零改动(25/26 文件逐字符一致);
  • spec.json 带 license: MIT,并明确写了 native: false、rnoh 0.84.3、reactNative 0.84.1;
  • 零运行时依赖,从根上避免依赖注入问题;
  • npm install / npm pack 全程 exit 0。

3.3 问题:README 漏了三条会让人踩坑的信息

漏掉的内容照文档写会怎样
默认 listMode = FLATLIST,放进同方向 ScrollView 会报 VirtualizedLists should never be nested…在可滚动页面里放个下拉,必然看到红屏警告;而库里其实自带 LIST_MODE.SCROLLVIEW / MODAL 两种规避方式
dropDownDirection 这个 prop 完全没写而它的内部映射看着是反的,没文档就只能猜(见第八节;实测发现语义其实是对的)
组件是"完全受控"的:open/setOpen 与 value/setValue 必须由父组件持有照抄"只传 items"的写法,界面完全不动,且不报任何错

另外中文 README 的"使用示例"是:

import Library from 'react-native-dropdown-picker';
const Library = require('react-native-dropdown-picker');

Library 这个占位名与真实导出名不一致,也没给出任何能跑起来的用法。

spec.json 还有一个应当修正的疏漏:没有 upstreamCommit。而这次 npm 元数据里明确有 gitHead(d252873…),所以完全可以填——同样是"缺字段",有的库是客观取不到,这个不是。


四、接入方式:三处

4.1 装包

// package.json
"react-native-dropdown-picker": "file:../react-native-dropdown-picker"

4.2 加进 watchFolders

file: 装进来的库是 junction(软链),Metro 需要知道它的真实目录才能解析源码:

// metro.config.js
watchFolders: [
  // ...
  path.resolve(__dirname, '../react-native-dropdown-picker'),
],

如果是从 npm registry 或 tarball 装成真目录,这一行就不需要。

4.3 注册测试页

// index.js
import DropdownPickerTestApp from './DropdownPickerTestApp';
AppRegistry.registerComponent('DropdownPickerTestApp', () => DropdownPickerTestApp);

切换页面(换页参数只在冷启动生效,force-stop 不能省):

hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey DropdownPickerTestApp

不需要 HAR、权限、ohpm、link-harmony。


五、验证设计:三层,每层都要有独立证据

这个库没有"一个返回值就能断言"的 API,所以验证拆成三层:

层要回答什么手段本轮结果
① 静态契约导出形态、枚举默认值、静态 setter 真的有效吗?在设备上读回对象与 *.DEFAULT39 条断言
② 布局几何收起多高?展开后推挤还是浮层?列表项多高?marker 夹逼:每个 Picker 前后各放一个文本 marker,量 Y(下) − Y(上)6 条断言 + 外部测量
③ 交互语义点选、搜索过滤、受控 open 真的生效吗?应用外点击 + 应用外注入文本 + 读回布局3 条断言 + 外部协议

关键设计:让主题里的声明变成"预言"。 这个库的主题对象里有几条可直接读取的数值,我先把它们变成断言,再去外部测量验证——同一件事由两个互相独立的来源各证一次:

主题里的声明(代码侧)外部测量(布局侧)
style.minHeight = 50收起态容器高度 150px = 50vp ✓
dropDownContainer.position = 'absolute'展开前后 marker 间距 312 → 312 不变 ⇒ 不推挤 ✓
listItemContainer.height = 40列表项间距 恒 120px = 40vp ✓

5.1 先取真值,再写断言(这次我没做到,付了代价)

写断言前应该先把要断言的对象打印出来。这一轮我跳过了这一步,两条断言靠猜,各错一次:

我猜的实际代价
LANGUAGE 有 9 种语言代码8 种(AR/EN/ES/FA/ID/IT/RU/TR)一次假失败
图标在 THEMES.LIGHT.ARROW_DOWN在 THEMES.LIGHT.ICONS.ARROW_DOWN(主题是 {ICONS, default} 两键结构)一次假失败 + 多花一轮构建

⇒ 教训:typeof / 键路径 / 计数这三类断言必须先取真值。

5.2 断言要按"真实形态"写:默认导出是对象,不是函数

写这个库的断言时有个坑:它的组件是

function Picker({ ... }) { ... }
export default memo(Picker);

memo() 返回的是对象而不是函数。所以:

typeof DropDownPicker === 'object'      // ✓ 正确
typeof DropDownPicker === 'function'    // ✗ 会失败

而且它没有 defaultProps、也没有 propTypes——所有默认值都写在函数参数的解构默认值里(value = null、items = []、maxHeight = 200、searchable = false、multiple = false、closeAfterSelecting = true……)。

这类"包装过的组件"(memo / forwardRef)一定要先确认 typeof,别凭经验写。


六、实测一:静态契约与主题结构(39 条)

设备上跑 A. 契约与静态 API,读回的关键事实:

· default 导出的 typeof = object
· LANGUAGE 的语言键 = DEFAULT, FALLBACK, ENGLISH, ARABIC, FARSI, TURKISH, RUSSIAN, SPANISH, INDONESIAN, ITALIAN
· LIGHT 主题的键 = ICONS, default
· LIGHT.ICONS 的键 = ARROW_DOWN, ARROW_UP, TICK, CLOSE
· LIGHT.default 的样式键数 = 37
· 恢复后的默认值 = {"mode":"SIMPLE","listMode":"FLATLIST","dir":"AUTO","lang":"EN"}

以下是这一组断言覆盖到的、值得写进结论的东西:

① 枚举默认值(实测值,不是猜的):

常量默认值可选值
MODE.DEFAULT'SIMPLE'SIMPLE / BADGE
LIST_MODE.DEFAULT'FLATLIST'FLATLIST / SCROLLVIEW / MODAL
DROPDOWN_DIRECTION.DEFAULT'AUTO'TOP / BOTTOM / AUTO
LANGUAGE.DEFAULT / FALLBACK'EN' / 'EN'8 种语言代码
THEMES.DEFAULT'LIGHT'LIGHT / DARK

② 静态 setter 有真实的全局副作用。 setMode / setListMode / setDropDownDirection / setLanguage 都是改全局默认值,改完可以读回:

DropDownPicker.setMode('BADGE');
DropDownPicker.MODE.DEFAULT;        // 'BADGE'   ← 真的变了

⇒ 这意味着它们会影响此后所有未显式传该 prop 的实例。测试里我改完立刻恢复原值,并断言"已恢复"。

③ addTheme 能把自定义主题注册进 THEMES(可读回),addTranslation / modifyTranslation 不抛错。

④ 主题就是一个 {ICONS, default} 两键对象:ICONS 放 4 张 PNG 资源,default 放 37 条样式。这个结构本身值得记录——想改主题时应该照这个形状给。

在这里插入图片描述


七、实测二:几何与浮层(marker 夹逼 + 主题互证)

7.1 方法:marker 夹逼

页面上每个 Picker 的前后各放一个文本 marker:

MARK-P1-START
[ Picker 1 ]
P1 已选=… open=…
MARK-P1-END

Y(MARK-P1-END) − Y(MARK-P1-START) 就是这一段的实际高度。这个差值与滚动位置、marker 自身高度都无关,所以它是稳定的量。

7.2 结果

观测数值结论
收起态区间距312拆开:marker(54) + 容器上下 padding(48) + Picker 容器 150 = 50vp + 状态行(60)
点开后区间距312(完全不变)⇒ 浮层,不推挤下方内容(绝对定位)
列表项 Y209 / 329 / 449 / 569 / 689间距恒 120px = 40vp

三条全部与主题里的声明吻合(minHeight 50、position 'absolute'、listItemContainer.height 40)。

视觉上一眼可辨:下拉展开时是盖住下面那个 Picker,而不是把它推下去。

为什么这个判定值得单独做:如果下拉是"把内容推下去"的实现,那么在长页面里就会出现"点开下拉,下面的按钮跳走"的体验问题;而绝对定位浮层则要额外注意 zIndex(主题里默认给了 zIndex: 1000,库还提供 zIndex / zIndexInverse 两个 prop 来调层级)。这两种实现的注意事项完全不同,所以必须先测出来是哪一种。


八、实测三:把"看着像缺陷"的东西测成事实

这是本轮最有价值的一段。

8.1 疑点:方向映射看着是反的

源码里:

export const GET_DROPDOWN_DIRECTION = (direction) => {
    switch (direction) {
        case DROPDOWN_DIRECTION.AUTO:   return 'top';
        case DROPDOWN_DIRECTION.TOP:    return 'bottom';   // ← 看着反了
        case DROPDOWN_DIRECTION.BOTTOM: return 'top';      // ← 看着反了
        default: return 'top';
    }
}

而 dropDownDirection 这个 prop 在上游 README 和交付包 README 里都没有任何说明。

只读代码的话,几乎必然会得出结论"上游把 TOP/BOTTOM 搞反了"。我没有直接这么写,而是做了对照实验。

8.2 对照实验:强制两个方向,各带独立 label

页面上放两个 Picker:

  • P5:dropDownDirection={DropDownPicker.DROPDOWN_DIRECTION.TOP}
  • P6:dropDownDirection={DropDownPicker.DROPDOWN_DIRECTION.BOTTOM}

各配独立 label(P5-* / P6-*),这样在布局 dump 里能确定某个列表项属于哪个 Picker。然后把 Picker 滚到视口中部(上下都留出空间),点开,比较列表首项的 Y 与 picker 的 Y。

8.3 结果(density 3)

强制值列表首项 Ypicker Y相对位置项间距
DROPDOWN_DIRECTION.TOP7621137ABOVE(上方)120, 120
DROPDOWN_DIRECTION.BOTTOM14381303BELOW(下方)120, 120

⇒ 用户视角的语义完全正确:写 TOP 列表就在上方,写 BOTTOM 就在下方。这不是缺陷。

那映射为什么"看着反"? 因为返回的字符串不是"列表在哪边",而是"列表用哪条边去贴 picker":

  • 返回 'bottom' ⇒ 用列表的 bottom 边贴 picker ⇒ 列表在上方 ✓(实测 TOP 对应它)
  • 返回 'top' ⇒ 用列表的 top 边贴 picker ⇒ 列表在下方 ✓(实测 BOTTOM 对应它)

自洽。 这一条如果只读代码写进文章,就是一个假缺陷。

8.4 顺带把 AUTO 的算术也核对了一遍

AUTO 不是静态的,它在打开时按空间重算:

if (isOpen && dropDownDirection === DROPDOWN_DIRECTION.AUTO) {
    const [, y] = await new Promise((resolve) =>
        pickerRef.current.measureInWindow((...args) => resolve(args))
    );
    const size = y + maxHeight + pickerHeight + bottomOffset;
    const direction = size < WINDOW_HEIGHT ? 'top' : 'bottom';
    ...
}

结合前面的结论(内部 'top' = 列表在下方):size 就是"如果向下展开,列表底边会落在哪个 Y"。装得下就向下、装不下就向上 ⇒ 逻辑正确。

实测核对:我单独测的那一次,picker 在窗口 y ≈ 548.7 vp,maxHeight 默认 200 vp、pickerHeight 实测 50 vp ⇒ size = 548.7 + 200 + 50 = 798.7 vp;而实测窗口可用高度 705 vp ⇒ 798.7 < 705 为假 ⇒ 取 'bottom'(内部值)⇒ 列表向上,与观测完全一致 ✓

顺带的收获:这说明 measureInWindow 返回的坐标与 Dimensions.get('window').height 单位一致(都是 vp)——否则 AUTO 会选错方向。这是对 RNOH 布局 API 的一次免费交叉验证。

在这里插入图片描述


九、交互:点选与搜索过滤

9.1 完全受控是前提

这个组件的 open / value 都必须是受控的:

const [open, setOpen] = useState(false);
const [value, setValue] = useState<string | null>(null);

<DropDownPicker
  open={open} value={value} items={items}
  setOpen={setOpen} setValue={setValue}
  placeholder="PICK-1-PLACEHOLDER"
/>

open 没有默认值(不传就是 undefined),setOpen / setValue 的默认值是空函数 ⇒ 不接这两个回调,界面不会有任何反应,也不会报错。这是这个库最容易被误判为"不工作"的地方。

(回调名是 onChangeValue,不是 onChange;另有 onOpen / onClose / onDirectionChanged 等。)

9.2 点选

把 Picker 放到视口中部 → 点开 → 列表 5 项全部渲染在 picker 上方(下方空间不足,AUTO 选了向上):

Alpha@638  Beta@758  Gamma@878  Delta@998  Epsilon@1118     (picker 在 1247)

点击 Beta 后,页面状态行变成:

P1 已选=b open=false

⇒ 选中生效(value 变成 b),并且列表自动收起(closeAfterSelecting 默认为 true)。

9.3 搜索过滤

searchable 的 Picker 展开后,布局 dump 里能看到搜索框是一个真正的 TextInput 节点:

TextInput#TextInput(474)@0x… [93,431,1227,523] clickable longClickable scrollable

用应用外注入文本写入 Ga,列表立刻只剩 Gamma:

步骤可见列表项
注入前Alpha, Beta, Gamma, Delta(+ Epsilon 被裁在屏外)
注入 Ga 后Gamma

⇒ 搜索过滤生效。

⚠️ 一条没测到的:用 uitest uiInput keyEvent 2072(DEL)没能清空该输入框(连按 5 次后列表仍只有 Gamma),所以**"清空搜索词后列表恢复全部项"本轮未验证**。

在这里插入图片描述


十、已知约束与本次验证边界

10.1 默认列表模式会在可滚动页面里报警告

现象:展开任意 Picker 后,屏幕底部弹出红色警告:

VirtualizedLists should never be nested inside plain ScrollViews with the same orientation
because it can break windowing and other functionality - use another VirtualizedList-backed container instead.

原因(读源码确认):

listMode = LIST_MODE.DEFAULT,     // = 'FLATLIST'
...
case LIST_MODE.FLATLIST:    return DropDownComponentWrapper(DropDownFlatListComponent);
case LIST_MODE.SCROLLVIEW:  return DropDownComponentWrapper(DropDownScrollViewComponent);

默认的 FLATLIST 会渲染一个 FlatList,而页面又把它放在了同方向的 ScrollView 里 ⇒ 触发 RN 的虚拟化列表嵌套警告。

要如实说明两点:

  1. 这是页面结构(把 Picker 放进同方向 ScrollView)触发的,不一定算库的缺陷;Picker 不在 ScrollView 里就不会有这条警告。
  2. 库本身就提供了规避手段:listMode={DropDownPicker.LIST_MODE.SCROLLVIEW}(或 MODAL)。源码里还有 if (autoScroll && listMode === LIST_MODE.SCROLLVIEW) ⇒ autoScroll 只在 SCROLLVIEW 模式下生效。

⚠️ 本轮未验证规避效果(改 listMode 需要再改一次页面并重编,为控制时长没有做)。

10.2 "看着像缺陷"的东西不要直接写成缺陷

这一轮最大的方法论收获就是第八节:GET_DROPDOWN_DIRECTION 的映射读代码像 bug、实测是对的。

正确做法:只要一个结论能让被测对象"显得有问题",就先设计一个能产生确定差异的对照组(这里就是"强制两个方向各来一个实例"),用外部可观测的量(列表首项 Y vs picker Y)判定,再下结论。

10.3 本次验证的边界

  1. 只在一台模拟器上验证(Pura X View,density 3,1320×2232),没有真机。
  2. LIST_MODE.SCROLLVIEW / MODAL 是否消除该警告未测;autoScroll 未测。
  3. 清空搜索框后列表是否恢复未测(DEL 按键没能清空输入框)。
  4. **多选(multiple)**只验证了"初始为空数组"与受控语义,没有实际勾选多项。
  5. 分类(parent 子项)、BADGE 模式、MODAL 模式、DARK 主题切换、language 切换后的文案、RTL、disabled、自定义 renderListItem / renderBadgeItem 均未测。
  6. 回调触发次数:onOpen / onChangeValue 只有"记录格式正确"的断言,没有"确实被触发 N 次"的强断言(因为"跑全部"发生在交互之前,运行时计数为 0)。
  7. 长列表与虚拟化滚动行为(maxHeight 截断、autoScroll 滚到选中项)未测。
  8. 没有跨平台对照(未在 iOS/Android 上跑同一套用例)。

未改动库代码。


十一、常见问题

Q1:为什么我传了 items 界面却完全不动?

因为它是完全受控组件。open / value 必须由父组件持有,并且要把 setOpen / setValue 传进去:

const [open, setOpen] = useState(false);
const [value, setValue] = useState(null);
<DropDownPicker open={open} setOpen={setOpen} value={value} setValue={setValue} items={items} />

不传这两个回调不会报错,只是没反应——这是最容易误判成"库坏了"的一点。

Q2:收起状态的高度怎么来的?

主题里 style.minHeight = 50,实测容器高度正是 50vp(density 3 下 150 物理像素)。想改高度,改主题或传 style / containerStyle。

Q3:下拉会把我下面的内容推下去吗?

不会。 主题里 dropDownContainer.position = 'absolute',实测展开前后 marker 间距完全不变(312 → 312)⇒ 它是浮层。

正因如此要留意层级:主题默认 zIndex: 1000,库还提供 zIndex / zIndexInverse 两个 prop 来调整多个 Picker 之间的覆盖顺序。

Q4:列表项高度是多少?

40vp(主题里 listItemContainer.height = 40),实测相邻项间距恒为 120 物理像素 = 40vp。

Q5:dropDownDirection 该传 TOP 还是 BOTTOM?

按你的直觉传就行,语义是正确的:TOP ⇒ 列表在上方,BOTTOM ⇒ 列表在下方(实测确认)。

AUTO(默认)会在每次打开时按可用空间自动决定:它算 pickerY + maxHeight + pickerHeight + bottomOffset,若超出窗口高度就向上、否则向下。

Q6:源码里 GET_DROPDOWN_DIRECTION 把 TOP 返回成 'bottom',是 bug 吗?

不是。 那个返回值是内部用的锚定边含义(返回 'bottom' = 把列表的 bottom 边贴到 picker 上 = 列表在上方)。实测两个方向都符合用户直觉,所以不要按字面去理解它。这个函数也不是公开 API。

Q7:为什么展开时屏幕上出现 VirtualizedLists should never be nested… 的红色警告?

默认 listMode = 'FLATLIST' 会渲染一个 FlatList,而你的页面又把它放在了同方向的 ScrollView 里。改法:

<DropDownPicker
  listMode={DropDownPicker.LIST_MODE.SCROLLVIEW}   // 或 MODAL
  open={open} setOpen={setOpen} value={value} setValue={setValue} items={items}
/>

(SCROLLVIEW 模式下 autoScroll 才生效。)

Q8:怎么换主题 / 加自定义主题?

静态赋默认,或按实例传:

DropDownPicker.setTheme('DARK');                        // 改全局默认
DropDownPicker.addTheme('MY', {ICONS: {...}, default: StyleSheet.create({...})});

主题对象的形状是 {ICONS, default}:ICONS 放 4 个图标资源(ARROW_DOWN / ARROW_UP / TICK / CLOSE),default 放样式(浅色主题实测 37 条)。

Q9:静态 setter 会影响哪些实例?

setMode / setListMode / setDropDownDirection / setLanguage / setTheme 改的都是全局默认值,影响此后所有未显式传该 prop 的实例。所以要么在应用启动时统一设一次,要么每个实例显式传 prop。

Q10:安装要注意什么?

零依赖,三条就够:装上包、把库目录加进 watchFolders(file: 装法是软链)、注册页面。不需要 HAR、权限、ohpm、link-harmony。


小结

react-native-dropdown-picker 是一个纯 JS、零运行时依赖的组件库:25/26 个文件与上游逐字符一致,接入只需三处,设备侧 48 / 48 断言全过,几何、浮层、方向、点选、搜索过滤全部实测正常。

真正值得记下来的是这几条:

  1. "看着像缺陷"要先做对照组,再决定要不要写。 源码把 DROPDOWN_DIRECTION.TOP 映射成 'bottom',读起来像方向搞反了;用"强制两个方向各一个实例、比较列表首项与 picker 的 Y"一测,用户语义完全正确——那个返回值是"锚定边"。只读代码就会写出一个假缺陷。

  2. 包装过的组件要先确认 typeof。 这个库的默认导出是 memo(Picker),是对象不是函数;而且没有 defaultProps / propTypes,默认值全在参数解构里。typeof / 键路径 / 计数这三类断言,写之前必须先把真值打出来(这轮我跳过了,两条断言各错一次,多花了一轮构建)。

  3. 让代码侧的声明变成"预言",再用外部测量验证一次。 主题里的 minHeight = 50、position = 'absolute'、listItemContainer.height = 40 被我写成断言,又被 marker 夹逼的外部测量独立证实。两个独立来源各证一次,比任何一个单独都可信。

  4. "浮层"还是"推挤"必须实测,因为它决定注意事项完全不同。 实测是浮层(312 → 312 不变),所以要关心 zIndex;如果是推挤,要关心的是下方内容跳动。

  5. 资源计数是便宜且有效的自检。 Copied 13 assets = 原 5 + 本库 8 个 PNG,精确对上,一条命令确认资源全部打包。

  6. 文档缺失要单独列出来。 这个库的三个关键约束(默认 FlatList 的嵌套警告、dropDownDirection 无文档、组件完全受控)在交付包 README 里一个都没写,而不受控的那条不会报错、只会"没反应"——这类问题最难排查。

  7. 同样是"缺字段",性质可能不同。 这个交付包的 spec.json 缺 upstreamCommit,但 npm 元数据里明确有 gitHead ⇒ 属于应当修正的疏漏;而有的包 npm 确实不提供 gitHead,那就不是疏漏。判断前先查数据源。


本篇用到的库

项内容
三方库react-native-dropdown-picker(上游 5.4.6 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-dropdown-picker
适配 TAG5.4.6-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS,零运行时依赖)
库类型纯 JS 组件库:单选/多选、可分类、可搜索的下拉选择器
运行时依赖零(dependencies: {});peerDeps 只有 react / react-native
图标资源自带 8 个 PNG(light/dark 各 4),不依赖 vector-icons / svg
上游仓库https://github.com/hossein-zare/react-native-dropdown-picker(MIT)
上游基线npm gitHead = d25287361f0b2391078a3f2517b4f5e60cd6b6cf
宿主工程RNOH084Demo(测试页 rnAppKey = DropdownPickerTestApp)

接入方式(三处,无语生接线):

# 从适配仓库安装(git+ 会 clone 成真目录)
npm install "git+https://atomgit.com/oh-react-native/react-native-dropdown-picker.git#5.4.6-ohos-1.0.0"

# 或本地 file: 安装(装成软链,需要配 watchFolders)
npm install ../react-native-dropdown-picker
// metro.config.js —— file: 装法是 junction,必须把真实目录加进 watchFolders
watchFolders: [ path.resolve(__dirname, '../react-native-dropdown-picker') ],
// 用法:完全受控;listMode 建议在可滚动页面里显式指定
import DropDownPicker from 'react-native-dropdown-picker';

const [open, setOpen] = useState(false);
const [value, setValue] = useState<string | null>(null);

<DropDownPicker
  open={open} setOpen={setOpen}
  value={value} setValue={setValue}
  items={[{label: 'Alpha', value: 'a'}, {label: 'Beta', value: 'b'}]}
  placeholder="请选择"
  searchable
  listMode={DropDownPicker.LIST_MODE.SCROLLVIEW}
  dropDownDirection={DropDownPicker.DROPDOWN_DIRECTION.AUTO}
  onChangeValue={(v) => console.log('selected', v)}
/>;
// 静态配置(改的是全局默认值,影响此后未显式传该 prop 的实例)
DropDownPicker.setTheme('DARK');
DropDownPicker.setLanguage('EN');
DropDownPicker.addTheme('MY', {ICONS: {...}, default: StyleSheet.create({...})});
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey DropdownPickerTestApp

验证环境

项版本
React Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npmv24.14.0 / 11.9.0
DevEco Studio26.0.0.621
HarmonyOS SDKAPI 26(26.0.0.32)
设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器 Pura X View(ohos-x64),1320×2232,density 3
宿主 HAP 产物entry-default-signed.hap(81,984,706 字节)
本次增量构建assembleHap 5 分 26 秒 / 重编 3 分 37 秒,HAP +189,498 字节;打包资源 5 → 13
验证规模设备侧 48 / 48 断言全部通过;外部协议含 marker 夹逼、强制方向对照 2 例、点选 1 次、搜索过滤 1 次、布局 dump 多次

欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN

React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native

RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐