React-Router-v4 使用指南

v3 与 v4 版本对比

  • 静态与动态路由
    相对于v2,v3版本,v4一个最大的变化是路由从静态改为了动态,就是说用v4开发不需要专门再写一个类似route.js的文件来专门存放路由与组件的对应的关系,用v4直接写在Route组件的path上就行。此外v4分成了好几个库:
    react-router React Router 核心
    react-router-dom 用于 DOM 绑定的 React Router
    react-router-native 用于 React Native 的 React Router
    react-router-redux React Router 和 Redux 的集成
    react-router-config 静态路由配置的小助手
    做浏览器端应用只需引入react-router-dom即可,做RN的话需要引入react-router-native,同时如果使用了redux,还必须引入react-router-redux
  • 特点
    v3 的特点:
    1、路由配置集中在一个地方
    2、布局和页面嵌套是由<Route>组件的嵌套派生而来的
    v4 的特点:
    1、不提倡将路由配置集中,而是分散化
    2、Route直接参与布局和页面嵌套
    3、Route本身就是组件
    4、Route 代替 props.children 实现嵌套
  • V4的新特性
    1、包容路由(Inclusive Routing)
      <Route path="/"  component={HomePage} />
      <Route path="/users" component={UsersPage} />

当访问/users,HomePage 和 UserPage 都会被渲染
2、独占路由(Exclusive Routing)
可以使用switch组件来实现v3式的独占路由,为了避免包容路由的规则,仍需使用exact

const PrimaryLayout = () => (
  <div className="primary-layout">
    <PrimaryHeader />
    <main>
      <Switch>
        <Route path="/" exact component={HomePage} />                // 使用exact来精准匹配
        <Route path="/users/add" component={UserAddPage} />     // /users/add 放在 /users前面就可以避免对/users使用exact
        <Route path="/users" component={UsersPage} />                 // /users 放在 /users/add后面就可以避免对/users使用exact
        <Redirect to="/" />                                                                   // 都不匹配时,走 redirect
      </Switch>
    </main>
  </div>
)

0、重要概念

  • Route
    Route就是一个组件,当路径匹配时就渲染,否则渲染为null;
    Route的path参数可以设正则,例如 path='/(index|home|)' 可以匹配/index或/home或/; 再如 path='/list/:number'可匹配/list/1,注意number不能改其他名字
    Route对应的组件可以从props中获取 match , location , history 三个参数:
// 取参数写法:
cons { dispatch, match:{ params:{ number } } } = this.props
// match 参数
params - (object) Key/value pairs parsed from the URL corresponding to the dynamic segments of the path
isExact - true if the entire URL was matched (no trailing characters)
path - (string) The path pattern used to match. Useful for building nested <Route>s
url - (string) The matched portion of the URL. Useful for building nested <Link>s
// location 参数(Link的to参数可以是location对象)
const location = {
  pathname: '/somewhere'
  state: { fromDashboard: true }
}
<Link to={location}/>
{
  key: 'ac3df4', // not with HashHistory!
  pathname: '/somewhere'
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}

// history 参数
`length` - (number) The number of entries in the history stack
`action` - (string) The current action (`PUSH`, `REPLACE`, or `POP`)
`location` - (object) The current location. May have the following properties:(这个location和上面那个不一样)
         `pathname` - (string) The path of the URL
         `search` - (string) The URL query string
         `hash` - (string) The URL hash fragment
         `state` - (string) location-specific state that was provided to e.g. `push(path, state)` when this location was pushed onto the 
                  stack. Only available in browser and memory history.
`push(path, [state])` - (function) Pushes a new entry onto the history stack
`replace(path, [state])` - (function) Replaces the current entry on the history stack
`go(n)` - (function) Moves the pointer in the history stack by `n` entries
`goBack()` - (function) Equivalent to `go(-1)`
`goForward()` - (function) Equivalent to `go(1)`
`block(prompt)` - (function) Prevents navigation (see [the history docs](https://github.com/ReactTraining/history#blocking-transitions))
  • Link
    可设置to和replace属性
  • NavLink
    特殊的Link,当激活时会自动加上active类名
  • Prompt
    页面离开时的弹出提示框
  • Redirect
    可设置 from 和 to 属性 ,当 Link 用即可

1、基本使用

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'
const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
)
const About = () => (
  <div>
    <h2>About</h2>
  </div>
)
const Topic = ({ match }) => (         // 函数式组件,每个组件会被传入match、location 和 history,用的最多的是match
  <div>
    <h3>{match.params.topicId}</h3>
  </div>
)
const Topics = ({ match }) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/rendering`}>
          Rendering with React
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/components`}>
          Components
        </Link>
      </li>
      <li>
        <Link to={`${match.url}/props-v-state`}>
          Props v. State
        </Link>
      </li>
    </ul>
    <Route path={`${match.url}/:topicId`} component={Topic}/>
    <Route exact path={match.url} render={() => (
      <h3>Please select a topic.</h3>
    )}/>
  </div>
)
class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/about">About</Link></li>
            <li><Link to="/topics">Topics</Link></li>
          </ul>
          <hr/>
          <Route exact path="/" component={Home}/>             //exact表示精准匹配,/about不会渲染Home
          <Route path="/about" component={About}/>
          <Route path="/topics" component={Topics}/>
        </div>
      </Router>
    )
  }
}
export default App

