React Router

React Router

下面未说明的都指的是React Router V4,用到的包是react-router-dom

React Router的特性

路由的基本原理就是保证view和url同步,React-Router有下面一些特点

  • 声明式的路由 跟react一样,我们可以声明式的书写router,可以用JSX语法

  • 嵌套路由及路径匹配

  • 支持多种路由切换方式

    可以用hashchange或者history.putState

    hashChange的兼容性较好,但在浏览器地址栏显示#看上去会很丑;而且hash记录导航历史不支持location.keylocation.statehashHistoryhashChange的实现。

    history.putState可以给我们提供优雅的url,但需要额外的服务端配置解决路径刷新问题;browserHistoryhistory.pushState的实现。

    因为两种方式都有优缺点,我们可以根据自己的业务需求进行挑选,这也是为什么我们的路由配置中需要从react.router引入browserHistory并将其当作props传给Router。

React Router的包

react—router实现了路由的核心功能,在V4之前(V2,V3)可以使用它,而react-router-dom基于react-router,加入了再浏览器环境下的一些功能,例如Link组件,BroswerRouter和HashRouter组件这类的DOM类组件,所以如果用到DOM绑定就使用react-router-dom,实际上react-routerreact-router-dom的子集,所以在新版本中我们使用react-router-dom就行了,不需要使用react-router.react-router-native在React Native中用到。

react-router-redux没有集成进来

React Router的API

React-Router的API主要有

BrowserRouter HashRouter MemoryRouter StaticRouter
Link NavLink Redirect Prompt
Route Router Swith

这些组件的具体用法可以在react-router官网segmentfault一篇文章查看,这里对它们做个总结:

BrowserRouter使用HTML5提供的History api(putState,replaceState和popState事件)来保持UI和URL的同步.

HashRouter使用URL的hash部分(即window.location.hash)来保持UI和URL的同步;HashRouter主要用于支持低版本的浏览器,因此对于一些新式浏览器,我们鼓励使用BrowserHistory

MemoryRouter将历史记录保存在内存中,这个在测试和非浏览器环境中很有用,例如react native

StaticRouter是一个永远不会改变位置的Router,这在服务端渲染场景中非常有用,因为用户实际上没有点击,所以位置时间上没有发生变化。

NavLinkLink的区别主要在于,前者会在与URL匹配时为呈现要素添加样式属性。

Route是这些组件中重要的组件,它的任务就是在其path属性与某个location匹配时呈现一些UI。

对于Router,一般程序只会使用其中一个高阶Router,包括BrowserRouter,HashRouter,MemoryRouter,NativeRouter和StaticRouter

Switch用于渲染与路径匹配的第一个RouteRedirect

React Router基本用法—基本路由

基本操作

两个页面homedetail

//home.js
import React from 'react

export default class Home extends React.Component {
    render(){
        return (
        <div>
           <a>调转到detail页面</a>
         </div>
        )
    }
}
//detail.js
import React from 'react'

export default class Home extends React.Component {
    render(){
        return(
        <div>
          <a>跳转到home页面</a>
        </div>
        )
    }
}
//Route.js
import React from 'react'
import {HashRouter, Route, Switch} from 'react-router-dom'
import Home from '../home'
import Detail from '../detail'

const BasicRoute = () => (
<HashRouter>
     <Switch>
       <Route exact path='/' component={Home} />
       <Route exact path='/detail' component={Detail} />
      </Switch>
 </HashRouter>
)

export default BasicRoute;
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Router from './router/router'

ReactDOM.render(
<Router/>,
document.getElementById('root')
)

通过a标签跳转

修改home.jsdetail.js

//home.js
import React from 'react'

export default class Home extends React.Component {
    render(){
        return(
        <div>
           <a href='#/detail'>跳转到detail页面</a>
        </div>
        )
    }
}
//detail.js
import React from 'react';

export default class Home extends React.Component {
    render() {
        return (
            <div>
                <a href='#/'>回到home</a>
            </div>
        )
    }
}

