From fb73c639824be97625ae45fcb67a9671f1570ad1 Mon Sep 17 00:00:00 2001 From: zoomchan-cxj Date: Sun, 12 Dec 2021 20:28:42 +0800 Subject: [PATCH] feat(hippy-react): change event capture handle --- docs/hippy-react/gesture.md | 16 +++++++++++----- .../src/components/ListView/index.jsx | 13 ++++++++++++- packages/hippy-react/src/events/dispatcher.ts | 15 ++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/hippy-react/gesture.md b/docs/hippy-react/gesture.md index 96912547880..d92d839fdaa 100644 --- a/docs/hippy-react/gesture.md +++ b/docs/hippy-react/gesture.md @@ -142,12 +142,15 @@ new Hippy({ ## 事件捕获 -> 最低支持版本 2.11.2 +> 最低支持版本 2.11.5 [[事件捕获范例]](//github.com/Tencent/Hippy/tree/master/examples/hippy-react-demo/src/components/ListView) -点击事件和触屏事件支持事件捕获,如需注册捕获阶段的事件处理函数,则应在目标元素事件名添加 `Capture` 后缀,如 `onClickCapture`、`onTouchDownCapture`。如果目标元素没有`Capture` -事件处理函数,默认不开启捕获。事件捕获会有一定性能损耗,如非必要尽量不开启。 +点击事件和触屏事件支持事件捕获,如需注册捕获阶段的事件处理函数,则应在目标元素事件名添加 `Capture` 后缀,如 `onClickCapture`、`onTouchDownCapture`。 + +Hippy为了做更好的性能优化,如果目标元素没有 `Capture` 事件处理函数,默认不开启捕获,全局冒泡配置 `bubbles: false` 不会影响捕获开启。事件捕获设计与 Web 标准一致,当在任意一个捕获函数内调用 `stopPropagation` 时,会同时阻止剩余的捕获阶段、目标节点阶段和冒泡阶段执行。 + +!> 事件捕获会有一定性能损耗,如非必要尽量不开启。 例子如下: @@ -159,14 +162,17 @@ render() onClick={() => { console.log("根节点 点击"); }} - onClickCapture={() => console.log("根节点 捕获点击")} + onClickCapture={(event) => { + // 如果根节点调用 stopPropagation,则按钮2的 onClickCapture 和按钮1的 onClick 都不会触发 + // event.stopPropagation(); + console.log("根节点 捕获点击") + }} > { // 点击按钮1不会触发根节点捕获点击 console.log("按钮1 点击") }} - > 点击按钮1 diff --git a/examples/hippy-react-demo/src/components/ListView/index.jsx b/examples/hippy-react-demo/src/components/ListView/index.jsx index f87b4052572..7b9bb3ec1ae 100644 --- a/examples/hippy-react-demo/src/components/ListView/index.jsx +++ b/examples/hippy-react-demo/src/components/ListView/index.jsx @@ -216,6 +216,14 @@ export default class ListExample extends React.Component { } return ( { + console.log('onClickCapture style outer', event.target.nodeId, event.currentTarget.nodeId); + }} + onTouchDown={(event) => { + // outer onTouchDown would not be called, because style1 invoked event.stopPropagation(); + console.log('onTouchDown style outer', event.target.nodeId, event.currentTarget.nodeId); + return false; + }} onClick={(event) => { console.log('click style outer', event.target.nodeId, event.currentTarget.nodeId); // return false means trigger bubble @@ -252,12 +260,15 @@ export default class ListExample extends React.Component { console.log('onTouchDown ListView', event.target.nodeId, event.currentTarget.nodeId); }} onClickCapture={(event) => { + // if calling capture event stopPropagation in one of node, + // all capture phase left, target phase and bubbling phase would stop. + // event.stopPropagation(); console.log('onClickCapture listview', event.target.nodeId, event.currentTarget.nodeId); }} onClick={(event) => { console.log('click listview', event.target.nodeId, event.currentTarget.nodeId); // return false means trigger bubble - return false; + return true; }} bounces={true} overScrollEnabled={true} diff --git a/packages/hippy-react/src/events/dispatcher.ts b/packages/hippy-react/src/events/dispatcher.ts index d562ad57af9..c0da641fa6f 100644 --- a/packages/hippy-react/src/events/dispatcher.ts +++ b/packages/hippy-react/src/events/dispatcher.ts @@ -141,10 +141,14 @@ function doCaptureAndBubbleLoop(originalEventName: string, nativeEvent: NativeEv currentTarget: getElementFromFiber(nextNodeItem), }); } - nextNodeItem = nextNodeItem.return; - while (nextNodeItem && !isHostComponent(nextNodeItem.tag)) { - // only handle HostComponent + if (eventQueue.length === 0) { + nextNodeItem = null; + } else { nextNodeItem = nextNodeItem.return; + while (nextNodeItem && !isHostComponent(nextNodeItem.tag)) { + // only handle HostComponent + nextNodeItem = nextNodeItem.return; + } } } if (eventQueue.length > 0) { @@ -156,8 +160,13 @@ function doCaptureAndBubbleLoop(originalEventName: string, nativeEvent: NativeEv const { eventName, currentTarget: currentTargetNode, listener, isCapture } = listenerObj; const syntheticEvent = new Event(eventName, currentTargetNode, targetNode); Object.assign(syntheticEvent, nativeEvent); + // whether it is capture or bubbling event, returning false or calling stopPropagation would both stop phase if (isCapture) { listener(syntheticEvent); + // event bubbles flag has higher priority + if (!syntheticEvent.bubbles) { + isStopBubble = true; + } } else { isStopBubble = listener(syntheticEvent); // If callback have no return, use global bubble config to set isStopBubble.