首页 > 其他 > 详细

React路由之HashRoute的实现原理

时间:2020-04-22 21:39:00      阅读:144      评论:0      收藏:0      [点我收藏+]

在上一篇中我们实现了BrowserRoute,这篇我们继续实现HashRouter。

本文的核心功能:

  • HashRouter
  • Route
  • Link
  • MenuLink
  • Switch
  • Redirect

1、HashRouter

HashRouter只是一个容器,并没有DOM结构,它渲染的就是它的子组件,并向下层传递location,代表当前的路径,当hash值发生变化的时候会通过hashchange捕获变化,并给pathname重新赋值,看一个具体的例子:
import React from "react";
import ReactDOM from "react-dom";
import {HashRouter as Router, Route} from ‘react-router-dom‘ //路由库
import Home from ‘./components/Home‘
import User from ‘./components/User‘
import Profile from ‘./components/Profile‘
ReactDOM.render(
  <Router>
    <Route exact path="/" component={Home}></Route>
    <Route path="/user" component={User}></Route>
    <Route path="/profile" component={Profile}></Route>
  </Router>,document.getElementById(‘root‘)
)
  • Router是路由容器,
  • Route是路由规则,一个Route代表一个路由规则
  • path 代表路径
  • component代表要渲染的组件

HashRouter的实现如下:

import React, {useState} from ‘react‘
import RouterContext from ‘./RouterContext‘
/**
 * HashRouter只是一个容器,并没有DOM结构,它渲染的就是它的子组件,并向下层传递location
 */

export default class HashRouter extends React.Component {
    state = {
        location: {
            pathname: window.location.hash.slice(1), // #/user
            state:window.history.state
        }
    }
    //组件挂载完成之后,根据hash改变pathname的值
    componentDidMount(){
        window.addEventListener(‘hashchange‘,event =>{
            this.setState({
                ...this.state,
                location: {
                    ...this.state.location,
                    pathname:window.location.hash.slice(1) 
                }
            })
        })
        window.location.hash = window.location.hash || ‘/‘ //如果没有hash值就给一个默认值
    }
    render() { //渲染子组件 如果子组件里面嵌套着二级三级路由需要通过上下文Context取
        let routerValue = {
            location: this.state.location
        }
        return (
            // this.props.children
            <RouterContext.Provider value={routerValue}>
                {this.props.children}
            </RouterContext.Provider>
        )
    }
}

因为HashRouter渲染的是它的子组件,那么子组件里面有可能嵌套着二级三级路由,这个时候就需要上下文Context来读取嵌套的值,需要创建一个Context

import React from ‘react‘
//创建一个上下文
let context = React.createContext()
export default context

在上面的HashRouter里面引入

2.Route

文章开始的例子中我们说过,route代表一条路由规则,path代表此规则的路径, component代表要渲染的组件,如果说通过Context传下来的路径location.pathname与当前属性中的路径path相匹配就进行渲染。

import React from ‘react‘
import RouterContext from ‘./RouterContext‘
import pathToRegexp from ‘path-to-regexp‘

export default class Route extends React.Component{
    static contextType = RouterContext //拿到的就是 this.context.location.pathname
    render() {
        let {path, component: RouteComeponent, exact} = this.props
        let pathname = this.context.location.pathname; //拿到地址栏路径
        let paramName = []
        let regexp = pathToRegexp(path, paramName, {end:exact})
        //进行匹配
        if(regexp.test(pathname)) {
            //渲染
            return <RouteComeponent></RouteComeponent>
        }else {
            return null
        }
    }
}

3.Link超链接

点击某个链接跳转到指定页面。实例:

import React from "react";
import ReactDOM from "react-dom";
import {HashRouter as Router, Route,Link} from ‘react-router-dom‘ //路由库
import Home from ‘./components/Home‘
import User from ‘./components/User‘
import Profile from ‘./components/Profile‘
/**
 * Router是路由容器
 * Route是路由规则,一个Route代表一个路由规则
 * path 代表路径 component代表要渲染的组件
 */
ReactDOM.render(
  <Router>
    <Link to="/">home</Link>
    <Link to="/user">user</Link>
    <Link to="/profile">profile</Link>
  </Router>,document.getElementById(‘root‘)
)

渲染结构:

技术分享图片

 

 

 可以看到它的渲染结构就是一个a链接,href就是属性to对应的值,所以可以这么实现link方法:

import React from ‘react‘
function Link (props) {
    return (
    <a href="{`#${props.to}`}">{props.children}</a>
    )
}

