从react-navigation转react-router

作者: 羽纱 | 来源:发表于2018-03-22 21:06 被阅读723次

这不是一篇路由入门文章,需要熟悉react-navigation,并且对react-router有基本了解。该文章主要是做两者路由的对比,让习惯使用react-navigation的开发者更好的使用react-router。
还没了解过react-router的建议在官网:react-router里过一遍文档。

react-navigation和react-router的对比:

  • 支持的平台:
    react-navigationreact-native
    react-routerreact-nativereactjs
  • 性质:
    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-routerRoute相当于在StackNavigator定义的screen,可以看出Route并没有规则整齐的摆放在某一处,而是在需要使用的地方使用即可。同StackNavigatorRoute必须是Router的子标签才能使用,文章后面会详细介绍。


react-navigation与react-router的原理解析:

从上部分贴出的示例代码可以看出,react-routerreact-navigation的使用风格有很大的不同,其实它们的原理也不尽相同。
react-navigation原理:
通过createNavigationContainer来创建导航容器,导航容器管理着自己的state,可以通过navigation属性dispatch各种Action(例如:BACK,PUSH,RESET等),然后处理action得到新的state,从而管理着整个导航状态,再使用NavigationViews来解析state并渲染出来。
因此react-navigation提供的StackNavigatorTabbarNavigatorDrawerNavigator,它不仅管理着路由的状态树,还负责状态树按需渲染出来,提供手势及动画效果.因此它不仅依赖react,还极其依赖react-native提供的各种组件以及动画。
react-router原理:
react-router的思想是一切皆为组件,使用Route就和使用div一样的,Router管理着historyhistory就相当于react-navigation的state,所有的Route必须是Router的子标签,history里拥有着路由栈和当前路由位置(location)等信息,通过context,可以在Route的组件内获取到location,然后匹配location和当前Routepath,如果匹配成功就会拥有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中才能嵌套,所以有两种方式,第一种,直接在configscreen里写navigator

const AppNavigator = StackNavigator({
    mainTab: {
        screen: MainTabNaivigator,
        navigationOptions: ({navigation}) => ({
            header: MainTabBarHeader,
        }),
    },
    ...
})

第二种就是主动把导航容器的navigation传入子导航完成嵌套(如果不懂可以移步我的另一篇文章:react-navigation路由篇之StackRouter)。

  • 路由传参
    react-router有两种方式可以传参,一种是通过Routepath属性定义参数(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>相当于replacehistory的路由跳转的方法是不是与react-navigation中的this.props.navigation.navigate(routename, params)很像?

  • 选择路由渲染方式
    Route的渲染属性有三个,分别是componentrenderchildren,前面贴出的所有代码中,使用的都是<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>

注意:无论是componentrender、还是children,都会传入history, match, location这三个属性,componetrender在路由没有匹配上时渲染nullchildren在路由没有匹配上时,正常渲染,不过matchnull。因此children经常用于指示器上。

  • 注意点
    1、可以在任意处使用相同的路由路径
    react-navigation中同一层级的路由名字是不能重复的,但是在react-router中相同路径的路由可以放在任意地方,用于做侧边栏(sidebars),面包屑等等。
    2、区分this.props.history.locationthis.props.location
    this.props.history.locationthis.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.paththis.props.location.pathname
this.props.match.path代表的是当前组件路由匹配结果,match可能为空。this.props.location.pathname代表当前App路由位置。
4、区分this.props.match.paththis.porps.match.url
url是URL的匹配部分,pathRoute中定义的path

image.png

react-router参考文献

相关文章

网友评论

  • 风过留笑:react-router4所谓的动态路由,个人感觉并不是很好,虽然混入到组件里,可以让包含它的组件可以更好的进行控制,但是有个比较大的弊端,没有了静态配置文件,会使代码在维护上带来一定困难。
    尤其是大项目,当有新同学加入时,由于没有路由配置文件,还需要一个组件一个组件的翻阅才能了解基本路由。
    羽纱:@好好看看呗 按需在需要的位置加入 能够更加灵活的使用导航 灵活的自定义导航

本文标题:从react-navigation转react-router

本文链接:https://www.haomeiwen.com/subject/vvczqftx.html