React Router--React Router4

React的路由其实就是一个React组件,路由要通过Route组件定义。例子:

import * as React from "react";
import {IndexRedirect ,Route} from 'react-router';

import Home from '../components/home';
import ImgUpload from '../components/uploadImg';
import Login from '../components/Login';
import App from '../contains/App';

export default (
    <Route path="/" component={App}>
        <IndexRedirect to='home' />
        <Route path="home" component={Home} />
        <Route path="login" component={Login} />
        <Route path="imgLoad" component={ImgUpload} />
    </Route>
)
import * as React from 'react';
import * as ReactDom   from 'react-dom';

import { Router ,browserHistory} from 'react-router';
import routes from '../router';

ReactDom.render(
    <Router history={browserHistory} routes={routes} />,
    document.getElementById('app')
)

上面代码中,用户访问根路由/home时,组件会先加载App组件,然后在加载Home组件。而App组件要写成以下:

import * as React from 'react'
import * as ReactDom  from 'react-dom'

interface IAppProps {}
interface IAppState {}

export default  class App extends React.Component<IAppProps,IAppState> {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <div>
                {this.props.children}
            </div>
        )
    }
}

上面代码中,App组件的this.props.children属性就是子组件。
这样看react的路由就是一个组件,最外层的route对应的是组建App,当访问/时,加载App组件,而加载里面的嵌套的路由时,会在App内部的this.props.children加载相应的组件。
IndexRoute
IndexRoute是指定当访问跟路由的时候this.props.children默认加载的组件。
IndexRedirect
IndexRedirect组件用于访问根路由的时候,将用户重定向到某个子组件。
histroy 属性
Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。
history属性,一共可以设置三种值
1.hashHistory:路由将通过URL的hash部分(#)切换。
2.browserHistory:浏览器的路由就不再通过Hash完成了,而显示正常的路径。
3.createMemoryHistory:主要用于服务器渲染。
路由的钩子
每个路由都有Enter和Leave钩子,用户进入或离开该路由时触发
例如

<Route
    path="messages/:id"
    onEnter={
      ({params}, replace) => replace(`/messages/${params.id}`)
    } 
  />
React Router4

以上我们讲解了Router4以前的版本,在v3中的一些思想到了v4,已经不正确了,
1.所有的路由全部订应在一起。
2.布局是通过<Route>组件的嵌套而来的。
3.布局和页面组件是完全纯粹的,它们是路由的一部分。
如果以上代码用v4实现:

import * as React from 'react'
import { BrowserRouter, Route, Link} from 'react-router-dom'

import Home from '../components/home';
import ImgUpload from '../components/uploadImg';
import Login from '../components/Login';

export const App = () => (
    <BrowserRouter>
        <div>
             <Route path="/" exact component={Home} />
             <Route path="home" component={Home} />
             <Route path="login" component={Login} />
             <Route path="imgLoad" component={ImgUpload} />
        </div>
    </BrowserRouter>
)
import * as React from 'react';
import * as ReactDom   from 'react-dom';

import {App} from '../contains/App';

ReactDom.render(
    <App />,
    document.getElementById('app')
)

路由直接被定义到App组建里,即之前的this.props.children
产生了新的api:
BrowserRouter:代表浏览器router,而且我们现在从 react-router-dom 中导入它。
HashRouter:路由将通过URL的hash部分(#)切换。
MemoryRouter: 主要用于服务器渲染。
包容性路由
V3 的路由规则是“排他性”的,这意味着只有一条路由将获胜,在this.props.children里面最终只加载一个组件。
但是,V4的路由规则是包容的,假如没有exact ,那么则会加载所有组件。例如上例中,当访问 / 时,默认加载App组件,但是App组件下还有Home、Login、ImgUpload,这些将都被渲染。

Paste_Image.png

排他性路由
如果你只需要在路由列表里匹配一个路由,则使用 <Switch> 来启用排他路由:

export const App = () => (
    <BrowserRouter>
        <div>
            <Switch>
                 <Route path="/" exact component={Home} />
                 <Route path="home" component={Home} />
                 <Route path="login" component={Login} />
                 <Route path="imgLoad" component={ImgUpload} />
            </Switch>
        </div>
    </BrowserRouter>
)

在此处,我们依然需要加上exact,因为无论你访问 /ImgUpload
/login 主页的路径也将被匹配,所以会先显示 Home,但是Switch只允许一条路由生效,所以无论你点login、还是ImgUpload,它永远显示的事Home。所以在配置顺序上要做一些调整,例如将 / 放到最后。
如果要是,加上 exact 则不需要考虑顺序问题了。
“默认路由”和“未找到”
V4中代替IndexRoute的 是<Route exact>;
如果没有路由解析,则可以使用 <Switch> 与 <Redirect>重定向到具有有效路径的默认页面。
嵌套布局
在V3使用嵌套布局:

export default (
    <Route path="/" component={App}>
        <IndexRedirect to='home' component={Home}/>
        <Route path="home" component={Home} />
        <Route path="login" component={Login} />
        <Route path="imgLoad" component={ImgUpload} />
        <Route path="Person" component={Person} >
            <IndexRedirect to='center'/>
            <Route path="center" component={Center} />
            <Route path="like" component={Like} />
        </Route>
    </Route>
)

app.js

export default  class App extends React.Component<IAppProps,IAppState> {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <div>
                {this.props.children}
            </div>
        )
    }
}

person.js

