From f2657f8cb6795f44e78b5600537a5bfe5ab69330 Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Fri, 17 May 2024 17:52:20 +0200 Subject: [PATCH 1/8] make animated component's event tag properly update --- .../createAnimatedComponent.tsx | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/createAnimatedComponent/createAnimatedComponent.tsx b/src/createAnimatedComponent/createAnimatedComponent.tsx index 2488ebca7bfa..4c7ea0922504 100644 --- a/src/createAnimatedComponent/createAnimatedComponent.tsx +++ b/src/createAnimatedComponent/createAnimatedComponent.tsx @@ -144,8 +144,8 @@ export function createAnimatedComponent( } componentDidMount() { - this._setComponentViewTag(); - this._setEventViewTag(); + this._componentViewTag = this._getComponentViewTag(); + this._eventViewTag = this._getEventViewTag(); this._attachNativeEvents(); this._jsPropsUpdater.addOnJSPropsChangeListener(this); this._attachAnimatedStyles(); @@ -226,22 +226,24 @@ export function createAnimatedComponent( } } - _setComponentViewTag() { - this._componentViewTag = this._getViewInfo().viewTag as number; + _getComponentViewTag() { + return this._getViewInfo().viewTag as number; } - _setEventViewTag() { - // Setting the tag for registering events - since the event emitting view can be nested inside the main component + _getEventViewTag() { + // Get the tag for registering events - since the event emitting view can be nested inside the main component const componentAnimatedRef = this._component as AnimatedComponentRef; + let newTag: number; if (componentAnimatedRef.getScrollableNode) { const scrollableNode = componentAnimatedRef.getScrollableNode(); - this._eventViewTag = findNodeHandle(scrollableNode) ?? -1; + newTag = findNodeHandle(scrollableNode) ?? -1; } else { - this._eventViewTag = + newTag = findNodeHandle( options?.setNativeProps ? this : componentAnimatedRef ) ?? -1; } + return newTag; } _attachNativeEvents() { @@ -291,6 +293,29 @@ export function createAnimatedComponent( _updateNativeEvents( prevProps: AnimatedComponentProps ) { + // If the event view tag changes, we need to completely re-mount all events + const computedEventTag = this._getEventViewTag(); + if (this._eventViewTag !== computedEventTag) { + // Remove all bindings from previous props that ran on the old viewTag + for (const key in prevProps) { + const prevProp = prevProps[key]; + if ( + has('workletEventHandler', prevProp) && + prevProp.workletEventHandler instanceof WorkletEventHandler + ) { + prevProp.workletEventHandler.unregisterFromEvents( + this._eventViewTag + ); + } + } + // We don't need to unregister from current (new) props, because their events weren't registered yet + // Replace the view tag + this._eventViewTag = computedEventTag; + // Attach the events with a new viewTag + this._attachNativeEvents(); + return; + } + for (const key in prevProps) { const prevProp = prevProps[key]; if ( From 0287870c2a0c4692fae99747ec06f24a137e78dd Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Tue, 21 May 2024 22:10:17 +0200 Subject: [PATCH 2/8] add NativeEventsManager --- .../NativeEventsManager.ts | 118 ++++++++++++++++++ src/createAnimatedComponent/commonTypes.ts | 10 ++ .../createAnimatedComponent.tsx | 105 ++-------------- 3 files changed, 139 insertions(+), 94 deletions(-) create mode 100644 src/createAnimatedComponent/NativeEventsManager.ts diff --git a/src/createAnimatedComponent/NativeEventsManager.ts b/src/createAnimatedComponent/NativeEventsManager.ts new file mode 100644 index 000000000000..37323955d622 --- /dev/null +++ b/src/createAnimatedComponent/NativeEventsManager.ts @@ -0,0 +1,118 @@ +'use strict'; +import type { + INativeEventsManager, + IAnimatedComponentInternal, + AnimatedComponentProps, + InitialComponentProps, +} from './commonTypes'; +import { has } from './utils'; +import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler'; +import { isWeb } from '../reanimated2/PlatformChecker'; + +const IS_WEB = isWeb(); + +type ManagedAnimatedComponent = React.Component< + AnimatedComponentProps +> & + IAnimatedComponentInternal; + +type WorkletEventHandlerProp = { + workletEventHandler: InstanceType; +}; + +function isWorkletEventHandler(prop: unknown): prop is WorkletEventHandlerProp { + return ( + has('workletEventHandler', prop) && + prop.workletEventHandler instanceof WorkletEventHandler + ); +} + +function executeForEachEventHandler( + props: AnimatedComponentProps, + callback: ( + key: string, + handler: InstanceType + ) => void +) { + for (const key in props) { + const prop = props[key]; + if (isWorkletEventHandler(prop)) { + callback(key, prop.workletEventHandler); + } + } +} + +export class NativeEventsManager implements INativeEventsManager { + _managedComponent: ManagedAnimatedComponent; + + constructor(component: ManagedAnimatedComponent) { + this._managedComponent = component; + } + + public attachNativeEvents(): void { + if (IS_WEB) { + return; + } + executeForEachEventHandler(this._managedComponent.props, (key, handler) => { + handler.registerForEvents(this._managedComponent._eventViewTag, key); + }); + } + + public detachNativeEvents(): void { + if (IS_WEB) { + return; + } + executeForEachEventHandler( + this._managedComponent.props, + (_key, handler) => { + handler.unregisterFromEvents(this._managedComponent._eventViewTag); + } + ); + } + + public updateNativeEvents( + prevProps: AnimatedComponentProps, + computedEventTag: number + ): void { + if (IS_WEB) { + return; + } + // If the event view tag changes, we need to completely re-mount all events + if (this._managedComponent._eventViewTag !== computedEventTag) { + // Remove all bindings from previous props that ran on the old viewTag + executeForEachEventHandler(prevProps, (_key, handler) => { + handler.unregisterFromEvents(this._managedComponent._eventViewTag); + }); + // We don't need to unregister from current (new) props, because their events weren't registered yet + // Replace the view tag + this._managedComponent._eventViewTag = computedEventTag; + // Attach the events with a new viewTag + this.attachNativeEvents(); + return; + } + + executeForEachEventHandler(prevProps, (key, prevHandler) => { + const newProp = this._managedComponent.props[key]; + if (!newProp) { + // Prop got deleted + prevHandler.unregisterFromEvents(this._managedComponent._eventViewTag); + } else if ( + isWorkletEventHandler(newProp) && + newProp.workletEventHandler !== prevHandler + ) { + // Prop got changed + prevHandler.unregisterFromEvents(this._managedComponent._eventViewTag); + newProp.workletEventHandler.registerForEvents( + this._managedComponent._eventViewTag + ); + } + }); + + executeForEachEventHandler(this._managedComponent.props, (key, handler) => { + if (!prevProps[key]) { + // Prop got added + handler.registerForEvents(this._managedComponent._eventViewTag); + } + }); + } +} diff --git a/src/createAnimatedComponent/commonTypes.ts b/src/createAnimatedComponent/commonTypes.ts index 5f8207e52c00..c9c7457030c9 100644 --- a/src/createAnimatedComponent/commonTypes.ts +++ b/src/createAnimatedComponent/commonTypes.ts @@ -54,6 +54,15 @@ export interface IJSPropsUpdater { ): void; } +export interface INativeEventsManager { + attachNativeEvents(): void; + detachNativeEvents(): void; + updateNativeEvents( + prevProps: AnimatedComponentProps, + computedEventTag: number + ): void; +} + export type LayoutAnimationStaticContext = { presetName: string; }; @@ -105,6 +114,7 @@ export interface IAnimatedComponentInternal { _jsPropsUpdater: IJSPropsUpdater; _InlinePropManager: IInlinePropManager; _PropsFilter: IPropsFilter; + _NativeEventsManager: INativeEventsManager; _viewInfo?: ViewInfo; context: React.ContextType; } diff --git a/src/createAnimatedComponent/createAnimatedComponent.tsx b/src/createAnimatedComponent/createAnimatedComponent.tsx index 4c7ea0922504..52490923de48 100644 --- a/src/createAnimatedComponent/createAnimatedComponent.tsx +++ b/src/createAnimatedComponent/createAnimatedComponent.tsx @@ -8,7 +8,6 @@ import type { } from 'react'; import React from 'react'; import { findNodeHandle, Platform } from 'react-native'; -import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler'; import '../reanimated2/layoutReanimation/animationsManager'; import invariant from 'invariant'; import { adaptViewConfig } from '../ConfigHelper'; @@ -33,8 +32,9 @@ import type { AnimatedComponentRef, IAnimatedComponentInternal, ViewInfo, + INativeEventsManager, } from './commonTypes'; -import { has, flattenArray } from './utils'; +import { flattenArray } from './utils'; import setAndForwardRef from './setAndForwardRef'; import { isFabric, @@ -56,6 +56,7 @@ import type { CustomConfig } from '../reanimated2/layoutReanimation/web/config'; import type { FlatList, FlatListProps } from 'react-native'; import { addHTMLMutationObserver } from '../reanimated2/layoutReanimation/web/domUtils'; import { getViewInfo } from './getViewInfo'; +import { NativeEventsManager } from './NativeEventsManager'; const IS_WEB = isWeb(); @@ -131,6 +132,7 @@ export function createAnimatedComponent( _jsPropsUpdater = new JSPropsUpdater(); _InlinePropManager = new InlinePropManager(); _PropsFilter = new PropsFilter(); + _NativeEventsManager: INativeEventsManager; _viewInfo?: ViewInfo; static displayName: string; static contextType = SkipEnteringContext; @@ -141,12 +143,13 @@ export function createAnimatedComponent( if (isJest()) { this.jestAnimatedStyle = { value: {} }; } + this._NativeEventsManager = new NativeEventsManager(this); } componentDidMount() { this._componentViewTag = this._getComponentViewTag(); this._eventViewTag = this._getEventViewTag(); - this._attachNativeEvents(); + this._NativeEventsManager.attachNativeEvents(); this._jsPropsUpdater.addOnJSPropsChangeListener(this); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); @@ -180,7 +183,7 @@ export function createAnimatedComponent( } componentWillUnmount() { - this._detachNativeEvents(); + this._NativeEventsManager.detachNativeEvents(); this._jsPropsUpdater.removeOnJSPropsChangeListener(this); this._detachStyles(); this._InlinePropManager.detachInlineProps(); @@ -246,30 +249,6 @@ export function createAnimatedComponent( return newTag; } - _attachNativeEvents() { - for (const key in this.props) { - const prop = this.props[key]; - if ( - has('workletEventHandler', prop) && - prop.workletEventHandler instanceof WorkletEventHandler - ) { - prop.workletEventHandler.registerForEvents(this._eventViewTag, key); - } - } - } - - _detachNativeEvents() { - for (const key in this.props) { - const prop = this.props[key]; - if ( - has('workletEventHandler', prop) && - prop.workletEventHandler instanceof WorkletEventHandler - ) { - prop.workletEventHandler.unregisterFromEvents(this._eventViewTag); - } - } - } - _detachStyles() { if (IS_WEB && this._styles !== null) { for (const style of this._styles) { @@ -290,71 +269,6 @@ export function createAnimatedComponent( } } - _updateNativeEvents( - prevProps: AnimatedComponentProps - ) { - // If the event view tag changes, we need to completely re-mount all events - const computedEventTag = this._getEventViewTag(); - if (this._eventViewTag !== computedEventTag) { - // Remove all bindings from previous props that ran on the old viewTag - for (const key in prevProps) { - const prevProp = prevProps[key]; - if ( - has('workletEventHandler', prevProp) && - prevProp.workletEventHandler instanceof WorkletEventHandler - ) { - prevProp.workletEventHandler.unregisterFromEvents( - this._eventViewTag - ); - } - } - // We don't need to unregister from current (new) props, because their events weren't registered yet - // Replace the view tag - this._eventViewTag = computedEventTag; - // Attach the events with a new viewTag - this._attachNativeEvents(); - return; - } - - for (const key in prevProps) { - const prevProp = prevProps[key]; - if ( - has('workletEventHandler', prevProp) && - prevProp.workletEventHandler instanceof WorkletEventHandler - ) { - const newProp = this.props[key]; - if (!newProp) { - // Prop got deleted - prevProp.workletEventHandler.unregisterFromEvents( - this._eventViewTag - ); - } else if ( - has('workletEventHandler', newProp) && - newProp.workletEventHandler instanceof WorkletEventHandler && - newProp.workletEventHandler !== prevProp.workletEventHandler - ) { - // Prop got changed - prevProp.workletEventHandler.unregisterFromEvents( - this._eventViewTag - ); - newProp.workletEventHandler.registerForEvents(this._eventViewTag); - } - } - } - - for (const key in this.props) { - const newProp = this.props[key]; - if ( - has('workletEventHandler', newProp) && - newProp.workletEventHandler instanceof WorkletEventHandler && - !prevProps[key] - ) { - // Prop got added - newProp.workletEventHandler.registerForEvents(this._eventViewTag); - } - } - } - _updateFromNative(props: StyleProps) { if (options?.setNativeProps) { options.setNativeProps(this._component as AnimatedComponentRef, props); @@ -502,7 +416,10 @@ export function createAnimatedComponent( ) { this._configureSharedTransition(); } - this._updateNativeEvents(prevProps); + this._NativeEventsManager.updateNativeEvents( + prevProps, + this._getEventViewTag() + ); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); From 81c0632cd22b7af31a29852afcb8d579f74276f9 Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Tue, 28 May 2024 16:41:54 +0200 Subject: [PATCH 3/8] apply code review requests --- .../NativeEventsManager.ts | 145 +++++++++++------- src/createAnimatedComponent/commonTypes.ts | 6 +- .../createAnimatedComponent.tsx | 33 +--- 3 files changed, 95 insertions(+), 89 deletions(-) diff --git a/src/createAnimatedComponent/NativeEventsManager.ts b/src/createAnimatedComponent/NativeEventsManager.ts index 37323955d622..cbba8c803699 100644 --- a/src/createAnimatedComponent/NativeEventsManager.ts +++ b/src/createAnimatedComponent/NativeEventsManager.ts @@ -4,88 +4,58 @@ import type { IAnimatedComponentInternal, AnimatedComponentProps, InitialComponentProps, + AnimatedComponentRef, } from './commonTypes'; import { has } from './utils'; import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler'; import { isWeb } from '../reanimated2/PlatformChecker'; - -const IS_WEB = isWeb(); - -type ManagedAnimatedComponent = React.Component< - AnimatedComponentProps -> & - IAnimatedComponentInternal; - -type WorkletEventHandlerProp = { - workletEventHandler: InstanceType; -}; - -function isWorkletEventHandler(prop: unknown): prop is WorkletEventHandlerProp { - return ( - has('workletEventHandler', prop) && - prop.workletEventHandler instanceof WorkletEventHandler - ); -} - -function executeForEachEventHandler( - props: AnimatedComponentProps, - callback: ( - key: string, - handler: InstanceType - ) => void -) { - for (const key in props) { - const prop = props[key]; - if (isWorkletEventHandler(prop)) { - callback(key, prop.workletEventHandler); - } - } -} +import { findNodeHandle } from 'react-native'; export class NativeEventsManager implements INativeEventsManager { _managedComponent: ManagedAnimatedComponent; + _componentOptions?: ComponentOptions; + _eventViewTag = 1; - constructor(component: ManagedAnimatedComponent) { + constructor(component: ManagedAnimatedComponent, options?: ComponentOptions) { this._managedComponent = component; + this._componentOptions = options; + this._eventViewTag = getEventViewTag( + this._managedComponent, + this._componentOptions + ); } - public attachNativeEvents(): void { - if (IS_WEB) { - return; - } + public attachNativeEvents() { executeForEachEventHandler(this._managedComponent.props, (key, handler) => { - handler.registerForEvents(this._managedComponent._eventViewTag, key); + handler.registerForEvents(this._eventViewTag, key); }); } - public detachNativeEvents(): void { - if (IS_WEB) { - return; - } + public detachNativeEvents() { executeForEachEventHandler( this._managedComponent.props, (_key, handler) => { - handler.unregisterFromEvents(this._managedComponent._eventViewTag); + handler.unregisterFromEvents(this._eventViewTag); } ); } public updateNativeEvents( - prevProps: AnimatedComponentProps, - computedEventTag: number - ): void { - if (IS_WEB) { - return; - } + prevProps: AnimatedComponentProps + ) { + const computedEventTag = getEventViewTag( + this._managedComponent, + this._componentOptions + ); // If the event view tag changes, we need to completely re-mount all events - if (this._managedComponent._eventViewTag !== computedEventTag) { + if (this._eventViewTag !== computedEventTag) { // Remove all bindings from previous props that ran on the old viewTag executeForEachEventHandler(prevProps, (_key, handler) => { - handler.unregisterFromEvents(this._managedComponent._eventViewTag); + handler.unregisterFromEvents(this._eventViewTag); }); // We don't need to unregister from current (new) props, because their events weren't registered yet // Replace the view tag - this._managedComponent._eventViewTag = computedEventTag; + this._eventViewTag = computedEventTag; // Attach the events with a new viewTag this.attachNativeEvents(); return; @@ -95,24 +65,81 @@ export class NativeEventsManager implements INativeEventsManager { const newProp = this._managedComponent.props[key]; if (!newProp) { // Prop got deleted - prevHandler.unregisterFromEvents(this._managedComponent._eventViewTag); + prevHandler.unregisterFromEvents(this._eventViewTag); } else if ( isWorkletEventHandler(newProp) && newProp.workletEventHandler !== prevHandler ) { // Prop got changed - prevHandler.unregisterFromEvents(this._managedComponent._eventViewTag); - newProp.workletEventHandler.registerForEvents( - this._managedComponent._eventViewTag - ); + prevHandler.unregisterFromEvents(this._eventViewTag); + newProp.workletEventHandler.registerForEvents(this._eventViewTag); } }); executeForEachEventHandler(this._managedComponent.props, (key, handler) => { if (!prevProps[key]) { // Prop got added - handler.registerForEvents(this._managedComponent._eventViewTag); + handler.registerForEvents(this._eventViewTag); } }); } } + +type ManagedAnimatedComponent = React.Component< + AnimatedComponentProps +> & + IAnimatedComponentInternal; + +type ComponentOptions = { + setNativeProps: ( + ref: AnimatedComponentRef, + props: InitialComponentProps + ) => void; +}; + +type WorkletEventHandlerHolder = { + workletEventHandler: InstanceType; +}; + +function isWorkletEventHandler( + prop: unknown +): prop is WorkletEventHandlerHolder { + return ( + has('workletEventHandler', prop) && + prop.workletEventHandler instanceof WorkletEventHandler + ); +} + +function executeForEachEventHandler( + props: AnimatedComponentProps, + callback: ( + key: string, + handler: InstanceType + ) => void +) { + for (const key in props) { + const prop = props[key]; + if (isWorkletEventHandler(prop)) { + callback(key, prop.workletEventHandler); + } + } +} + +function getEventViewTag( + component: ManagedAnimatedComponent, + options?: ComponentOptions +) { + // Get the tag for registering events - since the event emitting view can be nested inside the main component + const componentAnimatedRef = component._component as AnimatedComponentRef; + let newTag: number; + if (componentAnimatedRef.getScrollableNode) { + const scrollableNode = componentAnimatedRef.getScrollableNode(); + newTag = findNodeHandle(scrollableNode) ?? -1; + } else { + newTag = + findNodeHandle( + options?.setNativeProps ? component : componentAnimatedRef + ) ?? -1; + } + return newTag; +} diff --git a/src/createAnimatedComponent/commonTypes.ts b/src/createAnimatedComponent/commonTypes.ts index c9c7457030c9..3042e996724b 100644 --- a/src/createAnimatedComponent/commonTypes.ts +++ b/src/createAnimatedComponent/commonTypes.ts @@ -58,8 +58,7 @@ export interface INativeEventsManager { attachNativeEvents(): void; detachNativeEvents(): void; updateNativeEvents( - prevProps: AnimatedComponentProps, - computedEventTag: number + prevProps: AnimatedComponentProps ): void; } @@ -106,7 +105,6 @@ export interface IAnimatedComponentInternal { _styles: StyleProps[] | null; _animatedProps?: Partial>; _componentViewTag: number; - _eventViewTag: number; _isFirstRender: boolean; jestAnimatedStyle: { value: StyleProps }; _component: AnimatedComponentRef | HTMLElement | null; @@ -114,7 +112,7 @@ export interface IAnimatedComponentInternal { _jsPropsUpdater: IJSPropsUpdater; _InlinePropManager: IInlinePropManager; _PropsFilter: IPropsFilter; - _NativeEventsManager: INativeEventsManager; + _NativeEventsManager?: INativeEventsManager; _viewInfo?: ViewInfo; context: React.ContextType; } diff --git a/src/createAnimatedComponent/createAnimatedComponent.tsx b/src/createAnimatedComponent/createAnimatedComponent.tsx index 52490923de48..fd85f25ad826 100644 --- a/src/createAnimatedComponent/createAnimatedComponent.tsx +++ b/src/createAnimatedComponent/createAnimatedComponent.tsx @@ -124,7 +124,6 @@ export function createAnimatedComponent( _styles: StyleProps[] | null = null; _animatedProps?: Partial>; _componentViewTag = -1; - _eventViewTag = -1; _isFirstRender = true; jestAnimatedStyle: { value: StyleProps } = { value: {} }; _component: AnimatedComponentRef | HTMLElement | null = null; @@ -132,7 +131,7 @@ export function createAnimatedComponent( _jsPropsUpdater = new JSPropsUpdater(); _InlinePropManager = new InlinePropManager(); _PropsFilter = new PropsFilter(); - _NativeEventsManager: INativeEventsManager; + _NativeEventsManager?: INativeEventsManager; _viewInfo?: ViewInfo; static displayName: string; static contextType = SkipEnteringContext; @@ -143,13 +142,14 @@ export function createAnimatedComponent( if (isJest()) { this.jestAnimatedStyle = { value: {} }; } - this._NativeEventsManager = new NativeEventsManager(this); } componentDidMount() { this._componentViewTag = this._getComponentViewTag(); - this._eventViewTag = this._getEventViewTag(); - this._NativeEventsManager.attachNativeEvents(); + if (!IS_WEB) { + this._NativeEventsManager = new NativeEventsManager(this, options); + } + this._NativeEventsManager?.attachNativeEvents(); this._jsPropsUpdater.addOnJSPropsChangeListener(this); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); @@ -183,7 +183,7 @@ export function createAnimatedComponent( } componentWillUnmount() { - this._NativeEventsManager.detachNativeEvents(); + this._NativeEventsManager?.detachNativeEvents(); this._jsPropsUpdater.removeOnJSPropsChangeListener(this); this._detachStyles(); this._InlinePropManager.detachInlineProps(); @@ -233,22 +233,6 @@ export function createAnimatedComponent( return this._getViewInfo().viewTag as number; } - _getEventViewTag() { - // Get the tag for registering events - since the event emitting view can be nested inside the main component - const componentAnimatedRef = this._component as AnimatedComponentRef; - let newTag: number; - if (componentAnimatedRef.getScrollableNode) { - const scrollableNode = componentAnimatedRef.getScrollableNode(); - newTag = findNodeHandle(scrollableNode) ?? -1; - } else { - newTag = - findNodeHandle( - options?.setNativeProps ? this : componentAnimatedRef - ) ?? -1; - } - return newTag; - } - _detachStyles() { if (IS_WEB && this._styles !== null) { for (const style of this._styles) { @@ -416,10 +400,7 @@ export function createAnimatedComponent( ) { this._configureSharedTransition(); } - this._NativeEventsManager.updateNativeEvents( - prevProps, - this._getEventViewTag() - ); + this._NativeEventsManager?.updateNativeEvents(prevProps); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); From b72ba277b0c02b603b45e191a8220dd85889994b Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Tue, 28 May 2024 16:43:58 +0200 Subject: [PATCH 4/8] quick fix --- src/createAnimatedComponent/NativeEventsManager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/createAnimatedComponent/NativeEventsManager.ts b/src/createAnimatedComponent/NativeEventsManager.ts index cbba8c803699..1ffeecd9d05f 100644 --- a/src/createAnimatedComponent/NativeEventsManager.ts +++ b/src/createAnimatedComponent/NativeEventsManager.ts @@ -8,13 +8,12 @@ import type { } from './commonTypes'; import { has } from './utils'; import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler'; -import { isWeb } from '../reanimated2/PlatformChecker'; import { findNodeHandle } from 'react-native'; export class NativeEventsManager implements INativeEventsManager { _managedComponent: ManagedAnimatedComponent; _componentOptions?: ComponentOptions; - _eventViewTag = 1; + _eventViewTag = -1; constructor(component: ManagedAnimatedComponent, options?: ComponentOptions) { this._managedComponent = component; From 355591d7f6ac70367b493feb3687834230677e53 Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 29 May 2024 15:37:43 +0200 Subject: [PATCH 5/8] more review stylistic changes --- .../NativeEventsManager.ts | 140 +++++++++--------- src/createAnimatedComponent/commonTypes.ts | 12 +- .../createAnimatedComponent.tsx | 7 +- 3 files changed, 83 insertions(+), 76 deletions(-) diff --git a/src/createAnimatedComponent/NativeEventsManager.ts b/src/createAnimatedComponent/NativeEventsManager.ts index 1ffeecd9d05f..23047f41cce4 100644 --- a/src/createAnimatedComponent/NativeEventsManager.ts +++ b/src/createAnimatedComponent/NativeEventsManager.ts @@ -11,27 +11,27 @@ import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler'; import { findNodeHandle } from 'react-native'; export class NativeEventsManager implements INativeEventsManager { - _managedComponent: ManagedAnimatedComponent; - _componentOptions?: ComponentOptions; - _eventViewTag = -1; + private _managedComponent: ManagedAnimatedComponent; + private _componentOptions?: ComponentOptions; + private _eventViewTag = -1; constructor(component: ManagedAnimatedComponent, options?: ComponentOptions) { this._managedComponent = component; this._componentOptions = options; - this._eventViewTag = getEventViewTag( - this._managedComponent, - this._componentOptions - ); + this._eventViewTag = this.getEventViewTag(); } - public attachNativeEvents() { - executeForEachEventHandler(this._managedComponent.props, (key, handler) => { - handler.registerForEvents(this._eventViewTag, key); - }); + public attachEvents() { + this.executeForEachEventHandler( + this._managedComponent.props, + (key, handler) => { + handler.registerForEvents(this._eventViewTag, key); + } + ); } - public detachNativeEvents() { - executeForEachEventHandler( + public detachEvents() { + this.executeForEachEventHandler( this._managedComponent.props, (_key, handler) => { handler.unregisterFromEvents(this._eventViewTag); @@ -39,34 +39,31 @@ export class NativeEventsManager implements INativeEventsManager { ); } - public updateNativeEvents( + public updateEvents( prevProps: AnimatedComponentProps ) { - const computedEventTag = getEventViewTag( - this._managedComponent, - this._componentOptions - ); + const computedEventTag = this.getEventViewTag(); // If the event view tag changes, we need to completely re-mount all events if (this._eventViewTag !== computedEventTag) { // Remove all bindings from previous props that ran on the old viewTag - executeForEachEventHandler(prevProps, (_key, handler) => { + this.executeForEachEventHandler(prevProps, (_key, handler) => { handler.unregisterFromEvents(this._eventViewTag); }); // We don't need to unregister from current (new) props, because their events weren't registered yet // Replace the view tag this._eventViewTag = computedEventTag; // Attach the events with a new viewTag - this.attachNativeEvents(); + this.attachEvents(); return; } - executeForEachEventHandler(prevProps, (key, prevHandler) => { + this.executeForEachEventHandler(prevProps, (key, prevHandler) => { const newProp = this._managedComponent.props[key]; if (!newProp) { // Prop got deleted prevHandler.unregisterFromEvents(this._eventViewTag); } else if ( - isWorkletEventHandler(newProp) && + this.isWorkletEventHandler(newProp) && newProp.workletEventHandler !== prevHandler ) { // Prop got changed @@ -75,12 +72,58 @@ export class NativeEventsManager implements INativeEventsManager { } }); - executeForEachEventHandler(this._managedComponent.props, (key, handler) => { - if (!prevProps[key]) { - // Prop got added - handler.registerForEvents(this._eventViewTag); + this.executeForEachEventHandler( + this._managedComponent.props, + (key, handler) => { + if (!prevProps[key]) { + // Prop got added + handler.registerForEvents(this._eventViewTag); + } } - }); + ); + } + + private isWorkletEventHandler( + prop: unknown + ): prop is WorkletEventHandlerHolder { + return ( + has('workletEventHandler', prop) && + prop.workletEventHandler instanceof WorkletEventHandler + ); + } + + private executeForEachEventHandler( + props: AnimatedComponentProps, + callback: ( + key: string, + handler: InstanceType + ) => void + ) { + for (const key in props) { + const prop = props[key]; + if (this.isWorkletEventHandler(prop)) { + callback(key, prop.workletEventHandler); + } + } + } + + private getEventViewTag() { + // Get the tag for registering events - since the event emitting view can be nested inside the main component + const componentAnimatedRef = this._managedComponent + ._component as AnimatedComponentRef; + let newTag: number; + if (componentAnimatedRef.getScrollableNode) { + const scrollableNode = componentAnimatedRef.getScrollableNode(); + newTag = findNodeHandle(scrollableNode) ?? -1; + } else { + newTag = + findNodeHandle( + this._componentOptions?.setNativeProps + ? this._managedComponent + : componentAnimatedRef + ) ?? -1; + } + return newTag; } } @@ -99,46 +142,3 @@ type ComponentOptions = { type WorkletEventHandlerHolder = { workletEventHandler: InstanceType; }; - -function isWorkletEventHandler( - prop: unknown -): prop is WorkletEventHandlerHolder { - return ( - has('workletEventHandler', prop) && - prop.workletEventHandler instanceof WorkletEventHandler - ); -} - -function executeForEachEventHandler( - props: AnimatedComponentProps, - callback: ( - key: string, - handler: InstanceType - ) => void -) { - for (const key in props) { - const prop = props[key]; - if (isWorkletEventHandler(prop)) { - callback(key, prop.workletEventHandler); - } - } -} - -function getEventViewTag( - component: ManagedAnimatedComponent, - options?: ComponentOptions -) { - // Get the tag for registering events - since the event emitting view can be nested inside the main component - const componentAnimatedRef = component._component as AnimatedComponentRef; - let newTag: number; - if (componentAnimatedRef.getScrollableNode) { - const scrollableNode = componentAnimatedRef.getScrollableNode(); - newTag = findNodeHandle(scrollableNode) ?? -1; - } else { - newTag = - findNodeHandle( - options?.setNativeProps ? component : componentAnimatedRef - ) ?? -1; - } - return newTag; -} diff --git a/src/createAnimatedComponent/commonTypes.ts b/src/createAnimatedComponent/commonTypes.ts index 3042e996724b..07b45a682379 100644 --- a/src/createAnimatedComponent/commonTypes.ts +++ b/src/createAnimatedComponent/commonTypes.ts @@ -55,9 +55,9 @@ export interface IJSPropsUpdater { } export interface INativeEventsManager { - attachNativeEvents(): void; - detachNativeEvents(): void; - updateNativeEvents( + attachEvents(): void; + detachEvents(): void; + updateEvents( prevProps: AnimatedComponentProps ): void; } @@ -104,6 +104,9 @@ export interface AnimatedComponentRef extends Component { export interface IAnimatedComponentInternal { _styles: StyleProps[] | null; _animatedProps?: Partial>; + /** + * Used for Shared Element Transitions, Layout Animations and Animated Styles. It is not related to event handling. + */ _componentViewTag: number; _isFirstRender: boolean; jestAnimatedStyle: { value: StyleProps }; @@ -112,6 +115,9 @@ export interface IAnimatedComponentInternal { _jsPropsUpdater: IJSPropsUpdater; _InlinePropManager: IInlinePropManager; _PropsFilter: IPropsFilter; + /** + * Doesn't exist on web. + */ _NativeEventsManager?: INativeEventsManager; _viewInfo?: ViewInfo; context: React.ContextType; diff --git a/src/createAnimatedComponent/createAnimatedComponent.tsx b/src/createAnimatedComponent/createAnimatedComponent.tsx index fd85f25ad826..d2013948ea50 100644 --- a/src/createAnimatedComponent/createAnimatedComponent.tsx +++ b/src/createAnimatedComponent/createAnimatedComponent.tsx @@ -147,9 +147,10 @@ export function createAnimatedComponent( componentDidMount() { this._componentViewTag = this._getComponentViewTag(); if (!IS_WEB) { + // It exists only on native platforms. We initialize it here because the ref to the animated component is available only post-mount this._NativeEventsManager = new NativeEventsManager(this, options); } - this._NativeEventsManager?.attachNativeEvents(); + this._NativeEventsManager?.attachEvents(); this._jsPropsUpdater.addOnJSPropsChangeListener(this); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); @@ -183,7 +184,7 @@ export function createAnimatedComponent( } componentWillUnmount() { - this._NativeEventsManager?.detachNativeEvents(); + this._NativeEventsManager?.detachEvents(); this._jsPropsUpdater.removeOnJSPropsChangeListener(this); this._detachStyles(); this._InlinePropManager.detachInlineProps(); @@ -400,7 +401,7 @@ export function createAnimatedComponent( ) { this._configureSharedTransition(); } - this._NativeEventsManager?.updateNativeEvents(prevProps); + this._NativeEventsManager?.updateEvents(prevProps); this._attachAnimatedStyles(); this._InlinePropManager.attachInlineProps(this, this._getViewInfo()); From 99d39056b151fcfda00d803b1abb7a4e37ba4cfe Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 29 May 2024 15:42:23 +0200 Subject: [PATCH 6/8] lint fix --- src/createAnimatedComponent/commonTypes.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/createAnimatedComponent/commonTypes.ts b/src/createAnimatedComponent/commonTypes.ts index 07b45a682379..cd0a58863c78 100644 --- a/src/createAnimatedComponent/commonTypes.ts +++ b/src/createAnimatedComponent/commonTypes.ts @@ -57,9 +57,7 @@ export interface IJSPropsUpdater { export interface INativeEventsManager { attachEvents(): void; detachEvents(): void; - updateEvents( - prevProps: AnimatedComponentProps - ): void; + updateEvents(prevProps: AnimatedComponentProps): void; } export type LayoutAnimationStaticContext = { From ba3f6891aba4ff23314c821181ff1f107dc94dde Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Mon, 10 Jun 2024 17:59:49 +0200 Subject: [PATCH 7/8] some more stylistics --- .../NativeEventsManager.ts | 114 +++++++++--------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts b/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts index 6d7bbc598781..1a0becfdb965 100644 --- a/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts +++ b/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts @@ -11,30 +11,27 @@ import { WorkletEventHandler } from '../WorkletEventHandler'; import { findNodeHandle } from 'react-native'; export class NativeEventsManager implements INativeEventsManager { - private _managedComponent: ManagedAnimatedComponent; - private _componentOptions?: ComponentOptions; - private _eventViewTag = -1; + #managedComponent: ManagedAnimatedComponent; + #componentOptions?: ComponentOptions; + #eventViewTag = -1; constructor(component: ManagedAnimatedComponent, options?: ComponentOptions) { - this._managedComponent = component; - this._componentOptions = options; - this._eventViewTag = this.getEventViewTag(); + this.#managedComponent = component; + this.#componentOptions = options; + this.#eventViewTag = this.getEventViewTag(); } public attachEvents() { - this.executeForEachEventHandler( - this._managedComponent.props, - (key, handler) => { - handler.registerForEvents(this._eventViewTag, key); - } - ); + executeForEachEventHandler(this.#managedComponent.props, (key, handler) => { + handler.registerForEvents(this.#eventViewTag, key); + }); } public detachEvents() { - this.executeForEachEventHandler( - this._managedComponent.props, + executeForEachEventHandler( + this.#managedComponent.props, (_key, handler) => { - handler.unregisterFromEvents(this._eventViewTag); + handler.unregisterFromEvents(this.#eventViewTag); } ); } @@ -44,72 +41,45 @@ export class NativeEventsManager implements INativeEventsManager { ) { const computedEventTag = this.getEventViewTag(); // If the event view tag changes, we need to completely re-mount all events - if (this._eventViewTag !== computedEventTag) { + if (this.#eventViewTag !== computedEventTag) { // Remove all bindings from previous props that ran on the old viewTag - this.executeForEachEventHandler(prevProps, (_key, handler) => { - handler.unregisterFromEvents(this._eventViewTag); + executeForEachEventHandler(prevProps, (_key, handler) => { + handler.unregisterFromEvents(this.#eventViewTag); }); // We don't need to unregister from current (new) props, because their events weren't registered yet // Replace the view tag - this._eventViewTag = computedEventTag; + this.#eventViewTag = computedEventTag; // Attach the events with a new viewTag this.attachEvents(); return; } - this.executeForEachEventHandler(prevProps, (key, prevHandler) => { - const newProp = this._managedComponent.props[key]; + executeForEachEventHandler(prevProps, (key, prevHandler) => { + const newProp = this.#managedComponent.props[key]; if (!newProp) { // Prop got deleted - prevHandler.unregisterFromEvents(this._eventViewTag); + prevHandler.unregisterFromEvents(this.#eventViewTag); } else if ( - this.isWorkletEventHandler(newProp) && + isWorkletEventHandler(newProp) && newProp.workletEventHandler !== prevHandler ) { // Prop got changed - prevHandler.unregisterFromEvents(this._eventViewTag); - newProp.workletEventHandler.registerForEvents(this._eventViewTag); + prevHandler.unregisterFromEvents(this.#eventViewTag); + newProp.workletEventHandler.registerForEvents(this.#eventViewTag); } }); - this.executeForEachEventHandler( - this._managedComponent.props, - (key, handler) => { - if (!prevProps[key]) { - // Prop got added - handler.registerForEvents(this._eventViewTag); - } + executeForEachEventHandler(this.#managedComponent.props, (key, handler) => { + if (!prevProps[key]) { + // Prop got added + handler.registerForEvents(this.#eventViewTag); } - ); - } - - private isWorkletEventHandler( - prop: unknown - ): prop is WorkletEventHandlerHolder { - return ( - has('workletEventHandler', prop) && - prop.workletEventHandler instanceof WorkletEventHandler - ); - } - - private executeForEachEventHandler( - props: AnimatedComponentProps, - callback: ( - key: string, - handler: InstanceType - ) => void - ) { - for (const key in props) { - const prop = props[key]; - if (this.isWorkletEventHandler(prop)) { - callback(key, prop.workletEventHandler); - } - } + }); } private getEventViewTag() { // Get the tag for registering events - since the event emitting view can be nested inside the main component - const componentAnimatedRef = this._managedComponent + const componentAnimatedRef = this.#managedComponent ._component as AnimatedComponentRef; let newTag: number; if (componentAnimatedRef.getScrollableNode) { @@ -118,8 +88,8 @@ export class NativeEventsManager implements INativeEventsManager { } else { newTag = findNodeHandle( - this._componentOptions?.setNativeProps - ? this._managedComponent + this.#componentOptions?.setNativeProps + ? this.#managedComponent : componentAnimatedRef ) ?? -1; } @@ -127,6 +97,30 @@ export class NativeEventsManager implements INativeEventsManager { } } +function isWorkletEventHandler( + prop: unknown +): prop is WorkletEventHandlerHolder { + return ( + has('workletEventHandler', prop) && + prop.workletEventHandler instanceof WorkletEventHandler + ); +} + +function executeForEachEventHandler( + props: AnimatedComponentProps, + callback: ( + key: string, + handler: InstanceType + ) => void +) { + for (const key in props) { + const prop = props[key]; + if (isWorkletEventHandler(prop)) { + callback(key, prop.workletEventHandler); + } + } +} + type ManagedAnimatedComponent = React.Component< AnimatedComponentProps > & From bc663d0707b3f6d843a76be0ed03010dd9e3b6dd Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Mon, 10 Jun 2024 18:18:22 +0200 Subject: [PATCH 8/8] make not mutable properties readonly --- .../src/createAnimatedComponent/NativeEventsManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts b/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts index 1a0becfdb965..35ce8a68a99b 100644 --- a/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts +++ b/packages/react-native-reanimated/src/createAnimatedComponent/NativeEventsManager.ts @@ -11,8 +11,8 @@ import { WorkletEventHandler } from '../WorkletEventHandler'; import { findNodeHandle } from 'react-native'; export class NativeEventsManager implements INativeEventsManager { - #managedComponent: ManagedAnimatedComponent; - #componentOptions?: ComponentOptions; + readonly #managedComponent: ManagedAnimatedComponent; + readonly #componentOptions?: ComponentOptions; #eventViewTag = -1; constructor(component: ManagedAnimatedComponent, options?: ComponentOptions) {