但是这种方法只适用于hash路由,如果不是hash路由,就需要通过上下文拿到一个history对象的一个push方法,实现路径的跳转。

import React from ‘react‘
import RouterContext from ‘./RouterContext‘
function Link (props) {
    return (
        <RouterContext.Consumer>
            {
                routerValue => (
                    <a href="{`#${props.to}`}" onClick={() => routerValue.history.push(props.to)}>{props.children}</a>
                )
            }
        </RouterContext.Consumer>
    )
}

同时需要修改HashRouter传过来的实参

修改前:

技术分享图片

 

 修改后:

技术分享图片

 

 接下来实现动态路由和二级路由

 

4.Switch

switch是为了解决route的唯一渲染,保证路由只渲染一个路径。

如果配置了<Switch>

<Router history={history}>
    <Switch>
        <Route path=‘/home‘ render={()=>(<div>首页</div>)}/>
        <Route path=‘/home‘ component={()=>(<div>首页</div>)}/>
    </Switch>
</Router>

技术分享图片

 

 如果没有配置:

<Router history={history}>
    <Route path=‘/home‘ render={()=>(<div>首页</div>)}/>
    <Route path=‘/home‘ render={()=>(<div>首页</div>)}/>
</Router>

技术分享图片

 

 实现Switch:

 

import React, { useContext } from ‘react‘ 
import RouterContext from ‘./RouterContext‘
import pathToRegexp from ‘path-to-regexp‘

/**
 * switch 的作用是负责子组件的匹配,只会渲染第一个匹配上的子组件
 * useContext 获取上下文对象的一种方式
 * @param {*} props 
 */
export default function (props) {
    let routerContext = useContext(RouterContext) //拿到上下文中传过来的location
    let children = props.children
    children = Array.isArray(children) ? children : [children] //判断是否是数组,如果不是就包装成数组
    let pathname = routerContext.location.pathname
    //对子组件进行匹配
    for(let i = 0; i < children.length; i++) {
        let child = children[i] //child是一个react元素它的返回值是一个虚拟dom {type:Route,props:{exact,path,component}}
        let {path=‘/‘,component,exact=false} = child.props //取出对应的属性
        let regexp = pathToRegexp(path, [], {end: exact})
        let matched = pathname.match(regexp)
        //若匹配进行渲染
        if(matched) {
            return child
        }
    }
    //若不匹配就返回null
    return null
}

拿到上下文中传过来的location,然后取出pathname。再对它的子组件进行遍历,如果子组件的path属性和当前上下文中传过来的pathname属性相匹配就进行渲染,若不匹配就返回null。

5.Redirect

重定向,当所有都不匹配的时候会重定向到新的页面,就是改变path值驱动页面重新渲染。

例子:

import React from "react";
import ReactDOM from "react-dom";
import { HashRouter as Router, Route, Link, Switch, Redirect } from "react-router-dom"; //路由库
import Home from "./components/Home";
import User from "./components/User";
import Profile from "./components/Profile";
/**
 * Router是路由容器
 * Route是路由规则,一个Route代表一个路由规则
 * path 代表路径 component代表要渲染的组件
 */
ReactDOM.render(
  <Router>
    <Link to="/">home</Link>
    <Link to="/user">user</Link>
    <Link to="/profile">profile</Link>
    <Switch>
      <Route exact path="/" component={Home}></Route>
      <Route path="/user" component={User}></Route>
      <Route path="/user" component={User}></Route>
      <Route path="/profile" component={Profile}></Route>
      <Redirect to="/"></Redirect>
      <Redirect from="/home" to="/"></Redirect> 
    </Switch>
  </Router>,
  document.getElementById("root")
);

第一个Redireact是当location.pathname与上面所有的path属性不相等的时候会重定向到path=‘/‘的页面

第二个Redireact是当location.pathname为/home的时候,重定向到path=‘/‘的页面

实现Redireact:

import React, { useContext } from ‘react‘ 
import RouterContext from ‘./RouterContext‘
export default function (props) {
    let routerContext = useContext(RouterContext)
    //当Redirect元素的props.from属性和当前location.pathname属性相等时或者from属性不存在时就直接跳转到to
    if(!props.from || props.from === routerContext.location.pathname) {
        routerContext.history.push(props.to)
    }
    return null
}

6.MenuLink

。。。明天更新。。。

 推荐博文:

https://segmentfault.com/a/1190000014313428

https://www.cnblogs.com/sunLemon/p/9020153.html

React路由之HashRoute的实现原理

原文:https://www.cnblogs.com/lyt0207/p/12734944.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!