react-navigation的screen参数使用高阶组件时
场景:
react-native react-navigation @react-native-community/netinfo
需求:
假设我的底部导航有三个页面,每个页面需要在进入前都检查当前是否联网,没有联网的情况下,显示另一个页面,只有在有网络的情况下再进入响应的页面。
image.png
实现一:
先写公共的网络跑丢的页面,然后在每个页面进行逻辑判断, 不同情况显示不同页面,这个当然可以实现,但是逻辑判断那部分每个页面都写一次,就重复了,这个就不写代码了,更好的方式是使用高阶组件修饰,请看实现二。
实现二:
先写一个WithNoNet的高阶组件, 然后在每个页面中使用WithNoNet修饰一下,实现将这部分判断逻辑抽离出来,简洁优雅高效。
//WithNoNet高阶组件
import React from 'react'
import NetInfo from "@react-native-community/netinfo";
import NoNetwork from '../common/NoNetwork'
export default WrappedComponent => {
return class extends React.Component {
constructor(props) {
super(props)
this.state = {
isConnected: false,
hasCheckedNetwork: false, //为了解决执行网络检查时,NoNetwork页面一闪而过的现象
}
}
async componentDidMount(): void {
await this.checkNetwork()
}
checkNetwork = async () => {
try {
let netInfo = await NetInfo.fetch()
if (netInfo.isConnected) {
this.setState({
isConnected: true,
hasCheckedNetwork: true,
})
} else {
this.setState({
hasCheckedNetwork: true,
})
}
} catch (e) {
console.log(e)
}
}
render() {
const {isConnected, hasCheckedNetwork} = this.state
if (isConnected) {
return <WrappedComponent />
} else {
if (hasCheckedNetwork) {
return <NoNetwork checkNetwork={this.checkNetwork}/>
} else {
return null
}
}
}
}
}
Home主页使用WithNoNet修饰
//Home主页使用WithNoNet修饰
import React from 'react'
import { View,Text} from 'react-native'
import WithNoNet from '../WithNoNet'
class Home extends React.Component {
componentDidMount(){
console.log(this.props.navigation)
}
render() {
return (
<View>
<Text> This is home </Text>
</View>
)
}
}
export default WithNoNet(Home)
现在开始进入本文主题
这样修饰了之后,一看没啥问题,但是我一运行就出问题了, 因为Home组件的props中的navigation成了undefined,检查我的底部导航组件
import React from 'react'
import {createBottomTabNavigator, createAppContainer} from 'react-navigation'
import Base from '../page/Base'
import My from '../page/My'
import Ionicons from 'react-native-vector-icons/Ionicons'
import Home from '../page/Home/Home'
import Alert from '../page/Alert'
import {ACTIVATE_COLOR} from "../config/constant";
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'
const bottomNavigator = createBottomTabNavigator({
Home: {
screen:Home,
navigationOptions: {
tabBarLabel: "首页",
tabBarIcon: ({tintColor, focused}) =>
<Ionicons
name={'md-home'}
size={20}
color={tintColor}
/>
}
},
Alert: {
screen:Alert,
navigationOptions: {
tabBarLabel: "告警",
tabBarIcon: ({tintColor, focused}) =>
<MaterialCommunityIcons
name={'alert-decagram'}
size={20}
color={tintColor}
/>
}
},
My: {
screen: My,
navigationOptions: {
tabBarLabel: "我的",
tabBarIcon: ({tintColor, focused}) =>
<FontAwesome5
name={'user-circle'}
size={20}
color={tintColor}
/>
}
},
},
{
tabBarOptions: {
activeTintColor: ACTIVATE_COLOR,
labelStyle: {
fontSize: 13,
},
}
}
)
发现很有可能是因为Home进行了WithNoNet组件包裹,导致navigation组件传递不过去了,怎么办呢?
先google一把,然并卵,没找到答案,只能自己啃了。。。
首先我觉得应该在screen参数上下功夫,于是去react-navigation官网看createStackNavigation的 screen相关的东西,然而除了已知的能传一个组件(就像现在的做法)作为参数,也没有发现更多。
我看代码中 tabBarIcon可以是一个函数返回一个组件, 猜猜screen行不行呢?
tabBarIcon: ({tintColor, focused}) =>
<FontAwesome5
name={'user-circle'}
size={20}
color={tintColor}
/>
于是试了一把,改为
Home: {
screen: ({navigation}) => <Home navigation={navigation}/>,
navigationOptions: {
tabBarLabel: "首页",
tabBarIcon: ({tintColor, focused}) =>
<Ionicons
name={'md-home'}
size={20}
color={tintColor}
/>
}
},
然后我在WithNoNet中componentDidMount中打印this.props.navigation, 发现真打印出来了,那么这个问题就好办了,直接将navigation传递给子组件就行了。将WithNoNet改为如下
//WithNoNet高阶组件
import React from 'react'
import NetInfo from "@react-native-community/netinfo";
import NoNetwork from '../common/NoNetwork'
export default WrappedComponent => {
return class extends React.Component {
constructor(props) {
super(props)
this.state = {
isConnected: false,
hasCheckedNetwork: false, //为了解决执行网络检查时,NoNetwork页面一闪而过的现象
}
}
async componentDidMount(): void {
await this.checkNetwork()
}
checkNetwork = async () => {
try {
let netInfo = await NetInfo.fetch()
if (netInfo.isConnected) {
this.setState({
isConnected: true,
hasCheckedNetwork: true,
})
} else {
this.setState({
hasCheckedNetwork: true,
})
}
} catch (e) {
console.log(e)
}
}
render() {
const {isConnected, hasCheckedNetwork} = this.state
const {navigation} = this.props //获取navigation并传递给WrappedComponent
if (isConnected) {
return <WrappedComponent navigation={navigation}/>
} else {
if (hasCheckedNetwork) {
return <NoNetwork checkNetwork={this.checkNetwork}/>
} else {
return null
}
}
}
}
}
然后在Alert,my页面都引入WithNoNet组件,导出是进行包裹, 再将底部导航的所有screen配置为如下:
const bottomNavigator = createBottomTabNavigator({
Home: {
screen: ({navigation}) => <Home navigation={navigation}/>,
navigationOptions: {
tabBarLabel: "首页",
tabBarIcon: ({tintColor, focused}) =>
<Ionicons
name={'md-home'}
size={20}
color={tintColor}
/>
}
},
Alert: {
screen: ({navigation}) => <Alert navigation={navigation}/>,
navigationOptions: {
tabBarLabel: "告警",
tabBarIcon: ({tintColor, focused}) =>
<MaterialCommunityIcons
name={'alert-decagram'}
size={20}
color={tintColor}
/>
}
},
My: {
screen: ({navigation}) => <My navigation={navigation}/>,
navigationOptions: {
tabBarLabel: "我的",
tabBarIcon: ({tintColor, focused}) =>
<FontAwesome5
name={'user-circle'}
size={20}
color={tintColor}
/>
}
},
},
{
tabBarOptions: {
activeTintColor: ACTIVATE_COLOR,
labelStyle: {
fontSize: 13,
},
}
}
)
同时附上NoNetwork页面的代码
import React from 'react'
import {TouchableOpacity, Text, StyleSheet, Dimensions, Image} from 'react-native'
const errorImg = require('../res/icons/color/noNetwork.png')
const {height} = Dimensions.get('window')
export default class NoNetwork extends React.PureComponent {
render() {
return <TouchableOpacity
style={styles.wrapper}
onPress={this.props.checkNetwork}
>
<Image
source={errorImg}
style={styles.img}
/>
<Text>网络跑丢啦!!</Text>
<Text>轻触重试</Text>
</TouchableOpacity>
}
}
const styles = StyleSheet.create({
wrapper: {
height: height - 80,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
img: {
width: 40,
height: 40,
},
})
大功告成了,哈哈哈,解决了这个问题,让我对react的高阶组件理解更深刻了,也更喜欢高阶组件了。
同时希望能帮助到有相同问题的同学!!!