feat: integrate reanimated based stack (#42)

This commit is contained in:
Satyajit Sahoo
2019-08-06 12:22:45 +02:00
committed by Michał Osadnik
parent 89c279fb23
commit dcf57c095c
15 changed files with 184 additions and 177 deletions

View File

@@ -40,6 +40,11 @@
"prettier": "^1.18.2",
"typescript": "^3.5.1"
},
"resolutions": {
"react": "16.8.3",
"react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
"react-native-safe-area-view": "0.14.6"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",

View File

@@ -1,7 +1,7 @@
import { PartialState, NavigationState, TargetRoute } from './types';
export type Action =
| { type: 'GO_BACK' }
| { type: 'GO_BACK'; source?: string }
| {
type: 'NAVIGATE';
payload:

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { NavigationProp, ParamListBase } from './types';
const NavigationContext = React.createContext<
NavigationProp<ParamListBase> | undefined
NavigationProp<ParamListBase, string, any, any> | undefined
>(undefined);
export default NavigationContext;

View File

@@ -11,21 +11,24 @@ import {
RouteConfig,
} from './types';
type Props<ScreenOptions extends object> = {
type Props<State extends NavigationState, ScreenOptions extends object> = {
screen: RouteConfig<ParamListBase, string, ScreenOptions>;
navigation: NavigationProp<ParamListBase>;
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
route: Route<string> & { state?: NavigationState };
getState: () => NavigationState;
setState: (state: NavigationState) => void;
};
export default function SceneView<ScreenOptions extends object>({
export default function SceneView<
State extends NavigationState,
ScreenOptions extends object
>({
screen,
route,
navigation,
getState,
setState,
}: Props<ScreenOptions>) {
}: Props<State, ScreenOptions>) {
const { performTransaction } = React.useContext(NavigationStateContext);
const getCurrentState = React.useCallback(() => {

View File

@@ -349,7 +349,13 @@ export type CompositeNavigationProp<
A extends NavigationProp<any, any, any, any, infer E> ? E : {}
>;
export type Descriptor<ScreenOptions extends object> = {
export type Descriptor<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string,
State extends NavigationState = NavigationState,
ScreenOptions extends object = {},
EventMap extends { [key: string]: any } = {}
> = {
/**
* Render the component associated with this route.
*/
@@ -359,6 +365,17 @@ export type Descriptor<ScreenOptions extends object> = {
* Options for the route.
*/
options: ScreenOptions;
/**
* Navigation object for the screen
*/
navigation: NavigationProp<
ParamList,
RouteName,
State,
ScreenOptions,
EventMap
>;
};
export type RouteConfig<
@@ -378,7 +395,7 @@ export type RouteConfig<
| ScreenOptions
| ((props: {
route: RouteProp<ParamList, RouteName>;
navigation: NavigationHelpersCommon<ParamList>;
navigation: any;
}) => ScreenOptions);
/**

View File

@@ -31,7 +31,10 @@ type Options<ScreenOptions extends object> = {
emitter: NavigationEventEmitter;
};
export default function useDescriptors<ScreenOptions extends object>({
export default function useDescriptors<
State extends NavigationState,
ScreenOptions extends object
>({
state,
screens,
navigation,
@@ -61,7 +64,7 @@ export default function useDescriptors<ScreenOptions extends object>({
]
);
const navigations = useNavigationCache({
const navigations = useNavigationCache<State, ScreenOptions>({
state,
getState,
navigation,
@@ -97,9 +100,12 @@ export default function useDescriptors<ScreenOptions extends object>({
})),
...options[route.key],
},
navigation: navigations[route.key],
};
return acc;
},
{} as { [key: string]: Descriptor<ScreenOptions> }
{} as {
[key: string]: Descriptor<ParamListBase, string, State, ScreenOptions>;
}
);
}

View File

@@ -198,7 +198,7 @@ export default function useNavigationBuilder<
actionCreators: router.actionCreators,
});
const descriptors = useDescriptors<ScreenOptions>({
const descriptors = useDescriptors<State, ScreenOptions>({
state,
screens,
navigation,

View File

@@ -18,66 +18,69 @@ type Options = {
emitter: NavigationEventEmitter;
};
type NavigationCache = { [key: string]: NavigationProp<ParamListBase> };
type NavigationCache<
State extends NavigationState,
ScreenOptions extends object
> = {
[key: string]: NavigationProp<ParamListBase, string, State, ScreenOptions>;
};
export default function useNavigationCache({
state,
getState,
navigation,
setOptions,
emitter,
}: Options) {
// eslint-disable-next-line react-hooks/exhaustive-deps
const cache = React.useMemo(() => ({ current: {} as NavigationCache }), [
getState,
navigation,
setOptions,
emitter,
]);
export default function useNavigationCache<
State extends NavigationState,
ScreenOptions extends object
>({ state, getState, navigation, setOptions, emitter }: Options) {
const cache = React.useMemo(
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[getState, navigation, setOptions, emitter]
);
cache.current = state.routes.reduce<NavigationCache>((acc, route, index) => {
const previous = cache.current[route.key];
const isFirst = route.key === state.routes[0].key;
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
(acc, route, index) => {
const previous = cache.current[route.key];
const isFirst = route.key === state.routes[0].key;
if (previous && previous.isFirstRouteInParent() === isFirst) {
acc[route.key] = previous;
} else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { emit, ...rest } = navigation;
if (previous && previous.isFirstRouteInParent() === isFirst) {
acc[route.key] = previous;
} else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { emit, ...rest } = navigation;
acc[route.key] = {
...rest,
...emitter.create(route.key),
dispatch: (
action:
| NavigationAction
| ((state: NavigationState) => NavigationState)
) =>
navigation.dispatch(
typeof action === 'object' && action != null
? { source: route.key, ...action }
: action
),
setOptions: (options: object) =>
setOptions(o => ({
...o,
[route.key]: { ...o[route.key], ...options },
})),
isFocused: () => {
const state = getState();
acc[route.key] = {
...rest,
...emitter.create(route.key),
dispatch: (
action:
| NavigationAction
| ((state: NavigationState) => NavigationState)
) =>
navigation.dispatch(
typeof action === 'object' && action != null
? { source: route.key, ...action }
: action
),
setOptions: (options: object) =>
setOptions(o => ({
...o,
[route.key]: { ...o[route.key], ...options },
})),
isFocused: () => {
const state = getState();
if (index !== state.index) {
return false;
}
if (index !== state.index) {
return false;
}
return navigation ? navigation.isFocused() : true;
},
isFirstRouteInParent: () => isFirst,
} as NavigationProp<ParamListBase>;
}
return navigation ? navigation.isFocused() : true;
},
isFirstRouteInParent: () => isFirst,
} as NavigationProp<ParamListBase, string, State, ScreenOptions>;
}
return acc;
}, {});
return acc;
},
{}
);
return cache.current;
}

View File

@@ -29,11 +29,13 @@ module.exports = {
providesModuleNodeModules: [
'@babel/runtime',
'@react-native-community/masked-view',
'react',
'react-native',
'react-native-gesture-handler',
'react-native-reanimated',
'react-native-safe-area-view',
'react-native-screens',
'react-native-tab-view',
'shortid',
],

View File

@@ -17,6 +17,7 @@
"eject": "expo eject"
},
"dependencies": {
"@react-native-community/masked-view": "^0.1.1",
"expo": "^34.0.1",
"react": "16.8.3",
"react-dom": "^16.8.3",
@@ -24,6 +25,7 @@
"react-native-gesture-handler": "~1.3.0",
"react-native-paper": "3.0.0-alpha.3",
"react-native-reanimated": "~1.1.0",
"react-native-screens": "1.0.0-alpha.22",
"react-native-tab-view": "2.7.1",
"react-native-web": "^0.11.4",
"scheduler": "^0.14.0",
@@ -36,10 +38,5 @@
"@types/react-native": "^0.57.65",
"babel-preset-expo": "^6.0.0",
"expo-cli": "^3.0.6"
},
"resolutions": {
"react": "16.8.3",
"react-dom": "^16.8.3",
"react-native-safe-area-view": "0.14.6"
}
}

View File

@@ -22,7 +22,7 @@ type Props = TabRouterOptions &
children: React.ReactNode;
};
export type TabNavigationOptions = {
export type MaterialTopTabNavigationOptions = {
/**
* Title text for the screen.
*/
@@ -36,7 +36,7 @@ export type MaterialTopTabNavigationProp<
ParamList,
RouteName,
TabNavigationState,
TabNavigationOptions
MaterialTopTabNavigationOptions
> & {
/**
* Jump to an existing tab.
@@ -51,7 +51,7 @@ export type MaterialTopTabNavigationProp<
): void;
};
export function TabNavigator({
function TabNavigator({
initialRouteName,
backBehavior,
children,
@@ -59,7 +59,7 @@ export function TabNavigator({
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
TabNavigationState,
TabNavigationOptions,
MaterialTopTabNavigationOptions,
TabRouterOptions
>(TabRouter, {
initialRouteName,
@@ -85,6 +85,7 @@ export function TabNavigator({
);
}
export default createNavigator<TabNavigationOptions, typeof TabNavigator>(
TabNavigator
);
export default createNavigator<
MaterialTopTabNavigationOptions,
typeof TabNavigator
>(TabNavigator);

View File

@@ -7,23 +7,37 @@ import {
DefaultRouterOptions,
} from '@navigation-ex/core';
type Action =
export type StackActionType =
| {
type: 'PUSH';
payload: { name: string; params?: object };
source?: string;
}
| {
type: 'POP';
payload: { count: number };
source?: string;
}
| { type: 'POP_TO_TOP' };
| { type: 'POP_TO_TOP'; source?: string };
export type StackRouterOptions = DefaultRouterOptions;
export type StackNavigationState = NavigationState;
export const StackActions = {
push(name: string, params?: object): StackActionType {
return { type: 'PUSH', payload: { name, params } };
},
pop(count: number = 1): StackActionType {
return { type: 'POP', payload: { count } };
},
popToTop(): StackActionType {
return { type: 'POP_TO_TOP' };
},
};
export default function StackRouter(options: StackRouterOptions) {
const router: Router<StackNavigationState, CommonAction | Action> = {
const router: Router<StackNavigationState, CommonAction | StackActionType> = {
...BaseRouter,
getInitialState({ routeNames, routeParamList }) {
@@ -107,15 +121,21 @@ export default function StackRouter(options: StackRouterOptions) {
return null;
case 'POP':
if (state.index > 0) {
return {
...state,
index: state.index - 1,
routes: state.routes.slice(
0,
Math.max(state.routes.length - action.payload.count, 1)
),
};
{
const index = action.source
? state.routes.findIndex(r => r.key === action.source)
: state.routes.length - 1;
if (state.index > 0 && index > -1) {
return {
...state,
index: state.index - 1,
routes: state.routes.slice(
0,
Math.max(index - action.payload.count + 1, 1)
),
};
}
}
return null;
@@ -124,6 +144,7 @@ export default function StackRouter(options: StackRouterOptions) {
return router.getStateForAction(state, {
type: 'POP',
payload: { count: state.routes.length - 1 },
source: action.source,
});
case 'NAVIGATE':
@@ -163,6 +184,7 @@ export default function StackRouter(options: StackRouterOptions) {
name: action.payload.name,
params: action.payload.params,
},
source: action.source,
});
}
@@ -189,6 +211,7 @@ export default function StackRouter(options: StackRouterOptions) {
return router.getStateForAction(state, {
type: 'POP',
payload: { count: 1 },
source: action.source,
});
default:
@@ -196,17 +219,7 @@ export default function StackRouter(options: StackRouterOptions) {
}
},
actionCreators: {
push(name: string, params?: object) {
return { type: 'PUSH', payload: { name, params } };
},
pop(count: number = 1) {
return { type: 'POP', payload: { count } };
},
popToTop() {
return { type: 'POP_TO_TOP' };
},
},
actionCreators: StackActions,
};
return router;

View File

@@ -7,9 +7,10 @@ import {
Router,
} from '@navigation-ex/core';
type Action = {
export type TabActionType = {
type: 'JUMP_TO';
payload: { name: string; params?: object };
source?: string;
};
export type TabRouterOptions = DefaultRouterOptions & {
@@ -23,6 +24,12 @@ export type TabNavigationState = NavigationState & {
routeKeyHistory: string[];
};
export const TabActions = {
jumpTo(name: string, params?: object): TabActionType {
return { type: 'JUMP_TO', payload: { name, params } };
},
};
const changeIndex = (state: TabNavigationState, index: number) => {
const previousKey = state.routes[state.index].key;
const currentKey = state.routes[index].key;
@@ -41,7 +48,7 @@ export default function TabRouter({
initialRouteName,
backBehavior = 'history',
}: TabRouterOptions) {
const router: Router<TabNavigationState, Action | CommonAction> = {
const router: Router<TabNavigationState, TabActionType | CommonAction> = {
...BaseRouter,
getInitialState({ routeNames, routeParamList }) {
@@ -203,11 +210,7 @@ export default function TabRouter({
return action.type === 'NAVIGATE';
},
actionCreators: {
jumpTo(name: string, params?: object) {
return { type: 'JUMP_TO', payload: { name, params } };
},
},
actionCreators: TabActions,
};
return router;

View File

@@ -1,11 +1,15 @@
export {
default as StackRouter,
StackActions,
StackActionType,
StackRouterOptions,
StackNavigationState,
} from './StackRouter';
export {
default as TabRouter,
TabActions,
TabActionType,
TabRouterOptions,
TabNavigationState,
} from './TabRouter';

View File

@@ -1660,6 +1660,11 @@
xcode "^2.0.0"
xmldoc "^0.4.0"
"@react-native-community/masked-view@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.1.tgz#dbcfc5ec08efbb02d4142dd9426c8d7a396829d7"
integrity sha512-EyJVSbarZkOPYq+zCZLx9apMcpwkX9HvH6R+6CeVL29q88kEFemnLO/IhmE4YX/0MfalsduI8eTi7fuQh/5VeA==
"@segment/loosely-validate-event@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz#87dfc979e5b4e7b82c5f1d8b722dfd5d77644681"
@@ -11454,7 +11459,7 @@ react-native-branch@~3.0.1:
resolved "https://registry.yarnpkg.com/react-native-branch/-/react-native-branch-3.0.1.tgz#5b07b61cbd290168cd3c3662e017ebe0f356d2ca"
integrity sha512-vbcYxPZlpF5f39GAEUF8kuGQqCNeD3E6zEdvtOq8oCGZunHXlWlKgAS6dgBKCvsHvXgHuMtpvs39VgOp8DaKig==
react-native-gesture-handler@~1.3.0:
react-native-gesture-handler@^1.3.0, react-native-gesture-handler@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.3.0.tgz#d0386f565928ccc1849537f03f2e37fd5f6ad43f"
integrity sha512-ASRFIXBuKRvqlmwkWJhV8yP2dTpvcqVrLNpd7FKVBFHYWr6SAxjGyO9Ik8w1lAxDhMlRP2IcJ9p9eq5X2WWeLQ==
@@ -11470,21 +11475,32 @@ react-native-paper@3.0.0-alpha.3:
dependencies:
"@callstack/react-theme-provider" "^3.0.2"
color "^3.1.2"
react-native-safe-area-view "^0.14.6"
react-native-safe-area-view "^0.12.0"
react-native-reanimated@~1.1.0:
react-native-reanimated@^1.1.0, react-native-reanimated@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.1.0.tgz#ba6864055ec3a206cdd5209a293fe653ce276206"
integrity sha512-UGDVNfvuIkMqYUx6aytSzihuzv6sWubn0MQi8dRcw7BjgezhjJnVnJ/NSOcpL3cO+Ld7lFcRX6GKcskwkHdPkw==
react-native-safe-area-view@^0.14.6:
react-native-safe-area-view@0.14.6, react-native-safe-area-view@^0.12.0, react-native-safe-area-view@^0.14.6:
version "0.14.6"
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.14.6.tgz#9a9d37d9f8f3887d60c4076eae7b5d2319539446"
integrity sha512-dbzuvaeHFV1VBpyMaC0gtJ2BqFt6ls/405A0t78YN1sXiTrVr3ki86Ysct8mzifWqLdvWzcWagE5wfMtdxnqoA==
dependencies:
hoist-non-react-statics "^2.3.1"
react-native-screens@1.0.0-alpha.22:
version "1.0.0-alpha.22"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-1.0.0-alpha.22.tgz#7a120377b52aa9bbb94d0b8541a014026be9289b"
integrity sha512-kSyAt0AeVU6N7ZonfV6dP6iZF8B7Bce+tk3eujXhzBGsLg0VSLnU7uE9VqJF0xdQrHR91ZjGgVMieo/8df9KTA==
react-native-screens@^1.0.0-alpha.22:
version "1.0.0-alpha.23"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-1.0.0-alpha.23.tgz#25d7ea4d11bda4fcde2d1da7ae50271c6aa636e0"
integrity sha512-tOxHGQUN83MTmQB4ghoQkibqOdGiX4JQEmeyEv96MKWO/x8T2PJv84ECUos9hD3blPRQwVwSpAid1PPPhrVEaw==
dependencies:
debounce "^1.2.0"
react-native-tab-view@2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-2.7.1.tgz#6247ea6027be5656cb83d86ed9ee3e2c3f6e1a3b"
@@ -11523,63 +11539,7 @@ react-native-web@^0.11.4:
prop-types "^15.6.0"
react-timer-mixin "^0.13.4"
react-native@^0.59.8:
version "0.59.10"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.59.10.tgz#352f381e382f93a0403be499c9e384bf51c2591c"
integrity sha512-guB9YW+pBqS1dnfZ4ntzjINopiCUAbdmshU2wMWD1W32fRczLAopi/7Q2iHKP8LTCdxuYZV3fa9Mew5PSuANAw==
dependencies:
"@babel/runtime" "^7.0.0"
"@react-native-community/cli" "^1.2.1"
absolute-path "^0.0.0"
art "^0.10.0"
base64-js "^1.1.2"
chalk "^2.4.1"
commander "^2.9.0"
compression "^1.7.1"
connect "^3.6.5"
create-react-class "^15.6.3"
debug "^2.2.0"
denodeify "^1.2.1"
errorhandler "^1.5.0"
escape-string-regexp "^1.0.5"
event-target-shim "^1.0.5"
fbjs "^1.0.0"
fbjs-scripts "^1.0.0"
fs-extra "^1.0.0"
glob "^7.1.1"
graceful-fs "^4.1.3"
inquirer "^3.0.6"
invariant "^2.2.4"
lodash "^4.17.5"
metro-babel-register "0.51.0"
metro-react-native-babel-transformer "0.51.0"
mime "^1.3.4"
minimist "^1.2.0"
mkdirp "^0.5.1"
morgan "^1.9.0"
node-fetch "^2.2.0"
node-notifier "^5.2.1"
npmlog "^2.0.4"
nullthrows "^1.1.0"
opn "^3.0.2"
optimist "^0.6.1"
plist "^3.0.0"
pretty-format "24.0.0-alpha.6"
promise "^7.1.1"
prop-types "^15.5.8"
react-clone-referenced-element "^1.0.1"
react-devtools-core "^3.6.0"
regenerator-runtime "^0.11.0"
rimraf "^2.5.4"
semver "^5.0.3"
serve-static "^1.13.1"
shell-quote "1.6.1"
stacktrace-parser "^0.1.3"
ws "^1.1.5"
xmldoc "^0.4.0"
yargs "^9.0.0"
"react-native@https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz":
react-native@^0.59.8, "react-native@https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz":
version "0.59.8"
resolved "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz#717c25bde6007a70e9f206ef4360999dae18e7b0"
dependencies:
@@ -12906,13 +12866,6 @@ stacktrace-parser@0.1.4:
resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz#01397922e5f62ecf30845522c95c4fe1d25e7d4e"
integrity sha1-ATl5IuX2Ls8whFUiyVxP4dJefU4=
stacktrace-parser@^0.1.3:
version "0.1.6"
resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.6.tgz#c17d466d15ba51bee2f753d064f17327a886ff37"
integrity sha512-wXhu0Z8YgCGigUtHQq+J7pjXCppk3Um5DwH4qskOKHMlJmKwuuUSm+wDAgU7t4sbVjvuDTNGwOfFKgjMEqSflA==
dependencies:
type-fest "^0.3.0"
state-toggle@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.2.tgz#75e93a61944116b4959d665c8db2d243631d6ddc"
@@ -13602,7 +13555,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-fest@^0.3.0, type-fest@^0.3.1:
type-fest@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==