通过函数跳转

首先需要修改router.js中的代码

...
import {HashRouter, Route, Switch, hashHistory} from 'react-router-dom';
...
<HashRouter history={hashHistory}>
...

然后在home.js

export default class Home extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>
                <a href='#/detail'>去detail</a>
                <button onClick={() => this.props.history.push('detail')}>通过函数跳转</button>
            </div>
        )
    }
}

传参

很多场景下,我们还需要在页面跳转的同时传递参数,在react-router-dom中,同样提供了两种方式进行传参:

url传参和通过push函数隐式传参

url传参

修改route.js中的代码

...
<Route exact path="/detail/:id" component={Detail}/>
...

然后修改detail.js,使用this.props.match.params来获取url传过来的参数

...
componentDidMount() {
    console.log(this.props.match.params);
}
...

在地址栏输入“http://localhost:3000/#/detail/3”,打开控制台可以看到

隐式传参

修改home.js

import React from 'react';

export default class Home extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>
                <a href='#/detail/3'>去detail</a>
                    <button onClick={() => this.props.history.push({
                        pathname: '/detail',
                        state: {
                            id: 3
                        }
                })}>通过函数跳转</button>
            </div>
        )
    }
}

在detai.js中,就可以使用this.props.location.state获取home传过来的参数

componentDidMount() {
    //console.log(this.props.match.params);
    console.log(this.props.history.location.state);
}

跳转后打开控制台可以看到参数被打印

其他函数

replace

有些场景下,重复使用push或a标签跳转会产生死循环,为了避免这种情况出现,react-router-dom提供了replace。在可能会出现死循环的地方使用replace来跳转:

this.props.history.replace('/detail');
goBack

场景中需要返回上级页面的时候使用:

this.props.history.goBack();

React Router的基本用法—动态路由

React Router V4 实现了动态路由。

对于大型应用来说,一个首当其冲的问题就是所需加载的JavaScript的大小。程序应当只加载当前渲染页所需的JavaScript。有些开发者将这种方式称之为“代码分拆” —— 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。React-Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。Route可以定义 getChildRoutes,getIndexRoute 和 getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React-Router 会逐渐的匹配 URL 并只加载该URL对应页面所需的路径配置和组件。

const CourseRoute = {
  path: 'course/:courseId',

  getChildRoutes(location, callback) {
    require.ensure([], function (require) {
      callback(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades'),
      ])
    })
  },

  getIndexRoute(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Index'))
    })
  },

  getComponents(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Course'))
    })
  }
}

React Router的基本用法—嵌套路由

如果我们给/,/category/products创建了路由,但如果我们想要/category/shoes,/category/boots,/category/footwear这种形式的url呢?在React Router V4之前的版本中,我们的做法是利用Route组件的上下层嵌套:

 <Route exact path="/" component={Home}/>
 <Route path="/category" component={Category}/>
        <Route path='/category/shoes' component={Shoes}/>
        <Route path='/category/boots' component={Boots}/>
        <Route path='/category/footwear' component={Footwear}/>
 <Route path="/products" component={Products}/>

那么在V4版本中该怎么实现嵌套路由呢,我们可以将嵌套的路由放在父元素里面定义。

//app.js
import React, { Component } from 'react';
import { Link, Route, Switch } from 'react-router-dom
import Category from './Category'

class App extends Component {
    render(){
        return(
         <div>
        <nav className="navbar navbar-light">
          <ul className="nav navbar-nav">
            <li><Link to="/">Homes</Link></li>
            <li><Link to="/category">Category</Link></li>
            <li><Link to="/products">Products</Link></li>
          </ul>
       </nav>

    <Switch>
      <Route exact path="/" component={Home}/>
      <Route path="/category" component={Category}/>
      <Route path="/products" component={Products}/>
    </Switch>
    </div>
        )
    }
}

export default App;
//Category.jsx

import React from 'react';
import { Link, Route } from 'react-router-dom';

