当前Pull Request已关闭, 关闭人@mazheng
感谢提交 Pull Requests !此PR未通过DCO校验。
校验失败可能原因:
1. 未签署“DCO协议”(开发者原创声明协议),在线签署、查看签署状态。
2. Commits 中未包含 Signed-off-by信息,参考FAQ处理。
修复上述问题后,在PR的评论框输入“check dco” ,单击”评论”,系统将再次进行DCO校验。
当前检测到如下Commits 未包含Signed-off-by信息:
Thanks for submitting a pull request. This pull request has not passed the DCO check.
Possible causes:
1. You have not signed the Developer Certificate of Origin (DCO). Sign the DCO and check DCO status.
2. The commits do not contain the Signed-off-by information. To resolve this issue, see FAQs.
After resolving the preceding issues, enter check dco in the comment box of this pull request and click Comment. The system will check DCO status again.
The following commits do not contain the Signed-off-by information:


感谢提交 Pull Requests!
Thanks for submitting a pull request.


代码有更新,重置PR验证状态


感谢提交 Pull Requests!
Thanks for submitting a pull request.


修改点
本 PR 仅改动
src/modal.tsx一个文件。1. 关闭/打开时序的 TypeError 防护
isUnmounted,在componentWillUnmount()中置为true;同时把contentRef/backdropRef置空,让卸载后仍触发的回调成为 no-op。close()的setAnimation回调开头增加守卫:contentRef为空或已卸载时,直接走finalizeClose()而不去解引用contentRef.startAnimation。open()链路上的requestAnimationFrame回调、setAnimation回调、startAnimation完成回调、以及 backdrop 之后的第二个rAF回调,各自增加isUnmounted/contentRef守卫。2. 打开/关闭生命周期兜底,避免
isTransitioning卡死finalizeClose():把原先内联在startAnimation完成回调里的finalizeClose闭包提为类成员方法,供正常路径与降级路径复用。completeOpenWithoutAnimation():动画宿主不可用时,跳过内容动画直接完成打开流程(清理interactionHandle、复位isTransitioning、setState后回调onModalShow)。open()中contentRef为空、close()中contentRef为空时,分别走上述降级路径;close()的降级路径补发onModalWillHide。3.
panResponderThreshold真正生效onStartShouldSetPanResponder中移除onSwipeStart的触发(触点按下时位移为 0,此时并未开始滑动)。onPanResponderMove中,累计位移|dx|/|dy|均小于panResponderThreshold时直接 return:不移动模态、不发onSwipeStart/onSwipeMove;跨过阈值后再触发onSwipeStart。4.
supportedOrientations约束(HarmonyOS 模拟)constrainToSupportedOrientation(width, height):仅在Platform.OS === 'harmony'时生效;当前方向不被supportedOrientations支持时交换宽高。constrainDimensionsToSupportedOrientation(),在open()开头调用;handleDimensionsUpdate()也接入同一约束。5. 滑动百分比改用真实窗口尺寸
getGestureDeviceHeight()/getGestureDeviceWidth():取props.deviceHeight/Width或Dimensions.get('window'),不经过方向约束。calcDistancePercentage()的down/right分支改用这两个方法;布局仍沿用受方向约束的getDeviceWidth()/getDeviceHeight()。6.
hideModalContentWhileAnimating语义修正shouldHideContent由!showContent || (isTransitioning && !animationReady)改为!showContent || (isTransitioning && (props.hideModalContentWhileAnimating || !animationReady))。7. CodeCheck 圈复杂度整改(
15b332e)open()内层层嵌套的requestAnimationFrame/setAnimation/startAnimation回调提取为类成员方法:resetBackdropAnimation/resetContentAnimation/startOpenAnimation/handleOpenAnimationPrepared/handleOpenAnimationEnd/revealContent;open()只保留同步流程并改用提前 return 守卫;close()复用resetContentAnimation消除重复。open()圈复杂度 24 → 6,close()18 → 16。守卫条件、执行顺序、_pendingRAF入队时机均保持不变。根因分析
① 关闭模态时
cannot read property 'startAnimation' of nullclose()的原实现:this.contentRef.setAnimation(animationOut, () => { ... this.contentRef.startAnimation(...) // ← 这里 });react-native-animatable的setAnimation内部通过setState提交animationStyle,回调在该setState完成后才执行。在 RNOH 上,原生 Modal 宿主可能在这个窗口期内被平台 dismiss,React 随即把contentRef置为null;回调此时再解引用this.contentRef.startAnimation就抛 TypeError。open()链路上的setAnimation/startAnimation/requestAnimationFrame回调存在同样的时间窗口。而且这些回调还会setState,组件卸载后执行会产生 setState-after-unmount 告警。②
isTransitioning卡死导致模态彻底失效open()/close()都以if (this.isTransitioning) return;作为入口守卫,并在进入时置true,只在动画完成回调里复位为false。原实现中,
contentRef为空时:open()的if (this.contentRef) {...}整块被跳过,没有 else 分支——isTransitioning永远停在true。close()同理。一旦落入这个状态,此后所有
open()/close()调用都会被入口守卫挡掉,模态彻底不可用,且onModalShow/onModalHide再也不会触发。这比 ① 的 TypeError 影响更持久——TypeError 至少还能在日志里看见。③
panResponderThreshold声明了但从未参与判断defaultProps里有panResponderThreshold: 4,propTypes里也声明了,但代码中没有任何一处读取它。实际行为是:只要onStartShouldSetPanResponder返回 true,onSwipeStart立刻触发,随后任意微小位移都会移动模态——阈值形同虚设,轻点内容区就可能把模态拖动一下。而且
onSwipeStart被放在onStartShouldSetPanResponder(触点按下)里触发,语义也不对:按下时dx/dy都是 0,滑动尚未开始。④
supportedOrientations在 HarmonyOS 上无任何约束该 prop 在 iOS 上由原生 Modal 呈现层强制执行,在 Android 上是文档明确的 no-op,而 HarmonyOS 侧既没有原生支持也没有 JS 模拟,等于完全忽略。
⑤ 滑动百分比分母可能为负(本 PR 内的二次修正,
0c883f0)constrainToSupportedOrientation()在当前方向不被支持时会交换宽高,交换后的值经constrainDimensionsToSupportedOrientation()/handleDimensionsUpdate()写入state.deviceWidth/deviceHeight。而
calcDistancePercentage()用的gestureState.y0/moveY(及x0/moveX)是真实屏幕坐标。拿真实坐标去除以交换后的尺寸会得到错误的百分比;当触点超过交换后的边界时分母还会变负,导致newOpacityFactor > 1、遮罩透明度异常,onSwipeMove也会收到超出[0,1]的百分比。举例:400×800 竖屏 +
supportedOrientations: ['landscape']+swipeDirection: 'down',从y0 = 600下滑,(moveY - 600) / (400 - 600)得-0.5,factor 为 1.5。⑥
hideModalContentWhileAnimating被 ohos 闪屏修复覆盖为修复 RNOH 打开时的一帧闪屏,
shouldHideContent引入了!animationReady条件。但这条件把hideModalContentWhileAnimating的原语义(为 true 时整个过渡期间都隐藏内容)挤掉了:animationReady一旦为 true,即使hideModalContentWhileAnimating为 true,内容也会显示出来。方案说明(AI输出)
卸载守卫:采用「实例标志 + 置空 ref」双重防护。
isUnmounted覆盖 React 已卸载的情况,置空contentRef/backdropRef覆盖宿主被平台 dismiss 但组件尚未卸载的情况——后者正是 RNOH 上的典型场景,单靠isUnmounted挡不住。降级路径:所有守卫命中时不是简单
return,而是走completeOpenWithoutAnimation()/finalizeClose()。这一点很关键——直接 return 会把isTransitioning留在true,正好落进根因 ② 的死锁。降级路径保证isTransitioning一定复位、interactionHandle一定释放、onModalShow/onModalHide一定触发,代价只是丢掉这一次动画。finalizeClose由内联闭包提为类成员方法,是为了让正常路径与降级路径跑完全相同的收尾逻辑,避免两套实现漂移。阈值判断放在
onPanResponderMove:onStartShouldSetPanResponder阶段没有位移信息,无法做阈值判断。放在 move 里用累计dx/dy判断,同时把onSwipeStart一并挪过来,使「阈值」与「滑动开始」两个概念在时间点上对齐。阈值未跨过时整体 return,保证模态不动、回调不发,轻点内容区不会误触发滑动。supportedOrientations:限定Platform.OS === 'harmony'才生效。iOS 由原生强制执行、Android 是文档化的 no-op,JS 模拟若无平台判断会改变这两个平台的既有行为。滑动尺寸与布局尺寸分离:不回退方向约束,而是引入一组只用于手势计算的尺寸方法。布局需要受约束的几何(这正是
supportedOrientations模拟的目的),手势数学需要真实窗口空间(因为gestureState就是真实坐标)——两者本就是不同的量,分开取值比在某一处做补偿更直接,也不改变supportedOrientations的模拟行为。圈复杂度整改:严格按"提取方法"做纯结构调整,不动任何守卫条件、执行顺序和
_pendingRAF入队时机,便于 review 逐段比对。测试情况
新增测试用例(按需)
触发的已有测试用例(按需)
br_rnoh0.72分支example/src/→ Modal 示例页 → ✅ 通过手动验证
cannot read property 'startAnimation' of null。isTransitioning正确复位,后续open()/close()仍可用,onModalShow/onModalHide正常触发。panResponderThreshold生效:小于阈值的轻微拖动不移动模态、不触发onSwipeStart/onSwipeMove;跨过阈值后正常进入滑动。supportedOrientations约束下打开模态,呈现方向符合声明。supportedOrientations: ['landscape']+swipeDirection: 'down'场景下滑,onSwipeMove百分比落在[0,1],遮罩透明度正常(此前为 1.5)。hideModalContentWhileAnimating为 true 时整个过渡期间内容保持隐藏;为 false 时 ohos 闪屏防护仍生效。avoidKeyboard、customBackdrop、onBackdropPress、onBackButtonPress等行为无变化;15b332e为纯结构调整。f9d2a68修正)资料修改
panResponderThreshold与supportedOrientations在 HarmonyOS 上由"未实现"变为"已实现(JS 模拟)",README 的属性支持表建议同步更新,本 PR 暂未改动。src/modal.tsx,package.json仍为13.0.3-rc.1,未新增 CHANGELOG 条目(本仓库当前无CHANGELOG.md)。建议合入前补上版本号与变更记录。依赖关系变更
src/modal.tsx一个文件。onSwipeStart触发时机由「触点按下」推迟到「累计位移跨过panResponderThreshold」。hideModalContentWhileAnimating为 true 时,过渡期间内容保持隐藏。Checklist