这不是一篇路由入门文章,需要熟悉react-navigation,并且对react-router有基本了解。该文章主要是做两者路由的对比,让习惯使用react-navigation的开发者更好的使用react-router。
还没了解过react-router
的建议在官网:react-router里过一遍文档。
react-navigation和react-router的对比:
-
支持的平台:
react-navigation:react-native
react-router:react-native
、reactjs
-
性质:
react-navigation:静态路由(需要在程序一处进行完整的路由配置才能使用)
react-router:动态路由(route
在需要使用的地方配置,可以把Route
当做React
中的组件) -
使用示例:
react-navigation:
const AppNavigator = StackNavigator({
mainTab: {
screen: MainTabNaivigator,
navigationOptions: ({navigation}) => ({
header: MainTabBarHeader,
}),
},
.....
screenOne: {
screen: ScreenOne,
navigationOptions: ({navigation}) => ({
header: null
})
},
screenTwo: {
screen: ScreenTwo,
navigationOptions: ({navigation}) => ({
title: 'test'
})
},
}, {
headerMode: 'screen',
navigationOptions: NavigationHeader,
})
react-router:
<Root>
<Top>
<TopRow>
<Route
path={`${this.props.match.path}/Login`}
children={({match}) => (
<TopItem selected={match ? true : false} onClick={() => this.props.history.push(`${this.props.match.path}/Login`)}>登录</TopItem>
)}
/>
<TopText>|</TopText>
<Route
path={`${this.props.match.path}/Register`}
children={({match}) => (
<TopItem selected={match ? true : false} onClick={() => this.props.history.push(`${this.props.match.path}/Register`)}>注册</TopItem>
)}
/>
</TopRow>
</Top>
<AnimatedParent>
<Switch location={this.props.location}>
<Route path={`${this.props.match.path}/Login`} component={Login}/>
<Route path={`${this.props.match.path}/Register`} component={Register}/>
</Switch>
</AnimatedParent>
</Root>
react-router
的Route
相当于在StackNavigator
定义的screen
,可以看出Route
并没有规则整齐的摆放在某一处,而是在需要使用的地方使用即可。同StackNavigator
,Route
必须是Router
的子标签才能使用,文章后面会详细介绍。
react-navigation与react-router的原理解析:
从上部分贴出的示例代码可以看出,react-router
和react-navigation
的使用风格有很大的不同,其实它们的原理也不尽相同。
react-navigation原理:
通过createNavigationContainer
来创建导航容器,导航容器管理着自己的state
,可以通过navigation
属性dispatch
各种Action
(例如:BACK
,PUSH
,RESET
等),然后处理action
得到新的state
,从而管理着整个导航状态,再使用NavigationViews
来解析state
并渲染出来。
因此react-navigation
提供的StackNavigator
,TabbarNavigator
,DrawerNavigator
,它不仅管理着路由的状态树,还负责状态树按需渲染出来,提供手势及动画效果.因此它不仅依赖react,还极其依赖react-native提供的各种组件以及动画。
react-router原理:
react-router的思想是一切皆为组件,使用Route
就和使用div
一样的,Router
管理着history
,history
就相当于react-navigation
的state,所有的Route
必须是Router
的子标签,history
里拥有着路由栈和当前路由位置(location
)等信息,通过context
,可以在Route
的组件内获取到location
,然后匹配location
和当前Route
的path
,如果匹配成功就会拥有match
属性,并且Route
会被渲染出来,没有匹配成功则渲染null
。
react-router
的原理使我们使用它和使用React
组件一样简单,并且它只依赖react
,能在react-native
中使用。
react-router实践
-
在webApp中的配置
在最外面使用BrowserRouter
,它已经封装好了history
,context
,subscript
...
<BrowserRouter>
<App/>
</BrowserRouter>
这与react-navigation
放在外层一样:
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})}
/>
-
路由嵌套
react-router
的路由嵌套就和div
嵌套一样简单,取自官方的示例代码:
const App = () => (
<BrowserRouter>
{/* here's a div */}
<div>
{/* here's a Route */}
<Route path="/tacos" component={Tacos}/>
</div>
</BrowserRouter>
)
// when the url matches `/tacos` this component renders
const Tacos = ({ match }) => (
// here's a nested div
<div>
{/* here's a nested Route,
match.url helps us make a relative path */}
<Route
path={match.url + '/carnitas'}
component={Carnitas}
/>
</div>
)
在react-navigation
中,navigation
必须传递到子navigator
中才能嵌套,所以有两种方式,第一种,直接在config
的screen
里写navigator
:
const AppNavigator = StackNavigator({
mainTab: {
screen: MainTabNaivigator,
navigationOptions: ({navigation}) => ({
header: MainTabBarHeader,
}),
},
...
})
第二种就是主动把导航容器的navigation
传入子导航完成嵌套(如果不懂可以移步我的另一篇文章:react-navigation路由篇之StackRouter)。
-
路由传参
react-router
有两种方式可以传参,一种是通过Route
的path
属性定义参数(path
的取值规范:path-to-regexp
),这种形式的参数定义和传递会在url
上展示出来,例如?name=yusha&password=123
,可以通过this.props.match.params
获取参数:
<Route path='/Login/:name/:password' component={Login}/>
...
<Link to='/Login/yusha/123'/>
...
class Login extends React.Component {
...
this.props.match.params.name
...
}
另外一种传参方式是通过location
来传,这种参数不会在url
中展示出来:
<Route path='/Login' component={Login}/>
...
<Link
to={
path: '/Login'
state: {
name: 'yusha',
password: '123',
}
}
/>
...
class Login extends React.Component {
...
this.props.location.state.name
...
}
react-router
通过location
来传参就类似于react-navigation
通过this.props.navigation.navigate(routename, params)
,然后在this.props.navigation.state.params
中获取一样。
-
路由跳转
react-router
提供了多个路由跳转组件,<Link/>
、<NativeLink/>
、<Redirect/>
,在浏览器中它们原理就是<a/>
标签,因为在浏览器中history
的路由栈就是使用浏览器的路由栈。当然,我们也可以使用history
的一些跳转属性来跳转路由(我更喜欢这种):
//push 、replace、goBack等
this.props.history.push('/Login', {name: 'yusha', password:'123'})
<Link>
相当于push
,<Redirect>
相当于replace
。history
的路由跳转的方法是不是与react-navigation
中的this.props.navigation.navigate(routename, params)
很像?
-
选择路由渲染方式
Route
的渲染属性有三个,分别是component
、render
、children
,前面贴出的所有代码中,使用的都是<Route path='routePath' component={CustomComponent}/>
,这也是最易懂得,在路由匹配时渲染CustomComponent
,并且传入三个属性match,location,history
。
render
的使用场景路由渲染的组件需要当前域的参数,例如:
render() {
const test = '123'
return (
...
<Route
path='routePath'
render=({match, location, history}) => <CustomComponent match location history test/>)
/>
...
)
}
之前反复提及过,Route
只有在匹配上path
时才会渲染组件,否则渲染null
,那要是我想路由没有匹配时也渲染呢?这时可以使用children
:
<TopRow>
<Route
path={`${this.props.match.path}/Login`}
children={({match}) => (
<TopItem selected={match ? true : false} onClick={() => this.props.history.push(`${this.props.match.path}/Login`)}>登录</TopItem>
)}
/>
<TopText>|</TopText>
<Route
path={`${this.props.match.path}/Register`}
children={({match}) => (
<TopItem selected={match ? true : false} onClick={() => this.props.history.push(`${this.props.match.path}/Register`)}>注册</TopItem>
)}
/>
</TopRow>
注意:无论是
component
、render
、还是children
,都会传入history, match, location
这三个属性,componet
和render
在路由没有匹配上时渲染null
,children
在路由没有匹配上时,正常渲染,不过match
为null
。因此children
经常用于指示器上。
-
注意点
1、可以在任意处使用相同的路由路径
在react-navigation
中同一层级的路由名字是不能重复的,但是在react-router
中相同路径的路由可以放在任意地方,用于做侧边栏(sidebars),面包屑等等。
2、区分this.props.history.location
、this.props.location
this.props.history.location
和this.props.location
都是当前App
路由位置,但是this.props.history.location
是突变的,可以理解成它全局就一个引用,使用以下代码来体会一下:
<Route path='/Login' component={Login}/>
...
class Login extends React.Component {
componentWillReceiveProps(newProps) {
//路由发生改变时
console.log(this.props.location.pathname === newProps.location.pathname) //为false
console.log(this.props.history.location.pathname === newProps.history.location.pathname) //为true
}
}
3、区分this.props.match.path
、this.props.location.pathname
this.props.match.path
代表的是当前组件路由匹配结果,match
可能为空。this.props.location.pathname
代表当前App路由位置。
4、区分this.props.match.path
、this.porps.match.url
url
是URL的匹配部分,path
是Route
中定义的path
:
-
常用工具
route-tester: 可以用来测试路由匹配
网友评论
尤其是大项目,当有新同学加入时,由于没有路由配置文件,还需要一个组件一个组件的翻阅才能了解基本路由。