博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
react笔记--手动实现一个react-router(简易版)
阅读量:6903 次
发布时间:2019-06-27

本文共 8733 字,大约阅读时间需要 29 分钟。

前言

从vue转入到react技术栈有两月了,两个月来一直断断续续学习react的知识。自己也很久没有写过总结了(恐怖的加班),趁元旦假期抽空总结一波(还是要学习地)。习惯了vue简洁的语法和api,再回过来写react组件化,不习惯有木有(怪自己太菜)。

文中若有错误点,欢迎各位大佬指正

react-router路由的模式选择

用过react-router的会比较熟悉react路由模式,一般有两种,分别是hashHistory和history, 使用hashHistory模式,url后面会带有#号不太美观,而使用history模式,就是正常的url,但是如果匹配不到这个路由就会出现404请求。这种情况需要在服务器配置,如果URL匹配不到任何静态资源,就跳转到默认的index.html

两种方式实现原理

1.hashHistory路由

hash值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制

如http://localhost:3000/detail#/home,这段url的#号后面的就为hash值
window.location.hash 取到的就是#home

//监听hash变化 window.addEventListener ('hashchange',  (e)=> {        this.setState({            ...this.state,            location:{              ...location,               hash:window.location.hash               pathname:window.location.hash            },      }) });复制代码
2.history路由

window.history 对象表示窗口的浏览历史,它只有back()、forward() 和 go() 方法可以让用户调用, 而h5规范中又新增了几个关于操作history记录的APi,分别是replaceState,pushState,popstate

在点击浏览器前进和后退的时候,都会触发popstate事件,而采用pushState和replaceState不会触发此事件,

代码示例/*  state   要跳转到的URL对应的状态信息,可以存一些需要想保存的值,也可以直接传{}  title   该条记录的title,现在大多数浏览器不支持或者忽略这个参数  url     这个参数提供了新历史纪录的地址,可以是相对路径,不可跨域*/window.history.pushState(state, title, url) //replaceState和pushState的不同之处在与,replace是替换栈顶上的那个元素,不会影响栈的长度window.history.replaceState(state, title, url) //例子 window.addEventListener('popstate',(e)=>{      this.setState({        ...this.state,        location:{          ...location,          pathname:e.state.path,        },      }) })复制代码

实现路由

有了以上的知识点,就可以动手写组件了,在动手写组件之前,先来看看官方路由的具体用法,才能知道如何去设计这些组件

模块导入和导出
import {  HashRouter as Router, Route,Link, Redirect,Switch,} from 'react-router-dom';复制代码

react-router-dom中引出了很多的组件,模块中向外部导出接口,常见的做法是文件夹中有一个index.js向外暴露出这个模块的所有接口,所以可以设计为react-router-dom文件夹会下有一堆组件,通过一个index.js,使用export defalut向外部导出接口对接

路由中的组件使用示例
//router.js  配置路由export default const BasicRouter = () => {    return (        
首页
详情
)}复制代码

可以看到Router是最外层的父组件,它里面的每个子组件都可以从props中拿到Router组件中的state,router看作是父组件,而里面的route、Switch组件等,一般做法是采用porps向下级传递的方法,但如父子组件中间跨了多个子组件,采用props传值就很麻烦,这里采用组件的context来传递共享数据

//使用路由后,在所有子组件中打印this.props,会发现有这一陀东西,这里只是router组件中的部分state状态{    history:{      replace:e=>{},        push:e=>{},      },    match:{        params:'',        isExact:false    },    location:{        pathname:'',        hash:'',    }}复制代码

熟悉redux的人应该都知道,store中的共享状态需要通过一个顶层组件作为父组件,一般将顶级组件叫做Provider组件,由它内部创建context来作为数据的提供者

例如redux中的connect方法,它就是一个高阶组件,connext方法的参数在函数中通过解构拿到store中的数据,再通过props的方式给到connext传入的组件中,而在react 16.3版本中新增createContext方法,它返回了Provider, Consumer组件等,

context实现
//context.jsimport React from 'react';let { Provider,Consumer } = React.createContext()export  { Provider, Consumer}//顶级组件import { Provider } from './context'
{this.props.children}
//所有的子级组件 Consumer里面的childer是一个函数,由函数来返回渲染的块,state就是provider传入的valueimport { Consumer} from './context'render(){
{state => { //这里的state就是provider传入的value if(state.pathname===path){ return this.props.component } return null }}
}复制代码

Provider组件实现了,其他的就比较好办了,在hashRouter顶级组件中使用Provider组件,里面每个子组件中外层采用Consumer包裹,这样每个组件都能拿到provider的数据

hashRouter.js实现

hashRouter用于提供hisotry的数据以及方法给到子组件,如push,go等方法

//react-router-dom文件夹下hashRouter.jsimport React, {Component} from 'react';import {Provider} from './context';export default class HashRouter extends Component {  constructor () {    super (...arguments);    this.state = {      location: {        pathname: window.location.hash.slice(1), //去除#号        hash: window.location.hash,      },      history:{        push(to){            window.location.hash = to        }      }    };  }  componentDidMount () {    let location  = this.state    window.addEventListener ('hashchange',  (e)=> {      this.setState ({        location: {          ...location,          hash:window.location.hash,          pathname: window.location.hash.slice (1) || '',  //去除#号        },      });    });  }  render () {    return (      
{this.props.children}
); }}复制代码

