refactor: minor tweaks

This commit is contained in:
satyajit.happy
2019-06-10 01:29:06 +02:00
parent f10166df1b
commit 44909bd80a
8 changed files with 140 additions and 58 deletions

View File

@@ -20,18 +20,18 @@ Hook which can access the navigation state from the context. Along with the stat
### Router
An object that provides various actions to modify state as well as helpers.
An object that provides a reducer to update the state as well as some action creators.
### Navigator
Navigators bundle a `NavigationChild`, a `router` and a view which takes the navigation state and decides how to render it.
Navigators bundle a `router` and a view which takes the navigation state and decides how to render it.
A simple navigator could look like this:
```js
function StackNavigator({ initialRouteName, children, ...rest }) {
// The `navigation` object contains the navigation state and some helpers (e.g. push, pop)
// The `descriptors` object contains `navigation` objects for children routes and helper for rendering a screen
// The `descriptors` object contains the screen options and a helper for rendering a screen
const { navigation, descriptors } = useNavigationBuilder(StackRouter, {
initialRouteName,
children,
@@ -44,12 +44,12 @@ function StackNavigator({ initialRouteName, children, ...rest }) {
}
```
The navigator can render a screen by calling `descriptors[route.key].render()`. Internally, the descriptor wraps the screen in a `NavigationProvider` to support nested state:
The navigator can render a screen by calling `descriptors[route.key].render()`. Internally, the descriptor wraps the screen in a `NavigationStateContext.Provider` to support nested state:
```js
<NavigationProvider state={route}>
<NavigationStateContext.Provider state={route.state}>
<MyComponent />
</NavigationProvider>
</NavigationStateContext.Provider>
```
## Basic usage

View File

