react/packages/react-native-renderer/src/ReactNativeRenderer.js

303 lines
8.9 KiB
JavaScript
Raw Normal View History

/**
* 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
*/
import type {HostComponent} from './ReactNativeTypes';
import type {ReactPortal, ReactNodeList} from 'shared/ReactTypes';
2021-02-26 05:10:17 +08:00
import type {ElementRef, Element, ElementType} from 'react';
import './ReactNativeInjection';
import {
findHostInstance,
findHostInstanceWithWarning,
batchedUpdates as batchedUpdatesImpl,
discreteUpdates,
createContainer,
updateContainer,
injectIntoDevTools,
getPublicRootInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
Refactor stack handling (no functional changes) (#13165) * Refactor ReactDebugCurrentFiber to use named exports This makes the difference between it and ReactFiberCurrentFrame a bit clearer. ReactDebugCurrentFiber is Fiber's own implementation. ReactFiberCurrentFrame is the thing that holds a reference to the current implementation and delegates to it. * Unify ReactFiberComponentTreeHook and ReactDebugCurrentFiber Conceptually they're very related. ReactFiberComponentTreeHook contains implementation details of reading Fiber's stack (both in DEV and PROD). ReactDebugCurrentFiber contained a reference to the current fiber, and used the above utility. It was confusing when to use which one. Colocating them makes it clearer what you could do with each method. In the future, the plan is to stop using these methods explicitly in most places, and instead delegate to a warning system that includes stacks automatically. This change makes future refactorings simpler by colocating related logic. * Rename methods to better reflect their meanings Clarify which are DEV or PROD-only. Clarify which can return null. I believe the "work in progress only" was a mistake. I introduced it because I wasn't sure what guarantees we have around .return. But we know for sure that following a .return chain gives us an accurate stack even if we get into WIP trees because we don't have reparenting. So it's fine to relax that naming. * Rename ReactDebugCurrentFiber -> ReactCurrentFiber It's not completely DEV-only anymore. Individual methods already specify whether they work in DEV or PROD in their names.
2018-07-07 08:09:41 +08:00
// TODO: direct imports like some-package/src/* are bad. Fix me.
import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {
setBatchingImplementation,
batchedUpdates,
} from './legacy-events/ReactGenericBatching';
import ReactVersion from 'shared/ReactVersion';
// Modules provided by RN:
import {
UIManager,
legacySendAccessibilityEvent,
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
import {
getInspectorDataForViewTag,
getInspectorDataForViewAtPoint,
getInspectorDataForInstance,
} from './ReactNativeFiberInspector';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentNameFromType from 'shared/getComponentNameFromType';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
function findHostInstance_DEPRECATED(
componentOrHandle: any,
): ?React$ElementRef<HostComponent<mixed>> {
if (__DEV__) {
const owner = ReactCurrentOwner.current;
if (owner !== null && owner.stateNode !== null) {
if (!owner.stateNode._warnedAboutRefsInRender) {
console.error(
'%s is accessing findNodeHandle inside its render(). ' +
'render() should be a pure function of props and state. It should ' +
'never access something that requires stale data from the previous ' +
'render, such as refs. Move this logic to componentDidMount and ' +
'componentDidUpdate instead.',
getComponentNameFromType(owner.type) || 'A component',
);
}
owner.stateNode._warnedAboutRefsInRender = true;
}
}
if (componentOrHandle == null) {
return null;
}
if (componentOrHandle._nativeTag) {
return componentOrHandle;
}
if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
return componentOrHandle.canonical;
}
let hostInstance;
if (__DEV__) {
hostInstance = findHostInstanceWithWarning(
componentOrHandle,
'findHostInstance_DEPRECATED',
);
} else {
hostInstance = findHostInstance(componentOrHandle);
}
if (hostInstance == null) {
return hostInstance;
}
if ((hostInstance: any).canonical) {
// Fabric
return (hostInstance: any).canonical;
}
// $FlowFixMe[incompatible-return]
// $FlowFixMe[incompatible-exact]
return hostInstance;
}
function findNodeHandle(componentOrHandle: any): ?number {
if (__DEV__) {
const owner = ReactCurrentOwner.current;
if (owner !== null && owner.stateNode !== null) {
if (!owner.stateNode._warnedAboutRefsInRender) {
console.error(
'%s is accessing findNodeHandle inside its render(). ' +
'render() should be a pure function of props and state. It should ' +
'never access something that requires stale data from the previous ' +
'render, such as refs. Move this logic to componentDidMount and ' +
'componentDidUpdate instead.',
getComponentNameFromType(owner.type) || 'A component',
);
}
owner.stateNode._warnedAboutRefsInRender = true;
}
}
if (componentOrHandle == null) {
return null;
}
if (typeof componentOrHandle === 'number') {
// Already a node handle
return componentOrHandle;
}
if (componentOrHandle._nativeTag) {
return componentOrHandle._nativeTag;
}
if (componentOrHandle.canonical && componentOrHandle.canonical._nativeTag) {
return componentOrHandle.canonical._nativeTag;
}
let hostInstance;
if (__DEV__) {
hostInstance = findHostInstanceWithWarning(
componentOrHandle,
'findNodeHandle',
);
} else {
hostInstance = findHostInstance(componentOrHandle);
}
if (hostInstance == null) {
return hostInstance;
}
if ((hostInstance: any).canonical) {
// Fabric
return (hostInstance: any).canonical._nativeTag;
}
return hostInstance._nativeTag;
}
function dispatchCommand(handle: any, command: string, args: Array<any>) {
if (handle._nativeTag == null) {
if (__DEV__) {
console.error(
"dispatchCommand was called with a ref that isn't a " +
'native component. Use React.forwardRef to get access to the underlying native component',
);
}
return;
}
if (handle._internalInstanceHandle != null) {
const {stateNode} = handle._internalInstanceHandle;
if (stateNode != null) {
nativeFabricUIManager.dispatchCommand(stateNode.node, command, args);
}
} else {
UIManager.dispatchViewManagerCommand(handle._nativeTag, command, args);
}
}
function sendAccessibilityEvent(handle: any, eventType: string) {
if (handle._nativeTag == null) {
if (__DEV__) {
console.error(
"sendAccessibilityEvent was called with a ref that isn't a " +
'native component. Use React.forwardRef to get access to the underlying native component',
);
}
return;
}
if (handle._internalInstanceHandle != null) {
const {stateNode} = handle._internalInstanceHandle;
if (stateNode != null) {
nativeFabricUIManager.sendAccessibilityEvent(stateNode.node, eventType);
}
} else {
legacySendAccessibilityEvent(handle._nativeTag, eventType);
}
}
// $FlowFixMe[missing-local-annot]
function onRecoverableError(error) {
// TODO: Expose onRecoverableError option to userspace
// eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
console.error(error);
}
function render(
2021-02-26 05:10:17 +08:00
element: Element<ElementType>,
containerTag: number,
callback: ?() => void,
): ?ElementRef<ElementType> {
let root = roots.get(containerTag);
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(
containerTag,
LegacyRoot,
null,
false,
null,
'',
onRecoverableError,
null,
);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
// $FlowFixMe Flow has hardcoded values for React DOM that don't work with RN
return getPublicRootInstance(root);
}
function unmountComponentAtNode(containerTag: number) {
const root = roots.get(containerTag);
if (root) {
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
updateContainer(null, root, null, () => {
roots.delete(containerTag);
});
}
}
function unmountComponentAtNodeAndRemoveContainer(containerTag: number) {
unmountComponentAtNode(containerTag);
// Call back into native to remove all of the subviews from this container
UIManager.removeRootView(containerTag);
}
function createPortal(
children: ReactNodeList,
containerTag: number,
key: ?string = null,
): ReactPortal {
return createPortalImpl(children, containerTag, null, key);
}
setBatchingImplementation(batchedUpdatesImpl, discreteUpdates);
function computeComponentStackForErrorReporting(reactTag: number): string {
const fiber = getClosestInstanceFromNode(reactTag);
if (!fiber) {
return '';
}
Refactor stack handling (no functional changes) (#13165) * Refactor ReactDebugCurrentFiber to use named exports This makes the difference between it and ReactFiberCurrentFrame a bit clearer. ReactDebugCurrentFiber is Fiber's own implementation. ReactFiberCurrentFrame is the thing that holds a reference to the current implementation and delegates to it. * Unify ReactFiberComponentTreeHook and ReactDebugCurrentFiber Conceptually they're very related. ReactFiberComponentTreeHook contains implementation details of reading Fiber's stack (both in DEV and PROD). ReactDebugCurrentFiber contained a reference to the current fiber, and used the above utility. It was confusing when to use which one. Colocating them makes it clearer what you could do with each method. In the future, the plan is to stop using these methods explicitly in most places, and instead delegate to a warning system that includes stacks automatically. This change makes future refactorings simpler by colocating related logic. * Rename methods to better reflect their meanings Clarify which are DEV or PROD-only. Clarify which can return null. I believe the "work in progress only" was a mistake. I introduced it because I wasn't sure what guarantees we have around .return. But we know for sure that following a .return chain gives us an accurate stack even if we get into WIP trees because we don't have reparenting. So it's fine to relax that naming. * Rename ReactDebugCurrentFiber -> ReactCurrentFiber It's not completely DEV-only anymore. Individual methods already specify whether they work in DEV or PROD in their names.
2018-07-07 08:09:41 +08:00
return getStackByFiberInDevAndProd(fiber);
}
const roots = new Map();
const Internals = {
computeComponentStackForErrorReporting,
};
export {
// This is needed for implementation details of TouchableNativeFeedback
// Remove this once TouchableNativeFeedback doesn't use cloneElement
findHostInstance_DEPRECATED,
findNodeHandle,
dispatchCommand,
sendAccessibilityEvent,
render,
unmountComponentAtNode,
unmountComponentAtNodeAndRemoveContainer,
createPortal,
batchedUpdates as unstable_batchedUpdates,
Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
// This export is typically undefined in production builds.
// See the "enableGetInspectorDataForInstanceInProduction" flag.
getInspectorDataForInstance,
};
injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
Use Yarn Workspaces (#11252) * Enable Yarn workspaces for packages/* * Move src/isomorphic/* into packages/react/src/* * Create index.js stubs for all packages in packages/* This makes the test pass again, but breaks the build because npm/ folders aren't used yet. I'm not sure if we'll keep this structure--I'll just keep working and fix the build after it settles down. * Put FB entry point for react-dom into packages/* * Move src/renderers/testing/* into packages/react-test-renderer/src/* Note that this is currently broken because Jest ignores node_modules, and so Yarn linking makes Jest skip React source when transforming. * Remove src/node_modules It is now unnecessary. Some tests fail though. * Add a hacky workaround for Jest/Workspaces issue Jest sees node_modules and thinks it's third party code. This is a hacky way to teach Jest to still transform anything in node_modules/react* if it resolves outside of node_modules (such as to our packages/*) folder. I'm not very happy with this and we should revisit. * Add a fake react-native package * Move src/renderers/art/* into packages/react-art/src/* * Move src/renderers/noop/* into packages/react-noop-renderer/src/* * Move src/renderers/dom/* into packages/react-dom/src/* * Move src/renderers/shared/fiber/* into packages/react-reconciler/src/* * Move DOM/reconciler tests I previously forgot to move * Move src/renderers/native-*/* into packages/react-native-*/src/* * Move shared code into packages/shared It's not super clear how to organize this properly yet. * Add back files that somehow got lost * Fix the build * Prettier * Add missing license headers * Fix an issue that caused mocks to get included into build * Update other references to src/ * Re-run Prettier * Fix lint * Fix weird Flow violation I didn't change this file but Flow started complaining. Caleb said this annotation was unnecessarily using $Abstract though so I removed it. * Update sizes * Fix stats script * Fix packaging fixtures Use file: instead of NODE_PATH since NODE_PATH. NODE_PATH trick only worked because we had no react/react-dom in root node_modules, but now we do. file: dependency only works as I expect in Yarn, so I moved the packaging fixtures to use Yarn and committed lockfiles. Verified that the page shows up. * Fix art fixture * Fix reconciler fixture * Fix SSR fixture * Rename native packages
2017-10-19 07:22:21 +08:00
rendererPackageName: 'react-native-renderer',
rendererConfig: {
getInspectorDataForViewTag: getInspectorDataForViewTag,
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
null,
findNodeHandle,
),
},
});