React Native for OpenHarmony 实战:三方库 react-native-dropdown-picker 的鸿蒙化适配指南
本文记录把 react-native-dropdown-picker 适配到 HarmonyOS 的完整过程。
这个库是一个功能很全的下拉选择器:单选/多选、可分类、可搜索、可换主题、可多语言、支持 RTL。它是纯 JS 库且零运行时依赖,所以适配本身很轻——难点不在"能不能跑",而在"怎么证明它跑对了"。
因为它有两处结论只能靠跑起来才知道:
- 源码里方向映射
DROPDOWN_DIRECTION.TOP → 'bottom'看着是反的,而两个 README 对这个 prop 一个字都没写; - 它的默认列表模式会在可滚动页面里弹出一条红屏警告。
这两条如果只读代码,很容易写出错误结论。下面把它们都测成事实。

一、先说结论
| 项 | 结果 |
|---|---|
| 上游最新版 | 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 真的有效吗? | 在设备上读回对象与 *.DEFAULT | 39 条断言 |
| ② 布局几何 | 收起多高?展开后推挤还是浮层?列表项多高? | 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(完全不变) | ⇒ 浮层,不推挤下方内容(绝对定位) |
| 列表项 Y | 209 / 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)
| 强制值 | 列表首项 Y | picker Y | 相对位置 | 项间距 |
|---|---|---|---|---|
DROPDOWN_DIRECTION.TOP | 762 | 1137 | ABOVE(上方) | 120, 120 |
DROPDOWN_DIRECTION.BOTTOM | 1438 | 1303 | BELOW(下方) | 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 的虚拟化列表嵌套警告。
要如实说明两点:
- 这是页面结构(把 Picker 放进同方向 ScrollView)触发的,不一定算库的缺陷;Picker 不在 ScrollView 里就不会有这条警告。
- 库本身就提供了规避手段:
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 本次验证的边界
- 只在一台模拟器上验证(
Pura X View,density 3,1320×2232),没有真机。 LIST_MODE.SCROLLVIEW/MODAL是否消除该警告未测;autoScroll未测。- 清空搜索框后列表是否恢复未测(DEL 按键没能清空输入框)。
- **多选(
multiple)**只验证了"初始为空数组"与受控语义,没有实际勾选多项。 - 分类(
parent子项)、BADGE模式、MODAL模式、DARK主题切换、language切换后的文案、RTL、disabled、自定义renderListItem/renderBadgeItem均未测。 - 回调触发次数:
onOpen/onChangeValue只有"记录格式正确"的断言,没有"确实被触发 N 次"的强断言(因为"跑全部"发生在交互之前,运行时计数为 0)。 - 长列表与虚拟化滚动行为(
maxHeight截断、autoScroll滚到选中项)未测。 - 没有跨平台对照(未在 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 断言全过,几何、浮层、方向、点选、搜索过滤全部实测正常。
真正值得记下来的是这几条:
-
"看着像缺陷"要先做对照组,再决定要不要写。 源码把
DROPDOWN_DIRECTION.TOP映射成'bottom',读起来像方向搞反了;用"强制两个方向各一个实例、比较列表首项与 picker 的 Y"一测,用户语义完全正确——那个返回值是"锚定边"。只读代码就会写出一个假缺陷。 -
包装过的组件要先确认
typeof。 这个库的默认导出是memo(Picker),是对象不是函数;而且没有defaultProps/propTypes,默认值全在参数解构里。typeof/ 键路径 / 计数这三类断言,写之前必须先把真值打出来(这轮我跳过了,两条断言各错一次,多花了一轮构建)。 -
让代码侧的声明变成"预言",再用外部测量验证一次。 主题里的
minHeight = 50、position = 'absolute'、listItemContainer.height = 40被我写成断言,又被 marker 夹逼的外部测量独立证实。两个独立来源各证一次,比任何一个单独都可信。 -
"浮层"还是"推挤"必须实测,因为它决定注意事项完全不同。 实测是浮层(312 → 312 不变),所以要关心
zIndex;如果是推挤,要关心的是下方内容跳动。 -
资源计数是便宜且有效的自检。
Copied 13 assets= 原 5 + 本库 8 个 PNG,精确对上,一条命令确认资源全部打包。 -
文档缺失要单独列出来。 这个库的三个关键约束(默认 FlatList 的嵌套警告、
dropDownDirection无文档、组件完全受控)在交付包 README 里一个都没写,而不受控的那条不会报错、只会"没反应"——这类问题最难排查。 -
同样是"缺字段",性质可能不同。 这个交付包的
spec.json缺upstreamCommit,但 npm 元数据里明确有gitHead⇒ 属于应当修正的疏漏;而有的包 npm 确实不提供gitHead,那就不是疏漏。判断前先查数据源。
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-dropdown-picker(上游 5.4.6 的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-dropdown-picker |
| 适配 TAG | 5.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 Native | 0.84.1 |
| React | 19.2.3 |
| RNOH(npm / ohpm) | @react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3 |
| Node.js / npm | v24.14.0 / 11.9.0 |
| DevEco Studio | 26.0.0.621 |
| HarmonyOS SDK | API 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
更多推荐




所有评论(0)