@@ -24,10 +24,13 @@ type Action =
export type StackNavigationProp = NavigationProp<typeof StackRouter>;
const StackRouter = {
getInitialState(
routeNames: string[],
{ initialRouteName = routeNames[0] }: { initialRouteName?: string }
) {
initial({
routeNames,
initialRouteName = routeNames[0],
}: {
routeNames: string[];
initialRouteName?: string;
}) {
const index = routeNames.indexOf(initialRouteName);
return {
@@ -88,10 +91,16 @@ export default function StackNavigator(props: Props) {
key={route.key}
style={{
position: 'absolute',
top: i * 10,
margin: 20,
left: i * 20,
top: i * 20,
padding: 10,
height: 480,
width: 320,
backgroundColor: 'white',
border: '1px solid black',
borderRadius: 3,
boxShadow:
'0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)',
}}
>
{descriptors[route.key].render()}

View File

@@ -22,10 +22,13 @@ type Action = {
export type TabNavigationProp = NavigationProp<typeof TabRouter>;
const TabRouter = {
getInitialState(
routeNames: string[],
{ initialRouteName = routeNames[0] }: { initialRouteName?: string }
) {
initial({
routeNames,
initialRouteName = routeNames[0],
}: {
routeNames: string[];
initialRouteName?: string;
}) {
const index = routeNames.indexOf(initialRouteName);
return {
@@ -64,14 +67,15 @@ export default function TabNavigator(props: Props) {
const { navigation, descriptors } = useNavigationBuilder(TabRouter, props);
return (
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div style={{ display: 'flex', flexDirection: 'row', height: '100%' }}>
{navigation.state.routes.map((route, i, self) => (
<div
key={route.key}
style={{
width: `${100 / self.length}%`,
padding: 10,
borderRadius: 3,
backgroundColor: i === navigation.state.index ? 'tomato' : 'white',
border: '1px solid black',
}}
>
{descriptors[route.key].render()}

View File

@@ -1,2 +1,34 @@
<style type="text/css">
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
*:focus {
outline: none;
}
*:focus-visible {
outline: auto;
}
html,
body {
height: 100%;
width: 100%;
}
body {
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
background-color: #E2E1E0;
}
</style>
<div id="root"></div>
<script src="./index.tsx"></script>

View File

@@ -7,7 +7,7 @@ import TabNavigator, { TabNavigationProp } from './TabNavigator';
const First = ({ navigation }: { navigation: StackNavigationProp }) => (
<div>
<h1>First route</h1>
<h1>First</h1>
<button type="button" onClick={() => navigation.push('second')}>
Push second
</button>
@@ -22,7 +22,7 @@ const First = ({ navigation }: { navigation: StackNavigationProp }) => (
const Second = ({ navigation }: { navigation: StackNavigationProp }) => (
<div>
<h1>Second route</h1>
<h1>Second</h1>
<button type="button" onClick={() => navigation.push('first')}>
Push first
</button>
@@ -38,7 +38,7 @@ const Fourth = ({
navigation: TabNavigationProp & StackNavigationProp;
}) => (
<div>
<h1>Fourth route</h1>
<h1>Fourth</h1>
<button type="button" onClick={() => navigation.jumpTo('fifth')}>
Jump to fifth
</button>
@@ -57,7 +57,7 @@ const Fifth = ({
navigation: TabNavigationProp & StackNavigationProp;
}) => (
<div>
<h1>Fifth route</h1>
<h1>Fifth</h1>
<button type="button" onClick={() => navigation.jumpTo('fourth')}>
Jump to fourth
</button>

52
src/SceneView.tsx Normal file
View File

@@ -0,0 +1,52 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import { NavigationPropContext } from './useNavigationBuilder';
import { Route, NavigationProp, NavigationState } from './types';
type Props = {
screen:
| { component: React.ComponentType<any> }
| { children: (props: any) => React.ReactNode };
navigation: NavigationProp;
route: Route & { state?: NavigationState };
state: NavigationState;
setState: (state: NavigationState) => void;
};
export default function SceneView(props: Props) {
const { screen, route, state, setState } = props;
const navigation = React.useMemo(
() => ({
...props.navigation,
state: route,
}),
[props.navigation, route]
);
const value = React.useMemo(
() => ({
state: route.state,
setState: (child: NavigationState) =>
setState({
...state,
routes: state.routes.map(r =>
r === route ? { ...route, state: child } : r
),
}),
}),
[route, setState, state]
);
return (
<NavigationStateContext.Provider value={value}>
<NavigationPropContext.Provider value={navigation}>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={navigation} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation })
) : null}
</NavigationPropContext.Provider>
</NavigationStateContext.Provider>
);
}

View File

@@ -14,10 +14,10 @@ export type NavigationAction = {
};
export type Router<Action extends NavigationAction = NavigationAction> = {
getInitialState(
routeNames: string[],
options: { initialRouteName?: string }
): NavigationState;
initial(options: {
routeNames: string[];
initialRouteName?: string;
}): NavigationState;
reduce(state: NavigationState, action: Action): NavigationState;
actions: { [key: string]: (...args: any) => Action };
};

View File

@@ -8,15 +8,16 @@ import {
NavigationState,
} from './types';
import Screen, { Props as ScreenProps } from './Screen';
import SceneView from './SceneView';
type Options = {
initialRouteName?: string;
children: React.ReactElement[];
};
const NavigationPropContext = React.createContext<NavigationProp | undefined>(
undefined
);
export const NavigationPropContext = React.createContext<
NavigationProp | undefined
>(undefined);
export default function useNavigationBuilder(router: Router, options: Options) {
const screens = React.Children.map(options.children, child => {
@@ -35,10 +36,9 @@ export default function useNavigationBuilder(router: Router, options: Options) {
{} as { [key: string]: ScreenProps }
);
const routeNames = Object.keys(screens);
const {
state = router.getInitialState(routeNames, {
state = router.initial({
routeNames: Object.keys(screens),
initialRouteName: options.initialRouteName,
}),
setState,
@@ -69,34 +69,19 @@ export default function useNavigationBuilder(router: Router, options: Options) {
const descriptors = state.routes.reduce(
(acc, route) => {
const screen = screens[route.name];
const nav = {
...navigation,
state: route,
};
acc[route.key] = {
render: () => (
<NavigationStateContext.Provider
value={{
state: route.state,
setState: child =>
setState({
...state,
routes: state.routes.map(r =>
r === route ? { ...route, state: child } : r
),
}),
}}
>
<NavigationPropContext.Provider value={nav}>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={nav} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation: nav })
) : null}
</NavigationPropContext.Provider>
</NavigationStateContext.Provider>
),
render() {
return (
<SceneView
navigation={navigation}
route={route}
screen={screen}
state={state}
setState={setState}
/>
);
},
options: screen.options || {},
};
return acc;