2、与redux集成

// 先安依赖:npm install --save react-router-redux@next  npm install --save history
import React from 'react'
import ReactDOM from 'react-dom'
import { Route } from 'react-router'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'
import createHistory from 'history/createBrowserHistory'

import reducers from './reducers' // Or wherever you keep your reducers

// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory()

// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history)

// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer
  }),
  applyMiddleware(middleware)
)

// Now you can dispatch navigation actions from anywhere!
// store.dispatch(push('/foo'))

ReactDOM.render(
  <Provider store={store}>
    { /* ConnectedRouter will use the store from Provider automatically */ }
    <ConnectedRouter history={history}>
      <div>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
        <Route path="/topics" component={Topics}/>
      </div>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
)

3、V4 的使用技巧

  • 嵌套
    子路由中用this.props.match.path来获取父路由的匹配规则
<Switch>
        <Route path={props.match.path} exact component={BrowseUsersPage} />
        <Route path={`${props.match.path}/:userId`} component={UserProfilePage} />
</Switch>
  • 指定参数类型(/:userId(\d+))
<Route path={`/users/:userId(\\d+)`} component={UserProfilePage} />   // /users/edit 不会匹配,/users/123才会匹配
  • <Link> & <NavLink>
    功能一样,只是navlink组件可更加方便定制激活时的样式
  • match & history 常用属性
    match.params.XX
    match.path // 用于嵌套 Routes
    match.url // 用于嵌套 Links
    history.push
  • URL Query Strings
    v4里不能直接获取类似hash或者query string这样的参数,可以使用这个三方库 query-string
// 1、取查询字符串
const parsed = queryString.parse(location.search);  // location.search='?foo=bar'
console.log(parsed);    //  {foo: 'bar'}

// 2、取hash
const parsedHash = queryString.parse(location.hash);  // location.hash = '#token=bada55cafe'
console.log(parsedHash);  // {token: 'bada55cafe'}
// 3、制作location.search
parsed.foo = 'unicorn';
parsed.ilike = 'pizza';
const stringified = queryString.stringify(parsed);      //  'foo=unicorn&ilike=pizza'
location.search = stringified;
// note that `location.search` automatically prepends a question mark
console.log(location.search);       //  '?foo=unicorn&ilike=pizza'
  • 布局上
    当应用里有登录这种逻辑时,最好将登录和主应用分离,将其作为两个顶级路由
class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <BrowserRouter>
          <Switch>
            <Route path="/auth" component={UnauthorizedLayout} />
            <AuthorizedRoute path="/app" component={PrimaryLayout} />
          </Switch>
        </BrowserRouter>
      </Provider>
    )
  }
}

class AuthorizedRoute extends React.Component {
  componentWillMount() {
    getLoggedUser()
  }

  render() {
    const { component: Component, pending, logged, ...rest } = this.props
    return (
      <Route {...rest} render={props => {
        if (pending) return <div>Loading...</div>
        return logged
          ? <Component {...this.props} />
          : <Redirect to="/auth/login" />
      }} />
    )
  }
}

const stateToProps = ({ loggedUserState }) => ({
  pending: loggedUserState.pending,
  logged: loggedUserState.logged
})

export default connect(stateToProps)(AuthorizedRoute)
  • 组件命名
    顶级组件以 Layout结尾,页面级组件以 Page 结尾
  • Route的三种渲染方式
    component // 本质调用React.createElement 创建Component
    render // 适用于要封装高阶组件的情形,如封装一个<FadingRoute>
// wrapping/composing
const FadingRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    <FadeIn>
      <Component {...props}/>
    </FadeIn>
  )}/>
)
<FadingRoute path="/cool" component={Something}/>

children // 不管路径是否匹配,总是渲染。只是当路径不匹配时,match为null

<ul>
  <ListItemLink to="/somewhere"/>
  <ListItemLink to="/somewhere-else"/>
</ul>

const ListItemLink = ({ to, ...rest }) => (
  <Route path={to} children={({ match }) => (
    <li className={match ? 'active' : ''}>
      <Link to={to} {...rest}/>
    </li>
  )}/>
)

参考

1、v4中文文档
2、初探 React Router 4.0
3、React Router 4 简介及其背后的路由哲学
4、All About React Router 4

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容