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
1. //监听hash变化2. window.addEventListener ('hashchange', (e)=> {3. this.setState({4. ...this.state,5. location:{6. ...location,7. hash:window.location.hash8. pathname:window.location.hash9. },10. })11. });12. 复制代码
2.history路由
window.history 对象表示窗口的浏览历史,它只有back()、forward() 和 go() 方法可以让用户调用, 而h5规范中又新增了几个关于操作history记录的APi,分别是replaceState,pushState,popstate
在点击浏览器前进和后退的时候,都会触发popstate事件,而采用pushState和replaceState不会触发此事件,
1. 代码示例2. /*3. state 要跳转到的URL对应的状态信息,可以存一些需要想保存的值,也可以直接传{}4. title 该条记录的title,现在大多数浏览器不支持或者忽略这个参数5. url 这个参数提供了新历史纪录的地址,可以是相对路径,不可跨域6. */7. window.history.pushState(state, title, url)8. //replaceState和pushState的不同之处在与,replace是替换栈顶上的那个元素,不会影响栈的长度9. window.history.replaceState(state, title, url)10.11. //例子12. window.addEventListener('popstate',(e)=>{13. this.setState({14. ...this.state,15. location:{16. ...location,17. pathname:e.state.path,18. },19. })20. })21. 复制代码
实现路由
有了以上的知识点,就可以动手写组件了,在动手写组件之前,先来看看官方路由的具体用法,才能知道如何去设计这些组件
模块导入和导出
1. import { HashRouter as Router, Route,Link, Redirect,Switch,} from 'react-router-dom';2. 复制代码
react-router-dom中引出了很多的组件,模块中向外部导出接口,常见的做法是文件夹中有一个index.js向外暴露出这个模块的所有接口,所以可以设计为react-router-dom文件夹会下有一堆组件,通过一个index.js,使用export defalut向外部导出接口对接
路由中的组件使用示例
1. //router.js 配置路由2. export default const BasicRouter = () => {3. return (4. <div>5. <Router>6. <div>7. <div>8. <Link to="/home">首页</Link>9. <Link to="/detail">详情</Link>10. </div>11. <Switch>12. <Route path="/home" component={Home} />13. <Route path="/detail" component={Detail} />14. <Redirect to="/home" />15. </Switch>16. </div>17. </Router>18. </div>19. )20. }21. 复制代码
可以看到Router是最外层的父组件,它里面的每个子组件都可以从props中拿到Router组件中的state,router看作是父组件,而里面的route、Switch组件等,一般做法是采用porps向下级传递的方法,但如父子组件中间跨了多个子组件,采用props传值就很麻烦,这里采用组件的context来传递共享数据
1. //使用路由后,在所有子组件中打印this.props,会发现有这一陀东西,这里只是router组件中的部分state状态2. {3. history:{4. replace:e=>{},5. push:e=>{},6. },7. match:{8. params:'',9. isExact:false10. },11. location:{12. pathname:'',13. hash:'',14. }15. }16. 复制代码
熟悉redux的人应该都知道,store中的共享状态需要通过一个顶层组件作为父组件,一般将顶级组件叫做Provider组件,由它内部创建context来作为数据的提供者
例如redux中的connect方法,它就是一个高阶组件,connext方法的参数在函数中通过解构拿到store中的数据,再通过props的方式给到connext传入的组件中,而在react 16.3版本中新增createContext方法,它返回了Provider, Consumer组件等,
context实现
1. //context.js2.3. import React from 'react';4. let { Provider,Consumer } = React.createContext()5. export { Provider, Consumer}6.7. //顶级组件8.9. import { Provider } from './context'10. <Provider value={this.state}>11. {this.props.children}12. </Provider>13.14. //所有的子级组件 Consumer里面的childer是一个函数,由函数来返回渲染的块,state就是provider传入的value15.16. import { Consumer} from './context'17. render(){18. <Consumer>19. {state => {20. //这里的state就是provider传入的value21. if(state.pathname===path){22. return this.props.component23. }24. return null25. }}26. </Consumer>27. }28. 复制代码
Provider组件实现了,其他的就比较好办了,在hashRouter顶级组件中使用Provider组件,里面每个子组件中外层采用Consumer包裹,这样每个组件都能拿到provider的数据
hashRouter.js实现
hashRouter用于提供hisotry的数据以及方法给到子组件,如push,go等方法
1. //react-router-dom文件夹下hashRouter.js2.3. import React, {Component} from 'react';4. import {Provider} from './context';5.6. export default class HashRouter extends Component {7. constructor () {8. super (...arguments);9. this.state = {10. location: {11. pathname: window.location.hash.slice(1), //去除#号12. hash: window.location.hash,13. },14. history:{15. push(to){16. window.location.hash = to17. }18. }19. };20. }21.22. componentDidMount () {23. let location = this.state24. window.addEventListener ('hashchange', (e)=> {25. this.setState ({26. location: {27. ...location,28. hash:window.location.hash,29. pathname: window.location.hash.slice (1) || '', //去除#号30. },31. });32. });33. }34. render () {35. return (36. <Provider value={this.state}>37. {this.props.children}38. </Provider>39. );40. }41. }42. 复制代码
hashRouter组件state中的的push方法,直接将 window.location.hash值改变,会触发haschange时间,而在componentDidMount钩子函数中,监听hashchange事件中,在变化后将hash值存入state中
在componentWillUnmount记得要把绑定的事件解绑,remove事件需要将函数抽出来作为一个变量引用才能清除掉
Route.js实现
该组件用来传入component和path
1. import React, {Component} from 'react';2. import { Consumer} from './context'3. const pathToRegexp = require('path-to-regexp');4.5. export default class Route extends Component {6. constructor () {7. super (...arguments)8. }9. render () {10. let { path, component: Component, exact=false } = this.props;11. return (12. <Consumer>13. {state => {14. //pathToRegexp 方法,第一个参数,15. let reg= pathToRegexp(path,[],{end:exact })16. let pathname = state.location.pathname17. if (reg.test(pathname)) {18. return <Component {...state} />;19. }20. return null;21. }}22. </Consumer>23. );24. }25. }26. 复制代码
正常情况下,url可能会有这几种情况,如/foo/bar, 或者/foo:123,这种url如果不处理,默认是匹配不到的,而exact参数就是控制是否精确匹配,这里引入了 pathToRegexp库来生成正则表达式,来处理 url 中地址查询参数
1. //示例代码2.3.4. //如果需要精确匹配,将pathToRegexp的第三个参数end传为true,pathToRegexp第二个参数是匹配到的值5. let ret = []6. var re = pathToRegexp('/detail',ret,{7. end:true8. })9. re.test('/foo/1') // true10.11. //生成的正则12. /^\/detail(?:\/)?$/i13. /^\/detail(?:\/(?=$))?(?=\/|$)/i14.15. 复制代码
Switch.js实现
用于匹配只渲染一个route组件
1. import React, {Component} from 'react';2. import { Consumer} from './context'3. const pathToRegexp = require('path-to-regexp');4.5. export default class Switch extends Component {6. constructor () {7. super (...arguments);8. }9.10. render () {11. return (12. <Consumer>13. {state => {14. let pathname =state.location.pathname;15. let children = this.props.children16. for(let i=0;i<children.length;i++){17. let child = children[i]18. let path = child.props.path || ''19. let reg = pathToRegexp(path,[],{end:false})20. if(reg.test(pathname)){21. return child22. }23. }24. return null25. }}26. </Consumer>27. );28. }29. }30.31.32. //使用Switchs33.34. <Switch>35. <Route path="/home" component={Home} />36. <Route path="/detail" component={Detail} />37. <Redirect to="/home"/>38. </Switch>39.40. 复制代码
Switch组件将传入的children,遍历拿到每一个组件传入的path,并生成正则,如果正则能够匹配的上,则直接渲染child,否则return null,确保switch中包裹的子组件,只能渲染其中一个,switch组件是用于配合redirect组件来使用的
redirect.js实现
用于重定向
1. import React, {Component} from 'react';2. import { Consumer} from './context'3.4. export default class Redirect extends Component {5. constructor () {6. super (...arguments);7. }8.9. render () {10. return (11. <Consumer>12. {state => {13. let { history }= state;14. history.push(this.props.to)15. return null16. }}17. </Consumer>18. );19. }20. }21. 复制代码
redirect组件实现非常简单,如果该组件渲染,直接将window.location.hash = to
browserRouter.js的实现
browserRouter与hashRouter的实现不同点是,在state的push方法中调用window.history.pushState,压入后,浏览器的url会直接变化页面不会刷新,另外popstate监听事件,也需要同步一次state里面的pathname
1. import React, {Component} from 'react';2. import {Provider} from './context';3.4. class browserRouter extends Component {5. constructor () {6. super (...arguments);7. this.state = {8. location: {9. pathname: window.location.pathname ,10. hash: window.location.hash,11. },12. history:{13. push :(to)=>{14. this.pushState(null,null,to)15. }16. },17. queue:[]18. };19. this.pushState = this.pushState.bind(this)20. }21.22.23. pushState = (state="",title="",path="")=>{24. let queue = this.state.queue25. let {location} = this.state26. let historyInfo ={state,title,path}27. queue.push(historyInfo)28. this.setState({29. ...this.state,30. location:{31. ...location,32. pathname:path,33. },34. queue,35. })36. window.history.pushState(historyInfo,title,path)37. }38.39. componentDidMount () {40. let {location} = this.state41. window.addEventListener('popstate',(e)=>{42. this.setState({43. ...this.state,44. location:{45. ...location,46. pathname:e.state.path,47. },48. queue:this.state.queue,49. })50. })51. }52.53. render () {54. return (55. <Provider value={this.state}>56. {this.props.children}57. </Provider>58. );59. }60. }61.62. export default browserRouter;63. 复制代码
如何使用?
1.新建一个router.js,用于管理route组件
2.在index.js中导入使用
1.2. import React from 'react';3. import {4. HashRouter as Router,5. // BrowserRouter as Router,6. Route,7. Link,8. Redirect,9. Switch,10. } from './react-router-dom';11.12. import Home from './pages/home';13. import Detail from './pages/detail';14.15. const BasicRoute = () => {16. return (17. <div>18. <Router>19. <div>20. <div>21. <Link to="/home">首页</Link>22. <Link to="/detail">详情</Link>23. </div>24. <Switch>25. <Route path="/home" component={Home} />26. <Route path="/detail" component={Detail} />27. <Redirect to="/home" />28. </Switch>29. </div>30.31. </Router>32. </div>33. );34. };35. export default BasicRoute;36.37.38. // index.js中 使用39.40. import Router from './router'41. ReactDOM.render(<Router/>, document.getElementById('root'));42.43. 复制代码
