首页 > 技术文章 > React Router

yxiaoqian 2016-12-04 22:13 原文

路由可以向应用中快速的添加视图和数据流,同时保持页面与URL同步

import {Router ,Route, Link} from 'react-router'

class App extends React.Component {
        render(){
             return (
                <div>
                    <ul >
                        <li><Link to='/about'>About</Link></li>
                        <li><Link to='/inbox'>Inbox</Link></li>
                    </ul>
                    {this.props.children}
                </div>
                )}})
//获取URL参数
class Message extends React.Component{
    componentDidMount(){
      const id = this.props.params.id //来自于路径 ‘/inbox/messages/:id'
      
      fetchMessage(id,function(err,message){
        this.setState({message:message})
      })
  }
  render(){
    return(
      <div>
          <h3>message{this.props.params.id}</h3>
      </div>

}

React.render((
<Router> <Route path='/' component={App}> <Route path='about' component={About} /> <Route path='inbox' component={Inbox} onEnter={requireAuth}>
                  <Route path='messages/:id component={Message} />
              </Route>
</Route> <Router> ).document.body);

以上获取URL参数方法:

1 id = this.props.params.id        来自父组件路径path=‘/index/message.:id'

2 baz= this.props.location.query.bar   访问路径为  /foo?bar=baz 时,获取baz的值

3 newhash = window.location.hash 简单的用于跳转,不用存储location state

添加首页:IndexRouter 设置默认页面,当App的render中this.props.children还是undefined时显示

<IndexRoute component={Dashboard}/>

IndexLink

如果使用<Link to='/'>Home</Link>,他会一直处于激活状态,因为所有的URL开头都是‘/'。而我们希望仅仅在home被渲染之后才激活并链接它。  可以使用<IndexLink to='/'>home</IndexLink>.

简洁的URL:使用绝对路径,无需在URL中添加更多的层级,从而可以使用更简洁

<Route path='/messages/:id' component={Message}/>  但其可能在动态路由中无法使用

重定向Redirect 

<Redirect from='message/:id' to='/message/:id'/>

当有人点击/inbox/message/5时,会跳到/message/5,以防/inbox/message/5不存在

onEnter和onLeave进入和离开的hook,在页面跳转确认时触发。例如 权限验证,或数据持久保存

onLeave hook 在所有将离开的路由中触发,从最下层的字路由开始到最外层的父路由结束。

onEnter hook从最外层的父路由开始到最下层的字路由结束

例如:从/messages/5跳到/about,hook的执行顺序:/messages/:id的onLeave->/inbox的onLeave->/about的onEnter

requireAuth(nextState,replace,callback){if(isLogined){replace('./home');}else{replace('./login');}callback();}

路径语法

:paramName 匹配位于/ ? # 之后的URL;称作参数

() 在其内部的内容是可选的

* 匹配任意字符

History

一个history知道如何去监听浏览器地址栏的变化,并解析这个url转化为location对象,然后router使用它匹配到路由,渲染对应的组件。

1  browserHistory   

使用浏览器中的 historyAPI 用于处理URL,创建 example.com/some/path这样的真实URL 

2  hashHistory

我使用URL中的hash#部分去创建形如 example.com/#/some/path的路由.window.location.hash = newhash

3 creactMemoryHistory

Memory history 不会在地址栏被操作或读取,非常适合测试和其他渲染环境。但必须创建

const history = createMemoryHistory(location)

 

高级进阶

动态路由:

根据‘按需加载’的原则,路由非常适合做代码拆分:他的责任就是配置好每个view。

react router 的 路径匹配 及 组件加载 都是异步完成的,不仅允许你延迟加载组件,也可以延迟加载 路由的配置。在首次加载包中只需要有一个路径定义,路由会自动解析剩下的路径。

route定义的函数 getChildRoutes、getIndexRoute、getComponents  都为异步加载‘逐渐匹配’

const CourseRoute = {
path:'cource/:id',

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 Route 提供一个routerWillLeave的生命周期钩子,使得react组件可以拦截正在发生的跳转,或在离开route前提示用户。

routerWillLeave 返回值有以下两种:

1  return false 取消此次跳转

2  return 返回提示信息,在离开route前提示用户进行确认。

使用:1在route组件中引入 lifecycle mixin来安装这个钩子

import { Lifecycle } from 'react-router'

const Home = React.creactClass({

    //假设Home是一个 route 组件,使用 Lifecycle mixin 去获取一个 routerWillLeave 方法。

    mixins:[Lifecycle],
    
    routerWillLeave(nextLocation){
        if(!this.state.isSaved)
            return 'Your work is not saved! Are you sure leave?'
    },

    .....
})

2 如果想在一个深层次嵌套的组件中使用 routerWillLeave 钩子,只需在route组件中引入RouteContext mixin,这样就会把route放到context中。

import { Lifecycle, RouteContext } from 'react-router'

const Home  = React.creactClass ({

        //route 会被放在Home 和它子组件及孙子组件的context中,这样在层级树中Home及其所有子组件都可以拿到 route。

        mixin: [ RouteContext ],

        render(){
            return <NestedForm />
        } })

const NestedForm = React.creactClass({
         //后代组件使用Lifecycle mixin 获得 一个routerWillLeave 的方法。
        mixins:[Lifecycle],

        routerWillLeave(nextLocation){
            if(!this.state.isSave)
            return 'your work is not saved !'
        },
        ......
})

服务端渲染:

需求:放生错误时返回500, 需要重定向时返回30x,在渲染之前获得数据router

方法:在<Router>API下一层使用:

1 使用 match 在渲染之前根据 location 匹配route

2 使用 RoutingContext 同步渲染 route 组件

import { renderToString } from 'react-dom/server'
import { match,RoutingContext } from 'react-router'
import routes from './routes'

serve((req,res) => {

     //这里的req.url应该是从初识请求中获得的
    //完整的URL路径,包括查询字符串

    match({routes,location:req.url},{error,redirectLocation,renderProps) => {
    if(error){
        res.send(500,error.message)
        }else if (redirectLocation){
        res.redirect(302,redirectLocation.pathname + redirectLocation.search
        }else if (renderProps){
        res.send(200,renderToString(<RoutingContext {...renderProps}   //renderProps可以构建任何想要的形势
        }else{
        res.send(404,'Not found')
        }})})

 

路由组件的生命周期中获取数据:最简单的方法是通过router组件的生命周期Hook来实现。

理解当路由改变时组件生命周期的变化,可以在Invoice组件里实现一个简单的数据获取功能

let Invoice = React.creactClass({

    getInitialState(){
        return {
            invoice:null
        }
     },

    componentDidMount(){
        //初始化数据
        this.fetchInvoice()
    },

    componentDidUpdate(prevProps){
        //通过参数更新数据
        let oldId = prevProps.params.invoiceId
        let newId = this.props.params.invoiceId
        if (newId !== oldId)
            this.fetchInvoice()
        },

    componentWillUnmount(){
        //在组件移除前 忽略正在进行中的请求
        this.ignoreLastFetch = true
        },

    fetchInvoice(){
        let url = '/api/invoices/${this.props.params.invoiceId}
        this.request = fetch(url,(err,data) => {
            if (!this.ignoreLastFetch)
                this.setState({invoice:data.invoice})
            })
},

render(){
        return <InvoiceView invoice = {this.state.invoice}/>
        }
})

组件外部跳转:

在组件内部使用this.context.router来实现导航;

在组件外部使用Router组件上被赋予的history可以实现导航。

//main file 里renders a Router
import { Router,browserHistory } from 'react-router'
import routes from './app/routes'

render(<Router history={browserHistory} routers={routes} />.el)


//  a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')

 

浏览器的前进后退按钮发生了什么????

web浏览器的历史记录相当于一个仅有入栈的栈,当用户浏览器到某一个页面时将该文档存入到栈中,点击「后退」或「前进」按钮时移动指针到 history 栈中对应的某一个文档。

  • 使用 hashchange 事件来监听 window.location.hash 的变化
  • hash 发生变化浏览器会更新 URL,并且在 history 栈中产生一条记录
  • 路由系统会将所有的路由信息都保存到 location.hash 中
  • 在 react-router 内部注册了 window.addEventListener('hashchange', listener, false) 事件监听器
  • listener 内部可以通过 hash fragment 获取到当前 URL 对应的 location 对象

  在react中监听路由hash改变,包括 浏览器前进回退按钮 :componentWillReceiveProps(nextProps){if(nextProps.location.pathname != this.props.location.pathname){window.addEventListener('hashchange',listener,false)};

点击Link后 路由系统发生????

它的 to、query、hash 属性会被组合在一起并渲染为 href 属性。然后调用history.pushState(state,path) ,

然后调用 window.location.hash 或者 window.history.pushState() 修改了应用的 URL

推荐阅读