React Router使用教程

React Router知识体系

React Router使用教程_第1张图片
React Router概念图

React Router基本用法

import React from 'react';
import { render, findDOMNode } from 'react-dom';
import { Router, Route, Link, IndexRoute, Redirect } from 'react-router';
import { createHistory, createHashHistory, useBasename } from 'history';

// 此处用于添加根路径
const history = useBasename(createHashHistory)({
  queryKey: '_key',
  basename: '/blog-app',
});

React.render((
  
    
      
      
      
      
      
        
      
      
      
    
  
), document.getElementById('app'));

从上面代码中,我们可以发现:

  • Router 与 Route 一样都是 react 组件,它的 history 对象是整个路由系统的核心,它暴露了很多属性和方法在路由系统中使用;
  • Router不会被渲染,只是创建内部路由规则的配置对象
  • Route 的 path 属性表示路由组件所对应的路径,可以是绝对或相对路径,绝对路径可以忽略嵌套,相对路径由祖先节点和自身的路径拼接组成;
  • Route组件的path属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件。
  • Redirect 是一个重定向组件,有 from 和 to 两个属性;
  • Route 的 onEnter 钩子将用于在渲染对象的组件前做拦截操作,比如验证权限;
  • 在 Route 中,可以使用 component 指定单个组件,或者通过 components 指定多个组件集合;
  • Route组件还可以嵌套使用。
path属性可以使用通配符

// 匹配 /hello/tom


// 匹配 /hello
// 匹配 /hello/tom
// 匹配 /hello/liyan


// 匹配 /files/signin.jpg
// 匹配 /files/signout.html


// 匹配 /files/ 
// 匹配 /files/a
// 匹配 /files/a/b


// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg

通配符的规则如下:
(1):paramName

:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出。
(2)()

()表示URL的这个部分是可选的。
(3)*

*匹配任意字符直到名中下一个字符或者整个URL的末尾,并创建一个splat参数
(4) ***

** 匹配任意字符,直到下一个/、?、#为止。

路由原理

无论是传统的后端 MVC 主导的应用,还是在当下最流行的单页面应用中,路由的职责都很重要,但原理并不复杂,即保证视图和 URL 的同步,而视图可以看成是资源的一种表现。当用户在页面中进行操作时,应用会在若干个交互状态中切换,路由则可以记录下某些重要的状态,比如在一个购物系统中用户是否登录、购物车中有哪些物品。而这些变化同样会被记录在浏览器的历史中,用户可以通过浏览器的前进、后退按钮切换状态,同样可以将 URL 分享给好友。简而言之,用户可以通过手动输入或者与页面进行交互来改变 URL,然后通过同步或者异步的方式向服务端发送请求获取资源(当然,资源也可能存在于本地),成功后重新绘制 UI,原理如下图所示:

React Router使用教程_第2张图片
路由原理图
Route标签的原理

从上面的例子可以看出Route拥有下面的几个props:

  • exact:propType.bool
  • path:propType.string
  • component:propType.func
  • render:propType.func

他们都不是必填项,注意,如果path没有赋值,那么此Route就是默认渲染的。
Route的作用就是当url和Route中path属性的值匹配时,就渲染component中的组件或者render中的内容。

Route利用的是history|(history是用来兼容不同浏览器或者环境下的历史记录管理的,当我跳转或者点击浏览器的后退按钮时,history就必须记录这些变化)的listen方法来监听url的变化。为了防止引入新的库,Route的创作者选择了使用html5中的popState事件,只要点击了浏览器的前进或者后退按钮,这个事件就会触发,我们来看一下Route的代码:

class Route extends Component {
  static propTypes: {
    path: PropTypes.string,
    exact: PropTypes.bool,
    component: PropTypes.func,
    render: PropTypes.func,
  }

  componentWillMount() {
    addEventListener("popstate", this.handlePop)
  }

  componentWillUnmount() {
    removeEventListener("popstate", this.handlePop)
  }

  handlePop = () => {
    this.forceUpdate()
  }

  render() {
    const {
      path,
      exact,
      component,
      render,
    } = this.props

    //location是一个全局变量
    const match = matchPath(location.pathname, { path, exact })

    return (
      //有趣的是从这里我们可以看出各属性渲染的优先级,component第一
      component ? (
        match ? React.createElement(component, props) : null
      ) : render ? ( // render prop is next, only called if there's a match
        match ? render(props) : null
      ) : children ? ( // children come last, always called
        typeof children === 'function' ? (
          children(props)
        ) : !Array.isArray(children) || children.length ? ( // Preact defaults to empty children array
          React.Children.only(children)
        ) : (
              null
            )
      ) : (
              null
            )
    )
  }
}

Route在组件将要Mount的时候添加popState事件的监听,每当popState事件触发,就使用forceUpdate强制刷新,从而基于当前的location.pathname进行一次匹配,再根据结果渲染。

那么,matchPath方法是如何实现的呢?
Route引入了一个外部library:path-to-regexp。这个pathToRegexp方法用于返回一个满足要求的正则表达式,举个例子:

let keys = [],keys2=[]
let re = pathToRegexp('/home/:login', keys)
//re = /^\/home\/([^\/]+?)\/?$/i  keys = [{ name: 'login', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]   

let re2 = pathToRegexp('/home/login', keys2)
//re2 = /^\/home\/login(?:\/(?=$))?$/i  keys2 = []

值得一提的是matchPath方法中对匹配结果作了缓存,如果是已经匹配过的字符串,就不用再进行一次pathToRegexp了。

随后的代码就清晰了:

const match = re.exec(pathname)

if (!match)
  return null

const [ url, ...values ] = match
const isExact = pathname === url

//如果exact为true,需要pathname===url
if (exact && !isExact)
  return null

return {
  path, 
  url: path === '/' && url === '' ? '/' : url, 
  isExact, 
  params: keys.reduce((memo, key, index) => {
    memo[key.name] = values[index]
    return memo
  }, {})
}

Link

Link相当与html中的a标签,通过点击锚来实现页面的跳转,Link 组件的 API 应该如下所示:


其中的 to 是一个指向跳转目标地址的字符串,而 replace 则是布尔变量来指定当用户点击跳转时是替换 history 栈中的记录还是插入新的记录。基于上述的 API 设计,我们可以得到如下的组件声明:

class Link extends Component {
  static propTypes = {
    to: PropTypes.string.isRequired,
    replace: PropTypes.bool,
  }
}

现在我们已经知道 Link 组件的渲染函数中需要返回一个锚标签,因此我们需要为每个锚标签添加一个点击事件的处理器:

class Link extends Component {
  static propTypes = {
    to: PropTypes.string.isRequired,
    replace: PropTypes.bool,
  }
  handleClick = (event) => {
    const { replace, to } = this.props
    event.preventDefault()

    const { history } = this.context.router
    replace ? history.peplace(to) : history.push(to)
  }

  render() {
    const { to, children} = this.props

    return (
      
        {children}
      
    )
  }
}

需要注意的是,history.push和history.replace使用的是pushState方法和replaceState方法。

你可能感兴趣的:(React Router使用教程)