Flow
JavaScript は動的型付け言語ですが、Flow と合わせて使うと型情報を明示的に書いてトランスパイル時に静的な型チェックができます。殆どの JavaScript のライブラリは Flow 用の型情報を持っていませんが、メジャーなライブラリは flow-typed というツールで型情報を取得できます。詳しくは次の記事を参考にしてください。
React Navigation と Flow
React Native で画面遷移を行うライブラリとして最近メジャーな React Navigation も Flow 用の型情報を持っていませんが、flow-typed で取得することができます。そこまではいいんですが、取得した型情報の使い方がわかりません。型情報ファイルにたくさんの型が定義されているのですが、どこでどの型を使ったらいいのかわからないのです。
とりあえずカバレッジ 100% になった
わからないなりにも試行錯誤しているうちに、どこでどれを使ったらいいかなんとなく見えてきました。そうは言っても、論理的に説明出来るほどではないのでこうすればうまく行ったという例を貼っておきます。React Navigation のチュートリアルのコードを Flow 対応させてみたものです。かなり記述量が増え、これがベストかどうかはわからないですが、同じところで困った人の参考になれば。
元のコード: https://snack.expo.io/@react-navigation/full-screen-modal
// @flow
import React from "react";
import { Button, Image, View, Text } from "react-native";
import {
StackNavigator,
type NavigationNavigatorProps,
type NavigationLeafRoute,
type StackNavigatorConfig
} from "react-navigation"; // Version can be specified in package.json
class LogoTitle extends React.Component<{}> {
render() {
return (
<Image
source={require("./spiro.png")}
style={{ width: 30, height: 30 }}
/>
);
}
}
type HomeScreenNavigationParams = {
increaseCount: () => void
};
type HomeScreenProps = NavigationNavigatorProps<
{},
{ params: HomeScreenNavigationParams } & NavigationLeafRoute
>;
type HomeScreenState = {
count: number
};
class HomeScreen extends React.Component<HomeScreenProps, HomeScreenState> {
static navigationOptions = (props: HomeScreenProps) => {
const navigation = props.navigation;
const params = navigation.state.params || {};
return {
headerTitle: <LogoTitle />,
headerLeft: (
<Button
onPress={() => props.navigation.navigate("MyModal")}
title="Info"
color="#fff"
/>
),
headerRight: (
<Button onPress={params.increaseCount} title="+1" color="#fff" />
)
};
};
componentWillMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Home Screen</Text>
<Text>Count: {this.state.count}</Text>
<Button
title="Go to Details"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate("Details", {
itemId: 86,
otherParam: "First Details"
});
}}
/>
</View>
);
}
}
type DetailScreenNavigationParams = {
itemId: number,
otherParam: string
};
type DetailScreenProps = NavigationNavigatorProps<
typeof mainStackNavigatorConfig.navigationOptions,
{ params: DetailScreenNavigationParams } & NavigationLeafRoute
>;
class DetailsScreen extends React.Component<DetailScreenProps> {
static navigationOptions = (props: DetailScreenProps) => {
const navigationOptions = props.navigationOptions;
const { params } = props.navigation.state;
return {
title: params ? params.otherParam : "A Nested Details Screen",
/* These values are used instead of the shared configuration! */
headerStyle: {
backgroundColor: navigationOptions
? navigationOptions.headerTintColor
: null
},
headerTintColor: navigationOptions
? navigationOptions.headerStyle.backgroundColor
: null
};
};
render() {
/* 2. Read the params from the navigation state */
const { params } = this.props.navigation.state;
const itemId = params ? params.itemId : null;
const otherParam = params ? params.otherParam : null;
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Update the title"
onPress={() =>
this.props.navigation.setParams({ otherParam: "Updated!" })
}
/>
<Button
title="Go to Details... again"
onPress={() => this.props.navigation.navigate("Details")}
/>
<Button
title="Go back"
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
class ModalScreen extends React.Component<
NavigationNavigatorProps<{}, NavigationLeafRoute>
> {
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text style={{ fontSize: 30 }}>This is a modal!</Text>
<Button
onPress={() => this.props.navigation.goBack()}
title="Dismiss"
/>
</View>
);
}
}
const mainStackNavigatorConfig = {
initialRouteName: "Home",
navigationOptions: {
headerStyle: {
backgroundColor: "#f4511e"
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold"
}
}
};
const MainStack = StackNavigator(
{
Home: {
screen: HomeScreen
},
Details: {
screen: DetailsScreen
}
},
mainStackNavigatorConfig
);
const RootStack = StackNavigator(
{
Main: {
screen: MainStack
},
MyModal: {
screen: ModalScreen
}
},
{
mode: "modal",
headerMode: "none"
}
);
export default class App extends React.Component<{}> {
render() {
return <RootStack />;
}
}