前言
从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 (正常情况下,url可能会有这几种情况,如/foo/bar, 或者/foo:123,这种url如果不处理,默认是匹配不到的,而exact参数就是控制是否精确匹配,这里引入了 库来生成正则表达式,来处理 url 中地址查询参数{state => { //pathToRegexp 方法,第一个参数, let reg= pathToRegexp(path,[],{end:exact }) let pathname = state.location.pathname if (reg.test(pathname)) { return ); }}复制代码; } return null; }}
//示例代码//如果需要精确匹配,将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组件等,有兴趣可自行研究
代码地址 :