hashRouter组件state中的的push方法,直接将 window.location.hash值改变,会触发haschange时间,而在componentDidMount钩子函数中,监听hashchange事件中,在变化后将hash值存入state中

在componentWillUnmount记得要把绑定的事件解绑,remove事件需要将函数抽出来作为一个变量引用才能清除掉

Route.js实现

该组件用来传入component和path

import React, {Component} from 'react';import { Consumer} from './context'const pathToRegexp = require('path-to-regexp');export default class Route extends Component {  constructor () {    super (...arguments)  }  render () {    let { path, component: Component, exact=false } = this.props;    return (      
{state => { //pathToRegexp 方法,第一个参数, let reg= pathToRegexp(path,[],{end:exact }) let pathname = state.location.pathname if (reg.test(pathname)) { return
; } return null; }}
); }}复制代码

正常情况下,url可能会有这几种情况,如/foo/bar, 或者/foo:123,这种url如果不处理,默认是匹配不到的,而exact参数就是控制是否精确匹配,这里引入了 库来生成正则表达式,来处理 url 中地址查询参数

//示例代码//如果需要精确匹配,将pathToRegexp的第三个参数end传为true,pathToRegexp第二个参数是匹配到的值let ret = []var re = pathToRegexp('/detail',ret,{    end:true })re.test('/foo/1')  // true//生成的正则/^\/detail(?:\/)?$/i                /^\/detail(?:\/(?=$))?(?=\/|$)/i     复制代码
Switch.js实现

用于匹配只渲染一个route组件

import React, {Component} from 'react';import { Consumer} from './context'const pathToRegexp = require('path-to-regexp');export default class Switch extends Component {  constructor () {    super (...arguments);  }  render () {    return (      
{state => { let pathname =state.location.pathname; let children = this.props.children for(let i=0;i
); }} //使用Switchs
复制代码

Switch组件将传入的children,遍历拿到每一个组件传入的path,并生成正则,如果正则能够匹配的上,则直接渲染child,否则return null,确保switch中包裹的子组件,只能渲染其中一个,switch组件是用于配合redirect组件来使用的

redirect.js实现

用于重定向

import React, {Component} from 'react';import { Consumer} from './context'export default class Redirect extends Component {  constructor () {    super (...arguments);  }  render () {    return (      
{state => { let { history }= state; history.push(this.props.to) return null }}
); }}复制代码

redirect组件实现非常简单,如果该组件渲染,直接将window.location.hash = to

browserRouter.js的实现

browserRouter与hashRouter的实现不同点是,在state的push方法中调用window.history.pushState,压入后,浏览器的url会直接变化页面不会刷新,另外popstate监听事件,也需要同步一次state里面的pathname

import React, {Component} from 'react';import {Provider} from './context';class browserRouter extends Component {  constructor () {    super (...arguments);    this.state = {      location: {        pathname: window.location.pathname ,        hash: window.location.hash,      },      history:{        push :(to)=>{          this.pushState(null,null,to)          }      },      queue:[]          };    this.pushState = this.pushState.bind(this)  }  pushState = (state="",title="",path="")=>{       let queue  = this.state.queue       let {location}  = this.state        let historyInfo ={state,title,path}       queue.push( historyInfo)       this.setState({        ...this.state,        location:{          ...location,          pathname:path,        },        queue,      })      window.history.pushState(historyInfo,title,path)  }  componentDidMount () {    let {location}  = this.state     window.addEventListener('popstate',(e)=>{      this.setState({        ...this.state,        location:{          ...location,          pathname:e.state.path,        },        queue:this.state.queue,      })    })  }  render () {    return (      
{this.props.children}
); }}export default browserRouter;复制代码

如何使用?

1.新建一个router.js,用于管理route组件

2.在index.js中导入使用

import React from 'react';import {   HashRouter as Router,  //  BrowserRouter as Router,  Route,  Link,  Redirect,  Switch,} from './react-router-dom';import Home from './pages/home';import Detail from './pages/detail';  const BasicRoute = () => {  return (    
首页
详情
);};export default BasicRoute;// index.js中 使用import Router from './router'ReactDOM.render(
, document.getElementById('root'));复制代码

结尾

简易版的router组件到这里就实现的差不多了,但是还是有很多功能没实现,比如query参数处理,link组件等,有兴趣可自行研究

代码地址 :

转载地址:http://nhvdl.baihongyu.com/

你可能感兴趣的文章
python这+=和=的拓展知识
查看>>
oracle集群件
查看>>
linux shell 中"2>&1"含义
查看>>
oracle 11g RAC grid安装前准备
查看>>
01背包 暴力搜索
查看>>
RIP区域和OSPF区域通信
查看>>
MySQL
查看>>
k3cloud开发环境引入dll与net源代码
查看>>
网络安全系列之四十 在Linux中设置SET位权限
查看>>
SCCM OSD部署排错
查看>>
十道非常好的shell脚本试题
查看>>
app项目案例一手机浏览器
查看>>
linuxmint安装配置
查看>>
java 中 isEmpty和isBlank区别
查看>>
申请SSL证书怎样验证域名所有权
查看>>
麒麟开源堡垒机集中管控平台软件简介
查看>>
第十一单元练习
查看>>
从零开始的linux 第十六章
查看>>
EOS内存RAM是如何买卖的
查看>>
微服务架构中zuul的两种隔离机制实验
查看>>