mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 12:20:38 +01:00
We're fixing the timing of layout and passive effects in React Native,
and adding support for some Web APIs so common use cases for those
effects can be implemented with the same code on React and React Native.
Let's take this example:
```javascript
function MyComponent(props) {
const viewRef = useRef();
useLayoutEffect(() => {
const rect = viewRef.current?.getBoundingClientRect();
console.log('My view is located at', rect?.toJSON());
}, []);
return <View ref={viewRef}>{props.children}</View>;
}
```
This could would work as expected on Web (ignoring the use of `View` and
assuming something like `div`) but not on React Native because:
1. Layout is done asynchronously in a background thread in parallel with
the execution of layout and passive effects. This is incorrect and it's
being fixed in React Native (see
afec07aca2).
2. We don't have an API to access layout information synchronously. The
existing `ref.current.measureInWindow` uses callbacks to pass the
result. That is asynchronous at the moment in Paper (the legacy renderer
in React Native), but it's actually synchronous in Fabric (the new React
Native renderer).
This fixes point 2) by adding a Web-compatible method to access layout
information (on Fabric only).
This has 2 dependencies in React Native:
1. Access to `getBoundingClientRect` in Fabric, which was added in
https://github.com/facebook/react-native/blob/main/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp#L644-
L676
2. Access to `DOMRect`, which was added in
673c7617bc
.
As next step, I'll modify the implementation of this and other methods
in Fabric to warn when they're accessed during render. We can't do this
on Web because we can't (shouldn't) modify built-in DOM APIs, but we can
do it in React Native because the refs objects are built by the
framework.
258 lines
7.6 KiB
JavaScript
258 lines
7.6 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
/* eslint-disable */
|
|
|
|
// libdefs cannot actually import. These are supposed to be the types imported
|
|
// from 'react-native-renderer/src/ReactNativeTypes'
|
|
type __MeasureOnSuccessCallback = any;
|
|
type __MeasureInWindowOnSuccessCallback = any;
|
|
type __MeasureLayoutOnSuccessCallback = any;
|
|
type __ReactNativeBaseComponentViewConfig = any;
|
|
type __ViewConfigGetter = any;
|
|
|
|
// libdefs cannot actually import. This is supposed to be the type imported
|
|
// from 'react-native-renderer/src/legacy-events/TopLevelEventTypes';
|
|
type __RNTopLevelEventType = any;
|
|
|
|
// libdefs cannot actually import. This is supposed to be the type imported
|
|
// from 'react-reconciler/src/ReactCapturedValue'
|
|
type __CapturedError = any;
|
|
|
|
type DeepDifferOptions = {+unsafelyIgnoreFunctions?: boolean};
|
|
type RawEventEmitterEvent = $ReadOnly<{
|
|
eventName: string,
|
|
// We expect, but do not/cannot require, that nativeEvent is an object
|
|
// with the properties: key, elementType (string), type (string), tag (numeric),
|
|
// and a stateNode of the native element/Fiber the event was emitted to.
|
|
nativeEvent: {[string]: mixed, ...},
|
|
}>;
|
|
|
|
declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface' {
|
|
declare export function deepDiffer(
|
|
one: any,
|
|
two: any,
|
|
maxDepth?: number,
|
|
options?: DeepDifferOptions,
|
|
): boolean;
|
|
declare export function deepDiffer(
|
|
one: any,
|
|
two: any,
|
|
options: DeepDifferOptions,
|
|
): boolean;
|
|
declare export function deepFreezeAndThrowOnMutationInDev<T>(obj: T): T;
|
|
declare export function flattenStyle(style: any): any;
|
|
declare export var RCTEventEmitter: {
|
|
register: (eventEmitter: mixed) => void,
|
|
...
|
|
};
|
|
declare export var TextInputState: {
|
|
blurTextInput: (object: any) => void,
|
|
focusTextInput: (object: any) => void,
|
|
...
|
|
};
|
|
declare export var ReactFiberErrorDialog: {
|
|
showErrorDialog: (error: __CapturedError) => boolean,
|
|
...
|
|
};
|
|
declare export var Platform: {OS: string, ...};
|
|
declare export var UIManager: {
|
|
customBubblingEventTypes: Object,
|
|
customDirectEventTypes: Object,
|
|
createView: (
|
|
reactTag: number,
|
|
viewName: string,
|
|
rootTag: number,
|
|
props: ?Object,
|
|
) => void,
|
|
dispatchViewManagerCommand: (
|
|
reactTag: number,
|
|
command: string,
|
|
args: Array<any>,
|
|
) => void,
|
|
manageChildren: (
|
|
containerTag: number,
|
|
moveFromIndices: Array<number>,
|
|
moveToIndices: Array<number>,
|
|
addChildReactTags: Array<number>,
|
|
addAtIndices: Array<number>,
|
|
removeAtIndices: Array<number>,
|
|
) => void,
|
|
measure: (hostComponent: mixed, callback: Function) => void,
|
|
measureInWindow: (nativeTag: ?number, callback: Function) => void,
|
|
measureLayout: (
|
|
nativeTag: mixed,
|
|
nativeNode: number,
|
|
onFail: Function,
|
|
onSuccess: Function,
|
|
) => void,
|
|
removeRootView: (containerTag: number) => void,
|
|
removeSubviewsFromContainerWithID: (containerId: number) => void,
|
|
replaceExistingNonRootView: () => void,
|
|
setChildren: (containerTag: number, reactTags: Array<number>) => void,
|
|
updateView: (reactTag: number, viewName: string, props: ?Object) => void,
|
|
__takeSnapshot: (
|
|
view?: 'window' | Element<any> | number,
|
|
options?: {
|
|
width?: number,
|
|
height?: number,
|
|
format?: 'png' | 'jpeg',
|
|
quality?: number,
|
|
...
|
|
},
|
|
) => Promise<any>,
|
|
setJSResponder: (reactTag: number, blockNativeResponder: boolean) => void,
|
|
clearJSResponder: () => void,
|
|
findSubviewIn: (
|
|
reactTag: ?number,
|
|
point: Array<number>,
|
|
callback: (
|
|
nativeViewTag: number,
|
|
left: number,
|
|
top: number,
|
|
width: number,
|
|
height: number,
|
|
) => void,
|
|
) => void,
|
|
...
|
|
};
|
|
declare export var legacySendAccessibilityEvent: (
|
|
reactTag: number,
|
|
eventTypeName: string,
|
|
) => void;
|
|
declare export var BatchedBridge: {
|
|
registerCallableModule: (name: string, module: Object) => void,
|
|
...
|
|
};
|
|
declare export var ReactNativeViewConfigRegistry: {
|
|
customBubblingEventTypes: Object,
|
|
customDirectEventTypes: Object,
|
|
eventTypes: Object,
|
|
|
|
register: (name: string, callback: __ViewConfigGetter) => string,
|
|
get: (name: string) => __ReactNativeBaseComponentViewConfig,
|
|
...
|
|
};
|
|
declare export var RawEventEmitter: {
|
|
emit: (channel: string, event: RawEventEmitterEvent) => string,
|
|
...
|
|
};
|
|
declare export class CustomEvent {
|
|
isTrusted: boolean;
|
|
|
|
constructor(
|
|
name: string,
|
|
{
|
|
detail: any,
|
|
},
|
|
): void;
|
|
|
|
setSyntheticEvent(event: any): void;
|
|
}
|
|
}
|
|
|
|
declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore' {
|
|
}
|
|
|
|
// This is needed for a short term solution.
|
|
// See https://github.com/facebook/react/pull/15490 for more info
|
|
declare var nativeFabricUIManager: {
|
|
createNode: (
|
|
reactTag: number,
|
|
viewName: string,
|
|
rootTag: number,
|
|
props: ?Object,
|
|
eventTarget: Object,
|
|
) => Object,
|
|
cloneNode: (node: Object) => Object,
|
|
cloneNodeWithNewChildren: (node: Object) => Object,
|
|
cloneNodeWithNewProps: (node: Object, newProps: ?Object) => Object,
|
|
cloneNodeWithNewChildrenAndProps: (node: Object, newProps: ?Object) => Object,
|
|
appendChild: (node: Object, childNode: Object) => void,
|
|
|
|
createChildSet: (rootTag: number) => Object,
|
|
appendChildToSet: (childSet: Object, childNode: Object) => void,
|
|
completeRoot: (rootTag: number, childSet: Object) => void,
|
|
registerEventHandler: (
|
|
callback: (
|
|
eventTarget: null | Object,
|
|
type: __RNTopLevelEventType,
|
|
payload: Object,
|
|
) => void,
|
|
) => void,
|
|
setNativeProps: (node: Object, nativeProps: Object) => Object,
|
|
dispatchCommand: (node: Object, command: string, args: Array<any>) => void,
|
|
sendAccessibilityEvent: (node: Object, eventTypeName: string) => void,
|
|
|
|
measure: (node: Node, callback: __MeasureOnSuccessCallback) => void,
|
|
measureInWindow: (
|
|
node: Node,
|
|
callback: __MeasureInWindowOnSuccessCallback,
|
|
) => void,
|
|
measureLayout: (
|
|
node: Node,
|
|
relativeNode: Node,
|
|
onFail: () => void,
|
|
onSuccess: __MeasureLayoutOnSuccessCallback,
|
|
) => void,
|
|
getBoundingClientRect: (
|
|
node: Node,
|
|
) => [
|
|
/* x:*/ number,
|
|
/* y:*/ number,
|
|
/* width:*/ number,
|
|
/* height:*/ number,
|
|
],
|
|
findNodeAtPoint: (
|
|
node: Node,
|
|
locationX: number,
|
|
locationY: number,
|
|
callback: (Object) => void,
|
|
) => void,
|
|
setIsJSResponder: (
|
|
node: Node,
|
|
isJsResponder: boolean,
|
|
blockNativeResponder: boolean,
|
|
) => void,
|
|
unstable_DefaultEventPriority: number,
|
|
unstable_DiscreteEventPriority: number,
|
|
unstable_getCurrentEventPriority: () => number,
|
|
...
|
|
};
|
|
|
|
declare module 'View' {
|
|
declare module.exports: typeof React$Component;
|
|
}
|
|
|
|
declare module 'RTManager' {
|
|
declare function createNode(
|
|
tag: number,
|
|
classType: string,
|
|
props: ?Object,
|
|
): void;
|
|
|
|
declare function beginUpdates(): void;
|
|
|
|
declare function appendChildToContext(
|
|
contextTag: number,
|
|
childTag: number,
|
|
): void;
|
|
declare function appendChild(parentTag: number, childTag: number): void;
|
|
declare function prependChild(childTag: number, beforeTag: number): void;
|
|
declare function deleteChild(childTag: number): void;
|
|
declare function updateNode(tag: number, props: ?Object): void;
|
|
|
|
declare function completeUpdates(): void;
|
|
}
|
|
|
|
// shims/ReactFeatureFlags is generated by the packaging script
|
|
declare module '../shims/ReactFeatureFlags' {
|
|
declare export var debugRenderPhaseSideEffects: boolean;
|
|
}
|