mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-06 22:39:41 +08:00
feat: add integration with redux devtools extension
Currently supports: - Tracking actions and navigation state - Time travel for navigation state It doesn't do anything in production
This commit is contained in:
committed by
Satyajit Sahoo
parent
b7735af7fc
commit
ca985bb96a
@@ -25,6 +25,9 @@ const NavigationBuilderContext = React.createContext<{
|
||||
addActionListener?: (listener: ChildActionListener) => void;
|
||||
addFocusedListener?: (listener: FocusedNavigationListener) => void;
|
||||
onRouteFocus?: (key: string) => void;
|
||||
}>({});
|
||||
trackAction: (key: string, action: NavigationAction) => void;
|
||||
}>({
|
||||
trackAction: () => undefined,
|
||||
});
|
||||
|
||||
export default NavigationBuilderContext;
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as BaseActions from './BaseActions';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import useFocusedListeners from './useFocusedListeners';
|
||||
import useDevTools from './useDevTools';
|
||||
|
||||
import {
|
||||
Route,
|
||||
@@ -133,12 +134,25 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
const transactionStateRef = React.useRef<State | null>(null);
|
||||
const isTransactionActiveRef = React.useRef<boolean>(false);
|
||||
const isFirstMountRef = React.useRef<boolean>(true);
|
||||
const skipTrackingRef = React.useRef<boolean>(false);
|
||||
|
||||
const reset = React.useCallback((state: NavigationState) => {
|
||||
skipTrackingRef.current = true;
|
||||
setNavigationState(state);
|
||||
}, []);
|
||||
|
||||
const { trackState, trackAction } = useDevTools({
|
||||
name: '@navigation-ex',
|
||||
reset,
|
||||
state,
|
||||
});
|
||||
|
||||
const builderContext = React.useMemo(
|
||||
() => ({
|
||||
addFocusedListener,
|
||||
trackAction,
|
||||
}),
|
||||
[addFocusedListener]
|
||||
[addFocusedListener, trackAction]
|
||||
);
|
||||
|
||||
const performTransaction = React.useCallback((callback: () => void) => {
|
||||
@@ -189,6 +203,12 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (skipTrackingRef.current) {
|
||||
skipTrackingRef.current = false;
|
||||
} else {
|
||||
trackState(state);
|
||||
}
|
||||
|
||||
navigationStateRef.current = state;
|
||||
transactionStateRef.current = null;
|
||||
|
||||
@@ -197,7 +217,7 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
}
|
||||
|
||||
isFirstMountRef.current = false;
|
||||
}, [state, onStateChange]);
|
||||
}, [state, onStateChange, trackState]);
|
||||
|
||||
return (
|
||||
<NavigationBuilderContext.Provider value={builderContext}>
|
||||
|
||||
@@ -61,6 +61,7 @@ export default function useDescriptors<
|
||||
emitter,
|
||||
}: Options<ScreenOptions>) {
|
||||
const [options, setOptions] = React.useState<{ [key: string]: object }>({});
|
||||
const { trackAction } = React.useContext(NavigationBuilderContext);
|
||||
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
@@ -69,8 +70,16 @@ export default function useDescriptors<
|
||||
addActionListener,
|
||||
addFocusedListener,
|
||||
onRouteFocus,
|
||||
trackAction,
|
||||
}),
|
||||
[navigation, onAction, addActionListener, addFocusedListener, onRouteFocus]
|
||||
[
|
||||
navigation,
|
||||
onAction,
|
||||
addActionListener,
|
||||
addFocusedListener,
|
||||
onRouteFocus,
|
||||
trackAction,
|
||||
]
|
||||
);
|
||||
|
||||
const navigations = useNavigationCache<State, ScreenOptions>({
|
||||
|
||||
101
packages/core/src/useDevTools.tsx
Normal file
101
packages/core/src/useDevTools.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationState, NavigationAction, PartialState } from './types';
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
type Options = {
|
||||
name: string;
|
||||
reset: (state: NavigationState) => void;
|
||||
state: State;
|
||||
};
|
||||
|
||||
type DevTools = {
|
||||
init(value: any): void;
|
||||
send(action: any, value: any): void;
|
||||
subscribe(
|
||||
listener: (message: { type: string; [key: string]: any }) => void
|
||||
): () => void;
|
||||
};
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
__REDUX_DEVTOOLS_EXTENSION__:
|
||||
| {
|
||||
connect(options: { name: string }): DevTools;
|
||||
disconnect(): void;
|
||||
}
|
||||
| undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function useDevTools({ name, reset, state }: Options) {
|
||||
const devToolsRef = React.useRef<DevTools>();
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
global.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||
devToolsRef.current === undefined
|
||||
) {
|
||||
devToolsRef.current = global.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
|
||||
}
|
||||
|
||||
const devTools = devToolsRef.current;
|
||||
const lastStateRef = React.useRef<State>(state);
|
||||
const actions = React.useRef<
|
||||
Array<{ key: string; action: NavigationAction }>
|
||||
>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
devTools && devTools.init(lastStateRef.current);
|
||||
}, [devTools]);
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
devTools &&
|
||||
devTools.subscribe(message => {
|
||||
if (message.type === 'DISPATCH' && message.state) {
|
||||
reset(JSON.parse(message.state));
|
||||
}
|
||||
}),
|
||||
[devTools, reset]
|
||||
);
|
||||
|
||||
const trackState = React.useCallback(
|
||||
(state: State) => {
|
||||
if (!devTools) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (actions.current.length > 1) {
|
||||
devTools.send(actions.current.shift(), lastStateRef.current);
|
||||
}
|
||||
|
||||
if (actions.current.length) {
|
||||
devTools.send(actions.current.pop(), state);
|
||||
} else if (lastStateRef.current !== state) {
|
||||
devTools.send('@@UNKNOWN', state);
|
||||
}
|
||||
|
||||
lastStateRef.current = state;
|
||||
},
|
||||
[devTools]
|
||||
);
|
||||
|
||||
const trackAction = React.useCallback(
|
||||
(key: string, action: NavigationAction) => {
|
||||
if (!devTools) {
|
||||
return;
|
||||
}
|
||||
|
||||
actions.current.push({ key, action });
|
||||
},
|
||||
[devTools]
|
||||
);
|
||||
|
||||
return {
|
||||
trackAction,
|
||||
trackState,
|
||||
};
|
||||
}
|
||||
@@ -32,6 +32,7 @@ export default function useOnAction({
|
||||
onAction: onActionParent,
|
||||
onRouteFocus: onRouteFocusParent,
|
||||
addActionListener: addActionListenerParent,
|
||||
trackAction,
|
||||
} = React.useContext(NavigationBuilderContext);
|
||||
|
||||
const onAction = React.useCallback(
|
||||
@@ -60,6 +61,8 @@ export default function useOnAction({
|
||||
result = result === null && action.target === state.key ? state : result;
|
||||
|
||||
if (result !== null) {
|
||||
trackAction(state.key, action);
|
||||
|
||||
if (state !== result) {
|
||||
setState(result);
|
||||
}
|
||||
@@ -99,6 +102,7 @@ export default function useOnAction({
|
||||
getState,
|
||||
router,
|
||||
onActionParent,
|
||||
trackAction,
|
||||
onRouteFocusParent,
|
||||
setState,
|
||||
key,
|
||||
|
||||
Reference in New Issue
Block a user