const Category = ({ match }) => {
return( <div> <ul>
    <li><Link to={`${match.url}/shoes`}>Shoes</Link></li>
    <li><Link to={`${match.url}/boots`}>Boots</Link></li>
    <li><Link to={`${match.url}/footwear`}>Footwear</Link></li>

  </ul>
  <Route path={`${match.path}/:name`} render= {({match}) =>( <div> <h3> {match.params.name} </h3></div>)}/> //嵌套路由
  </div>)
}
export default Category;

我们需要理解上面的match对象,当路由路径和当前路径成功匹配时会产生match对象,它有如下属性:

  • match.url: 返回路由路径字符串,常用来构建Link路径
  • match.path: 返回路由路径字符串,常用来构建Route路径
  • match.isExact: 返回布尔值,如果准确(没有任何多余字符)匹配则返回true
  • match.params: 返回一个对象包含Path-to-RegExp包从URL解析测键值对

注意match.urlmatch.path没有太大区别,控制台经常出现相同的输出,例如访问/user

const UserSubLayout = ({ match }) => {
  console.log(match.url)   // output: "/user"
  console.log(match.path)  // output: "/user"
  return (
    <div className="user-sub-layout">
      <aside>
        <UserNav />
      </aside>
      <div className="primary-content">
        <Switch>
          <Route path={match.path} exact component={BrowseUsersPage} />
          <Route path={`${match.path}/:userId`} component={UserProfilePage} />
        </Switch>
      </div>
    </div>
  )
}
//注意这里match在组件的参数中被解构,意思就是我们可以使用match.path代替props.match.path

一般的,我们在构建Link组件的路径时用match.url,在构建Route组件的路径时用match.path

还有一个地方需要理解的是Route组件有三个可以用来定义要渲染内容的props:

  • component: 当URL匹配时,router会将传递的组件使用React.createElement来生成一个React元素
  • render:适合行内渲染,在当前路径匹配路由路径时,renderprop期望一个函数返回一个元素
  • children: childrenproprender很类似,也期望一个函数返回一个React元素。然而,不管路径是否匹配,children都会渲染。

React Router的基本用法—带path参数的嵌套路由

一个真实的路由应该是根据数据,然后动态显示。假设我们获取了从服务端API返回的product数据,如下所示

//Product.jsx

const productData = [
{
  id: 1,
  name: 'NIKE Liteforce Blue Sneakers',
  description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.',
  status: 'Available'

},
{
  id: 2,
  name: 'Stylised Flip Flops and Slippers',
  description: 'Mauris finibus, massa eu tempor volutpat, magna dolor euismod dolor.',
  status: 'Out of Stock'

},
{
  id: 3,
  name: 'ADIDAS Adispree Running Shoes',
  description: 'Maecenas condimentum porttitor auctor. Maecenas viverra fringilla felis, eu pretium.',
  status: 'Available'
},
{
  id: 4,
  name: 'ADIDAS Mid Sneakers',
  description: 'Ut hendrerit venenatis lacus, vel lacinia ipsum fermentum vel. Cras.',
  status: 'Out of Stock'
},

];

我们需要根据下面这些路径创建路由:

  • /products. 这个路径应该展示产品列表。
  • /products/:productId.如果产品有:productId,这个页面应该展示该产品的数据,如果没有,就该展示一个错误信息。
//Products.jsx

