mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
refactor[AppContainer]: migrate to functional component (#41285)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41285 Changelog: [Internal] I have an ongoing work on improving debugging overlays, lots of changes in these files are planned. - Split `AppContainer` into 2 implementations: DEV and PROD - Rewrite to functional components Reviewed By: javache Differential Revision: D50559549 fbshipit-source-id: 3790f149d504bd45be78c6f3103e05677f3a107d
This commit is contained in:
committed by
Facebook GitHub Bot
parent
19945ad83b
commit
9cc46f58ad
+8
-6
@@ -29,13 +29,17 @@ const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
|
||||
// Required for React DevTools to view/edit React Native styles in Flipper.
|
||||
// Flipper doesn't inject these values when initializing DevTools.
|
||||
hook.resolveRNStyle = require('../StyleSheet/flattenStyle');
|
||||
hook.nativeStyleEditorValidAttributes = Object.keys(ReactNativeStyleAttributes);
|
||||
if (hook) {
|
||||
hook.resolveRNStyle = require('../StyleSheet/flattenStyle');
|
||||
hook.nativeStyleEditorValidAttributes = Object.keys(
|
||||
ReactNativeStyleAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
class Inspector extends React.Component<
|
||||
{
|
||||
inspectedView: ?HostRef,
|
||||
onRequestRerenderApp: (callback: (instance: ?HostRef) => void) => void,
|
||||
onRequestRerenderApp: () => void,
|
||||
...
|
||||
},
|
||||
{
|
||||
@@ -194,9 +198,7 @@ class Inspector extends React.Component<
|
||||
|
||||
setTouchTargeting(val: boolean) {
|
||||
PressabilityDebug.setEnabled(val);
|
||||
this.props.onRequestRerenderApp(inspectedView => {
|
||||
this.setState({inspectedView});
|
||||
});
|
||||
this.props.onRequestRerenderApp();
|
||||
}
|
||||
|
||||
setNetworking(val: boolean) {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* 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 strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {Props} from './AppContainer';
|
||||
|
||||
import TraceUpdateOverlay from '../Components/TraceUpdateOverlay/TraceUpdateOverlay';
|
||||
import View from '../Components/View/View';
|
||||
import ViewNativeComponent from '../Components/View/ViewNativeComponent';
|
||||
import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
|
||||
import ReactDevToolsOverlay from '../Inspector/DevtoolsOverlay';
|
||||
import LogBoxNotificationContainer from '../LogBox/LogBoxNotificationContainer';
|
||||
import StyleSheet from '../StyleSheet/StyleSheet';
|
||||
import {RootTagContext, createRootTag} from './RootTag';
|
||||
import * as React from 'react';
|
||||
|
||||
const {useEffect, useState, useCallback} = React;
|
||||
|
||||
const reactDevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
|
||||
type InspectorDeferredProps = {
|
||||
inspectedView: React.ElementRef<typeof ViewNativeComponent> | null,
|
||||
onInspectedViewRerenderRequest: () => void,
|
||||
};
|
||||
|
||||
const InspectorDeferred = ({
|
||||
inspectedView,
|
||||
onInspectedViewRerenderRequest,
|
||||
}: InspectorDeferredProps) => {
|
||||
// D39382967 adds a require cycle: InitializeCore -> AppContainer -> Inspector -> InspectorPanel -> ScrollView -> InitializeCore
|
||||
// We can't remove it yet, fallback to dynamic require for now. This is the only reason why this logic is in a separate function.
|
||||
const Inspector = require('../Inspector/Inspector');
|
||||
|
||||
return (
|
||||
<Inspector
|
||||
inspectedView={inspectedView}
|
||||
onRequestRerenderApp={onInspectedViewRerenderRequest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const AppContainer = ({
|
||||
children,
|
||||
fabric,
|
||||
initialProps,
|
||||
internal_excludeInspector = false,
|
||||
internal_excludeLogBox = false,
|
||||
rootTag,
|
||||
showArchitectureIndicator,
|
||||
WrapperComponent,
|
||||
}: Props): React.Node => {
|
||||
const [mainRef, setMainRef] = useState<React.ElementRef<
|
||||
typeof ViewNativeComponent,
|
||||
> | null>(null);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const [shouldRenderInspector, setShouldRenderInspector] = useState(false);
|
||||
const [shouldRenderDebuggingOverlays, setShouldRenderDebuggingOverlays] =
|
||||
useState(reactDevToolsHook?.reactDevtoolsAgent != null);
|
||||
|
||||
useEffect(() => {
|
||||
let inspectorSubscription = null;
|
||||
if (!internal_excludeInspector) {
|
||||
inspectorSubscription = RCTDeviceEventEmitter.addListener(
|
||||
'toggleElementInspector',
|
||||
() => setShouldRenderInspector(value => !value),
|
||||
);
|
||||
}
|
||||
|
||||
let reactDevToolsAgentListener = null;
|
||||
// Subscribe listener, if agent is not attached yet
|
||||
if (
|
||||
reactDevToolsHook != null &&
|
||||
reactDevToolsHook.reactDevtoolsAgent == null
|
||||
) {
|
||||
reactDevToolsAgentListener = () => setShouldRenderDebuggingOverlays(true);
|
||||
reactDevToolsHook.on?.('react-devtools', reactDevToolsAgentListener);
|
||||
}
|
||||
|
||||
return () => {
|
||||
inspectorSubscription?.remove();
|
||||
|
||||
if (
|
||||
reactDevToolsHook?.off != null &&
|
||||
reactDevToolsAgentListener != null
|
||||
) {
|
||||
reactDevToolsHook.off('react-devtools', reactDevToolsAgentListener);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
let innerView: React.Node = (
|
||||
<View
|
||||
collapsable={!shouldRenderDebuggingOverlays && !shouldRenderInspector}
|
||||
pointerEvents="box-none"
|
||||
key={key}
|
||||
style={styles.container}
|
||||
ref={setMainRef}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (WrapperComponent != null) {
|
||||
innerView = (
|
||||
<WrapperComponent
|
||||
initialProps={initialProps}
|
||||
fabric={fabric === true}
|
||||
showArchitectureIndicator={showArchitectureIndicator === true}>
|
||||
{innerView}
|
||||
</WrapperComponent>
|
||||
);
|
||||
}
|
||||
|
||||
const onInspectedViewRerenderRequest = useCallback(
|
||||
() => setKey(k => k + 1),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<RootTagContext.Provider value={createRootTag(rootTag)}>
|
||||
<View style={styles.container} pointerEvents="box-none">
|
||||
{innerView}
|
||||
|
||||
{shouldRenderDebuggingOverlays && <TraceUpdateOverlay />}
|
||||
{shouldRenderDebuggingOverlays && (
|
||||
<ReactDevToolsOverlay inspectedView={mainRef} />
|
||||
)}
|
||||
|
||||
{shouldRenderInspector && (
|
||||
<InspectorDeferred
|
||||
inspectedView={mainRef}
|
||||
onInspectedViewRerenderRequest={onInspectedViewRerenderRequest}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!internal_excludeLogBox && <LogBoxNotificationContainer />}
|
||||
</View>
|
||||
</RootTagContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {flex: 1},
|
||||
});
|
||||
|
||||
export default AppContainer;
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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 strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {Props} from './AppContainer';
|
||||
|
||||
import View from '../Components/View/View';
|
||||
import StyleSheet from '../StyleSheet/StyleSheet';
|
||||
import {RootTagContext, createRootTag} from './RootTag';
|
||||
import * as React from 'react';
|
||||
|
||||
const AppContainer = ({
|
||||
children,
|
||||
fabric,
|
||||
initialProps,
|
||||
rootTag,
|
||||
showArchitectureIndicator,
|
||||
WrapperComponent,
|
||||
}: Props): React.Node => {
|
||||
let innerView = children;
|
||||
|
||||
if (WrapperComponent != null) {
|
||||
innerView = (
|
||||
<WrapperComponent
|
||||
initialProps={initialProps}
|
||||
fabric={fabric === true}
|
||||
showArchitectureIndicator={showArchitectureIndicator === true}>
|
||||
{innerView}
|
||||
</WrapperComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RootTagContext.Provider value={createRootTag(rootTag)}>
|
||||
<View style={styles.root} pointerEvents="box-none">
|
||||
{innerView}
|
||||
</View>
|
||||
</RootTagContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {flex: 1},
|
||||
});
|
||||
|
||||
export default AppContainer;
|
||||
+6
-155
@@ -10,170 +10,21 @@
|
||||
|
||||
import type {RootTag} from './RootTag';
|
||||
|
||||
import View from '../Components/View/View';
|
||||
import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
|
||||
import StyleSheet from '../StyleSheet/StyleSheet';
|
||||
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
|
||||
import {RootTagContext, createRootTag} from './RootTag';
|
||||
import * as React from 'react';
|
||||
|
||||
const reactDevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
export type Props = $ReadOnly<{|
|
||||
children?: React.Node,
|
||||
fabric?: boolean,
|
||||
rootTag: number | RootTag,
|
||||
initialProps?: {...},
|
||||
showArchitectureIndicator?: boolean,
|
||||
WrapperComponent?: ?React.ComponentType<any>,
|
||||
internal_excludeLogBox?: ?boolean,
|
||||
internal_excludeInspector?: ?boolean,
|
||||
internal_excludeLogBox?: boolean,
|
||||
internal_excludeInspector?: boolean,
|
||||
|}>;
|
||||
|
||||
type State = {|
|
||||
inspector: ?React.Node,
|
||||
devtoolsOverlay: ?React.Node,
|
||||
traceUpdateOverlay: ?React.Node,
|
||||
mainKey: number,
|
||||
|};
|
||||
|
||||
class AppContainer extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
inspector: null,
|
||||
devtoolsOverlay: null,
|
||||
traceUpdateOverlay: null,
|
||||
mainKey: 1,
|
||||
};
|
||||
_mainRef: ?React.ElementRef<typeof View>;
|
||||
_subscription: ?EventSubscription = null;
|
||||
_reactDevToolsAgentListener: ?() => void = null;
|
||||
|
||||
static getDerivedStateFromError: any = undefined;
|
||||
|
||||
mountReactDevToolsOverlays(): void {
|
||||
if (__DEV__) {
|
||||
const DevtoolsOverlay = require('../Inspector/DevtoolsOverlay').default;
|
||||
const devtoolsOverlay = <DevtoolsOverlay inspectedView={this._mainRef} />;
|
||||
|
||||
const TraceUpdateOverlay =
|
||||
require('../Components/TraceUpdateOverlay/TraceUpdateOverlay').default;
|
||||
const traceUpdateOverlay = <TraceUpdateOverlay />;
|
||||
|
||||
this.setState({devtoolsOverlay, traceUpdateOverlay});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
if (__DEV__) {
|
||||
if (!this.props.internal_excludeInspector) {
|
||||
this._subscription = RCTDeviceEventEmitter.addListener(
|
||||
'toggleElementInspector',
|
||||
() => {
|
||||
const Inspector = require('../Inspector/Inspector');
|
||||
const inspector = this.state.inspector ? null : (
|
||||
<Inspector
|
||||
inspectedView={this._mainRef}
|
||||
onRequestRerenderApp={updateInspectedView => {
|
||||
this.setState(
|
||||
s => ({mainKey: s.mainKey + 1}),
|
||||
() => updateInspectedView(this._mainRef),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
this.setState({inspector});
|
||||
},
|
||||
);
|
||||
|
||||
if (reactDevToolsHook != null) {
|
||||
if (reactDevToolsHook.reactDevtoolsAgent) {
|
||||
// In case if this is not the first AppContainer rendered and React DevTools are already attached
|
||||
this.mountReactDevToolsOverlays();
|
||||
return;
|
||||
}
|
||||
|
||||
this._reactDevToolsAgentListener = () =>
|
||||
this.mountReactDevToolsOverlays();
|
||||
|
||||
if (reactDevToolsHook.on != null) {
|
||||
reactDevToolsHook.on(
|
||||
'react-devtools',
|
||||
this._reactDevToolsAgentListener,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this._subscription != null) {
|
||||
this._subscription.remove();
|
||||
}
|
||||
|
||||
if (
|
||||
reactDevToolsHook?.off != null &&
|
||||
this._reactDevToolsAgentListener != null
|
||||
) {
|
||||
reactDevToolsHook.off('react-devtools', this._reactDevToolsAgentListener);
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
let logBox = null;
|
||||
if (__DEV__) {
|
||||
if (!this.props.internal_excludeLogBox) {
|
||||
const LogBoxNotificationContainer =
|
||||
require('../LogBox/LogBoxNotificationContainer').default;
|
||||
logBox = <LogBoxNotificationContainer />;
|
||||
}
|
||||
}
|
||||
|
||||
let innerView: React.Node = (
|
||||
<View
|
||||
collapsable={!this.state.inspector && !this.state.devtoolsOverlay}
|
||||
key={this.state.mainKey}
|
||||
pointerEvents="box-none"
|
||||
style={styles.appContainer}
|
||||
ref={ref => {
|
||||
this._mainRef = ref;
|
||||
}}>
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
|
||||
const Wrapper = this.props.WrapperComponent;
|
||||
if (Wrapper != null) {
|
||||
innerView = (
|
||||
<Wrapper
|
||||
initialProps={this.props.initialProps}
|
||||
fabric={this.props.fabric === true}
|
||||
showArchitectureIndicator={
|
||||
this.props.showArchitectureIndicator === true
|
||||
}>
|
||||
{innerView}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RootTagContext.Provider value={createRootTag(this.props.rootTag)}>
|
||||
<View style={styles.appContainer} pointerEvents="box-none">
|
||||
{innerView}
|
||||
{this.state.traceUpdateOverlay}
|
||||
{this.state.devtoolsOverlay}
|
||||
{this.state.inspector}
|
||||
{logBox}
|
||||
</View>
|
||||
</RootTagContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
appContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
const AppContainer: React.AbstractComponent<Props> = __DEV__
|
||||
? require('./AppContainer-dev').default
|
||||
: require('./AppContainer-prod').default;
|
||||
|
||||
module.exports = AppContainer;
|
||||
|
||||
Reference in New Issue
Block a user