React Native で Top Tabs Navigator と呼ばれる SmartNews のタブのようなビューを作ってみます。
React Navigation を使います。
ライブラリのインストール
shell
npm install @react-navigation/native @react-navigation/material-top-tabs react-native-tab-view
shell
npx expo install react-native-screens react-native-safe-area-context react-native-pager-view
最も基本的な画面を作る
TopTabNavigator.tsx
import MemoListScreen from '../screens/MemoListScreen';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
export default function TopTabNavigator() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={MemoListScreen} />
<Tab.Screen name="Settings" component={MemoListScreen} />
</Tab.Navigator>
);
}
App.tsx
は以下のように書きます。
App.tsx
import TopTabNavigator from './lib/components/uiParts/TopTabNavigator';
import { initialize } from './lib/databases/typeorm.config';
import { DataSourceReady } from './lib/states/atoms/DataSourceReady';
import { NavigationContainer } from '@react-navigation/native';
import { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { RecoilRoot, useSetRecoilState } from 'recoil';
export default function App() {
return (
<RecoilRoot>
<NavigationContainer>
<View style={styles.spacer}></View>
<TopTabNavigator />
<DatabaseInitializer />
</NavigationContainer>
</RecoilRoot>
);
}
function DatabaseInitializer() {
const setDataSourceReady = useSetRecoilState(DataSourceReady);
useEffect(() => {
async function initializeDatabase() {
try {
await initialize();
setDataSourceReady(true);
console.log('[INFO]DataSource initialized');
} catch (err) {
console.log(err);
console.log('[ERROR]DataSource failed to initialize');
setDataSourceReady(false);
}
}
initializeDatabase();
}, []);
return null;
}
const styles = StyleSheet.create({
spacer: {
marginTop: 30,
},
});
シミュレーターを立ち上げると、以下のような画面が表示されます。
タブで表示しているコンポーネントに型付きでパラメータを渡す
MemoListScreen.tsx
import { type RootTabParamList } from '../uiParts/TopTabNavigator';
import { type RouteProp } from '@react-navigation/native';
import { SafeAreaView, Text } from 'react-native';
type MemoListScreenRouteProp = RouteProp<RootTabParamList, 'Home' | 'Settings'>;
interface Props {
route: MemoListScreenRouteProp;
}
export default function MemoListScreen({ route }: Props) {
console.log(route);
const { tabId } = route.params;
return (
<SafeAreaView>
<Text>MemoList - {tabId}</Text>
</SafeAreaView>
);
}
TopTabNavigator.tsx
は以下のようになります。
TopTabNavigator.tsx
import MemoListScreen from '../screens/MemoListScreen';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
export interface RootTabParamList {
Home: { tabId: string };
Settings: { tabId: string };
[key: string]: { tabId: string } | undefined;
}
const Tab = createMaterialTopTabNavigator<RootTabParamList>();
export default function TopTabNavigator() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={MemoListScreen} initialParams={{ tabId: 'home' }} />
<Tab.Screen
name="Settings"
component={MemoListScreen}
initialParams={{ tabId: 'settings' }}
/>
</Tab.Navigator>
);
}
React Natigation でパラメータに型を設定する方法は以下のドキュメントが参考になります。
動的にタブの数を増やす方法
<Tab.Screen name="Home"
の name
が一意でなければいけないため、 RootTabParamList
でかっちりと型を定めるのは泣きながら諦めました。
TopTabNavigator.tsx
import MemoListScreen from '../screens/MemoListScreen';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
interface TabScreenProp {
name: string;
tabId: string;
}
const tabScreenProps: TabScreenProp[] = [
{ name: 'TodoList', tabId: '123' },
{ name: 'Memo', tabId: '456' },
{ name: 'Tasks', tabId: '789' },
{ name: '買い物', tabId: '101112' },
{ name: '下書き', tabId: '131415' },
];
export default function TopTabNavigator() {
return (
<Tab.Navigator>
{tabScreenProps.map((tabScreenProp) => (
<Tab.Screen
key={tabScreenProp.name}
name={tabScreenProp.name}
component={MemoListScreen}
initialParams={{ tabId: tabScreenProp.tabId }}
/>
))}
</Tab.Navigator>
);
}
MemoListScreen.tsx
は以下のようにします。 any
を使ってしまっています。
MemoListScreen.tsx
import { SafeAreaView, Text } from 'react-native';
export default function MemoListScreen({ route }: any) {
const { tabId } = route.params;
return (
<SafeAreaView>
<Text>MemoList - {tabId}</Text>
</SafeAreaView>
);
}
上記の例の場合、以下のような画面が表示されます。
タブバーを横幅いっぱいでスクロールできるようにする
<Tab.Navigator screenOptions={{ tabBarScrollEnabled: true,
を設定すれば、タブナビゲーションでスクロールできるようになります。ついでに長いタブ名は text-overflow: ellipsis
のように、折りたたみができるようにしました。
TopTabNavigator.tsx
import MemoListScreen from '../screens/MemoListScreen';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { Text } from 'react-native';
const Tab = createMaterialTopTabNavigator();
interface TabScreenProp {
name: string;
tabId: string;
}
const tabScreenProps: TabScreenProp[] = [
{ name: 'TodoList', tabId: '123' },
{ name: 'Memo', tabId: '456' },
{ name: 'Tasks', tabId: '789' },
{ name: '買い物', tabId: '101112' },
{ name: '下書き', tabId: '131415' },
{
name: '長い長いタブの名前はどうなるのかな?',
tabId: '131415',
},
];
export default function TopTabNavigator() {
return (
<Tab.Navigator screenOptions={{ tabBarScrollEnabled: true, tabBarItemStyle: { width: 160 } }}>
{tabScreenProps.map((tabScreenProp) => (
<Tab.Screen
key={tabScreenProp.name}
name={tabScreenProp.name}
component={MemoListScreen}
initialParams={{ tabId: tabScreenProp.tabId }}
options={{
tabBarLabel: ({ focused, color }) => (
<Text numberOfLines={1} ellipsizeMode="tail" style={{ color }}>
{tabScreenProp.name}
</Text>
),
}}
/>
))}
</Tab.Navigator>
);
}
できあがったコンポーネントは以下のようになります。