const Products = ({ match }) => {

   const productsData = [
    {
        id: 1,
        name: 'NIKE Liteforce Blue Sneakers',
        description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.',
        status: 'Available'

    },

    //Rest of the data has been left out for code brevity

];
 /* Create an array of `<li>` items for each product
  var linkList = productsData.map( (product) => {
    return(
      <li>
        <Link to={`${match.url}/${product.id}`}>
          {product.name}
        </Link>
      </li>
      )

    })

  return(
    <div>
        <div>
         <div>
           <h3> Products</h3>
           <ul> {linkList} </ul>
         </div>
        </div>

        <Route path={`${match.url}/:productId`}
            render={ (props) => <Product data= {productsData} {...props} />}/>
        <Route exact path={match.url}
            render={() => (
            <div>Please select a product.</div>
            )}
        />
    </div>
  )
}

下面是Product组件的代码

//Product.jsx

const Product = ({match,data}) => {
  var product= data.find(p => p.id == match.params.productId);
  var productData;

  if(product)
    productData = <div>
      <h3> {product.name} </h3>
      <p>{product.description}</p>
      <hr/>
      <h4>{product.status}</h4>  </div>;
  else
    productData = <h2> Sorry. Product doesnt exist </h2>;

  return (
    <div>
      <div>
         {productData}
      </div>
    </div>
  )
}

React Router的基本用法—保护式路由

考虑到这样一个场景,用户必须先验证登录状态才能进入到主页,所以需要保护式路由,这里需要保护的路由是Admin,如果登录没通过则先进入Login路由组件。保护式路由会用到重定向组件Redirect,如果有人已经注销了账户,想进入/admin页面,他们会被重定向到/login页面。当前路径的信息是通过state传递的,若用户信息验证成功,用户会被重定向回初始路径。在子组件中,你可以通过this.props.location.state获取state的信息。

`<Redirect to={{pathname: '/login', state: {from: props.location}}}`

具体地,我们需要自定义路由来实现上面的场景

class App5 extends React.Component {
    render(){
        return (
            <div className="app5">
                <ul>
                    <li>
                        <Link to='/'>Home</Link>
                    </li>
                    <li>
                        <Link to='/category'>Category</Link>
                    </li>
                    <li>
                        <Link to='/products'>Products</Link>
                    </li>
                    <li>
                        <Link to='/admin'>Admin</Link>
                    </li>
                </ul>
                <Route exact path='/' component={Home} />
                <Route path='/category' component={Category} />
                <Route path='/products' component={Products} />
                <Route path='/login' component={Login} />

                {/*自定义路由*/}
                <PrivateRoute path='/admin' component={Admin} />
            </div>
        )
    }
}

const Home = props => <h2>This is Home {console.log('Home-Props')}{console.log(props)}</h2>

const Admin = () => <h2>Welcome to admin!</h2>

// 自定义路由
const PrivateRoute = (({component:Component,...rest}) => {
    return (
        <Route
            {...rest}
            render={props =>
                // 如果登录验证通过则进入Admin路由组件
                fakeAuth.isAuthenticated === true
                ?(<Component />)
                // 将from设置为Admin路由pathname,并传递给子组件Login
                :(<Redirect to={{pathname:'/login',state:{from:props.location.pathname}}} />)
            }
         />
    )
})

Login组件实现如下,主要就是通过this.props.location.state.from来记住是从哪个页面跳转过来的,然后如果toAdminfalse的话就要进行登录,登录后将toAdmin设为true,为true就是进行重定向跳转到原来的页面<Redirect to={from} />

class Login extends React.Component {
    constructor(){
        super()
        this.state = {
            toAdmin:false
        }
    }

    login = () =>{
        fakeAuth.authenticate(() => {
            this.setState({
                toAdmin:true
            })
        })
    }

    render(){
        const from = this.props.location.state.from
        const toAdmin = this.state.toAdmin
        if(toAdmin) {
            return (
                <Redirect to={from} />
            )
        }
        return (
            <div className="login">
            {console.log(this.props)}
                <p>You must log in then go to the{from} </p>
                <button onClick={this.login}>
                    Log in
                </button>
            </div>
        )
    }
}

export default Login

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

推荐阅读更多精彩内容

  • React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的。它也分...
    __db84阅读 735评论 0 0
  • React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的。它也分...
    IT老马阅读 58,879评论 0 49
  • React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的。它也分...
    应晨皓阅读 435评论 0 0
  • React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的。它也分...
    CLYDE_6715阅读 267评论 0 0
  • React Router教程 React项目的可用的路由库是React-Router,当然这也是官方支持的。它也分...
    惊蛰_5269阅读 302评论 0 0