「这是我参与2022首次更文挑战的第N天,活动详情查看:2022首次更文挑战」。
我好像吐槽过RN很多次,哈哈,不过国内确实很多项目组在用RN。众所周知,RN官方的库不够多,甚至不够好,比如说导航库,现在比较推荐的导航库不是RN官方的,而是第三方的React Navigation。
关于React Navigation,今天我们主说导航方式。React Navigation本身提供了丰富的导航方式,如stack、tab、drawer等。
但是如果这么多的导航还是不满足你的需求,你需要定制自己的导航,怎么办呢?如果你想从0造轮子,那也太复杂了,毕竟客户端不比web端,客户端不仅仅需要考虑兼容性,底层涉及太多太复杂。这个时候我们就不要从0早轮子了,还是用零件组装吧~
React Navigation就给我们完美提供了相关零件。接下来我们就来说下,如何利用React Navigation定制自己的Navigator。
环境准备
我用的是React Native cli,前面诸多环境搭建就不说了,直接说安装了:
npx react-native init lesson3
cd lesson3
yarn ios
yarn android
复制代码
除了默认的包,我们还需要再安装@react-navigation/bottom-tabs、@react-navigation/native、react-native-safe-area-context等,具体直接上package.json吧,
{
"name": "lesson3",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"@react-navigation/bottom-tabs": "^6.0.9",
"@react-navigation/native": "^6.0.6",
"@react-navigation/native-stack": "^6.2.4",
"react": "17.0.2",
"react-native": "0.66.0",
"react-native-elements": "^3.4.2",
"react-native-safe-area-context": "^3.3.2",
"react-native-screens": "^3.8.0",
"react-native-vector-icons": "^8.1.0",
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/runtime": "^7.12.5",
"@react-native-community/eslint-config": "^2.0.0",
"babel-jest": "^26.6.3",
"eslint": "7.14.0",
"jest": "^26.6.3",
"metro-react-native-babel-preset": "^0.66.2",
"react-test-renderer": "17.0.2"
},
"jest": {
"preset": "react-native"
}
}
复制代码
注意安装完上面的这些React Navigation等库之后,不要忘记进入ios文件下再装下ios相关的依赖:
cd ios && pod install
复制代码
接下来,我们就可以来定制自己的Navigator了,其实就是实现createMyNavigator
创建函数的过程,先看用法:
const {Navigator, Screen, Group} = createMyNavigator();
复制代码
接下来我们来实现一个Tab导航,我们一步步来实现:
代码实现与原理
createNavigatorFactory
这是一个HOC,亲们还记得什么是HOC吗?HOC,Higher Order Component,高阶组件,是指一个参数和返回值都是组件的函数。 此函数用于创建Navigator
和 Screen
,因此接下我们要实现的就是TabNavigator
组件了
const createMyNavigator = createNavigatorFactory(TabNavigator);
export default createMyNavigator;
复制代码
TabNavigator
这是一个组件,控制导航显示方式与页面显示,也就是说,这个组件显示两部分内容:导航与页面。 那么TabNavigator组件架子就是:
function TabNavigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}) {
return (
<NavigationContent>
<View>
<Text>导航</Text>
</View>
<View>
<Text>页面</Text>
</View>
</NavigationContent>
);
}
复制代码
另外,TabNavigator
接收props参数,包括children、screenOptions等,用作子节点、Screen参数等,这些都是来自组件Navigator的子节点:
const {Navigator, Screen, Group} = createMyTabNavigator();
export default function HomeRouterScreen() {
return (
<Navigator>
<Screen name="home" component={HomeScreen} options={{title: '首页'}} />
<Screen
name="user"
component={UserScreen}
options={{title: '用户中心'}}
/>
<Screen
name="setting"
component={SettingScreen}
options={{title: '设置'}}
/>
</Navigator>
);
}
复制代码
接下来我们现导航和页面组件之前,还要再了解一个自定义Hook:useNavigationBuilder。它接收子组件children和页面配置信息screenOptions作为参数。返回Navigator组件的子组件的信息,或者叫子路由的信息,如子路由信息数组等。useNavigationBuilder使用如下:
function TabNavigator({children, screenOptions}) {
const {state, descriptors, navigation, NavigationContent} =
useNavigationBuilder(TabRouter, {
children,
screenOptions,
});
return (
<NavigationContent>
// 省略一万字
</NavigationContent>
);
}
复制代码
好了,接下来我们就可以分别来实现导航组件和页面组件部分了。
导航组件
说是导航组件,其实有时候是没有组件的,只是导航方式,比如stack导航只是进栈出栈的数据信息改变,并没有显示出来的组件。但是我们现在写的Tab导航是有导航组件的。
好,那接下来我们就来实现这里的Tab导航组件,其实就是个数组遍历的过程,把所有的导航配置遍历显示,然后添加切换页面的点击事件:
// 导航
<View style={[{flexDirection: 'row', paddingTop: 50}, tabBarStyle]}>
{state.routes.map((route, index) => (
<Pressable
title={descriptors[route.key].options.title || route.name}
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={{
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
}}>
{/* 显示title或者route.name */}
<Text>{descriptors[route.key].options.title || route.name}</Text>
</Pressable>
))}
</View>
复制代码
页面
而页面显示,同导航组件,遍历所有的页面数据,匹配到当前的页面则显示,否则隐藏,显示页面则是通过descriptor.render()函数的执行。匹配页面可以通过state.index判断,即页面索引,每次导航切换,都会更新state.index,因此我们可以通过state.index===index判断是否显示页面。
// 页面
<View style={[{flex: 1}, contentStyle]}>
{state.routes.map((route, index) => {
const descriptor = descriptors[route.key];
const isFocused = state.index === index;
return (
<Screen
key={route.key}
focused={isFocused}
route={descriptor.route}
navigation={descriptor.navigation}
header={<Header title={descriptor.options.title || route.name} />}
headerShown={descriptor.options.headerShown}
style={[
StyleSheet.absoluteFill,
{display: index === state.index ? 'flex' : 'none'},
]}>
{descriptor.render()}
{/* <Text>{JSON.stringify(state)}</Text> */}
</Screen>
);
})}
</View>
复制代码
以上,自定义导航我们便实现完啦,代码非常详细了,大家可以自己尝试下~
如果想要完整可运行的代码,查看这里。