export default class Person extends React.Component<any,any>{
    constructor(props){
        super(props)
    }
    render(){
        return (
            <div>
                <div className="nav-wrap">
                    <ul className="nav">
                        <li><Link to="/home">首页</Link></li>
                        <li><Link to="/imgLoad">上传</Link></li>
                        <li><Link to="/login">登陆</Link></li>
                        <li><Link to="/Person">个人</Link></li>
                    </ul>
                </div>
                <div>
                    <ul className="nav">
                        <li><Link to="/Person/center">center</Link></li>
                        <li><Link to="/Person/like">like</Link></li>
                    </ul>
                    {this.props.children}
                </div>
            </div>
        )
    }
}

如果使用V4,我们只需要在嵌套的组件里面设计:
App.js

export const App = () => (
    <BrowserRouter>
        <div>
            <Switch>
              <Route path="/" exact component={Home} />
              <Route path="/login" component={Login} />
              <Route path="/imgLoad" component={ImgUpload} />
              <Route path="/imgLoad" component={ImgUpload} />
              <Route path="/person" component={Person} />
            </Switch>
        </div>
    </BrowserRouter>
)

person.tsx

interface states  {
    id : Number,
    [propName:string] : any
}
export default class Person extends React.Component<any,states>{
    constructor(props){
        super(props)
        this.state = {
            id : 12
        }
    }
    render(){
        return (
            <div>
                <div className="nav-wrap">
                    <ul className="nav">
                        <li><Link to="/">首页</Link></li>
                        <li><Link to="/imgLoad">上传</Link></li>
                        <li><Link to="/login">登陆</Link></li>
                        <li><Link to="/person">person</Link></li>
                    </ul>
                </div>
                <div>
                    <ul className="nav">
                        <li><Link to="/person">center</Link></li>
                        <li><Link to="/person/like">like</Link></li>
                        <li><Link to={`/person/${this.state.id}`}>user</Link></li>
                    </ul>
                    <div>
                        this is person
                    </div>
                    <div>
                        <Switch>
                           <Route path={this.props.match.path} exact component={Center} />
                           <Route path={`${this.props.match.path}/like`} component={Like} />
                           <Route path={`${this.props.match.path}/:userId`} component={User} />
                        </Switch>
                    </div>
                </div>
            </div>
        )
    }
}

main.tsx

ReactDom.render(
    <App />,
    document.getElementById('app')
)

在组建的props中,存在一个match对象,我们打印一下:

Paste_Image.png

我们可以拿到 当前组建的路由,因此在person.js可以这样

<Switch>
    <Route path={this.props.match.path} exact component={Center} />
    <Route path={`${this.props.match.path}/like`} component={Like} />
    <Route path={`${this.props.match.path}/:userId`} component={User} />
</Switch>

match 对象给我们提供了几个属性,包括 match.params、match.path、match.url
match.path vs match.url
在上述代码中,我们打印一下User里面的match可以发现
match.path:

match.path

match.url:

Paste_Image.png

一个是我们设计的路由,一个是当前访问的裸游;
match.path常用来构建新的路由。
match.url常用来构建嵌套的link

<li><Link to={`${this.props.match.url}/add`}>add</Link></li>

渲染出来:
http://localhost:3000/person/12/add
授权路由
在应用程序中,通常会根据用户的登录状态来限制用户访问某些路由。对于未经授权的页面(如“登录”和“忘记密码”)与已授权的页面(应用程序的主要部分)看起来不一样也是常见的。为了解决这些需求,需要考虑一个应用程序的主要入口点:

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>
    )
  }
}

使用 react-redux 与 React Router v4 非常类似,就像之前一样,只需将 BrowserRouter
包在 <Provider>
中即可。
通过这种方法可以得到一些启发。第一个是根据我们所在的应用程序的哪个部分,在两个顶层布局之间进行选择。像访问 /auth/login 或 /auth/forgot-password 这样的路径会使用 UnauthorizedLayout —— 一个看起来适于这种情况的布局。当用户登录时,我们将确保所有路径都有一个 /app 前缀,它使用 AuthorizedRoute 来确定用户是否登录。如果用户在没有登录的情况下,尝试访问以 /app 开头的页面,那么将被重定向到登录页面。
v4 中一个惊人的新功能是能够为特定的目的创建你自己的路由。它不是将 component 的属性传递给 <Route>,而是传递一个 render 回调函数:

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" />
      }} />
    )
  }
}

<Link> vs <NavLink>
<NavLink>与 <Link>一样,但如果 <NavLink>匹配浏览器的 URL,那么它可以提供一些额外的样式能力。例如,在示例应用程序中,有一个<PrimaryHeader>
组件看起来像这样:

const PrimaryHeader = () => (
  <header className="primary-header">
    <h1>Welcome to our app!</h1>
    <nav>
      <NavLink to="/app" exact activeClassName="active">Home</NavLink>
      <NavLink to="/app/users" activeClassName="active">Users</NavLink>
      <NavLink to="/app/products" activeClassName="active">Products</NavLink>
    </nav>
  </header>
)

使用 <NavLink> 可以让我给任何一个激活的链接设置一个 active 样式。而且,需要注意的是,我也可以给它们添加 exact 属性。如果没有 exact,由于 v4 的包容性匹配策略,那么在访问 /app/users 时,主页的链接将处于激活中。
NavLink 带 exact 属性等价于 v3 的 <link>,而且更稳定。

参考文章:
[译] 关于 React Router 4 的一切
All About React Router 4

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

推荐阅读更多精彩内容