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.hash
8. pathname:window.location.hash
9. },
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:false
10. },
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.js
2.
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传入的value
15.
16. import { Consumer} from './context'
17. render(){
18. <Consumer>
19. {state => {
20. //这里的state就是provider传入的value
21. if(state.pathname===path){
22. return this.props.component
23. }
24. return null
25. }}
26. </Consumer>
27. }
28. 复制代码
Provider组件实现了,其他的就比较好办了,在hashRouter顶级组件中使用Provider组件,里面每个子组件中外层采用Consumer包裹,这样每个组件都能拿到provider的数据
hashRouter.js实现
hashRouter用于提供hisotry的数据以及方法给到子组件,如push,go等方法
1. //react-router-dom文件夹下hashRouter.js
2.
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 = to
17. }
18. }
19. };
20. }
21.
22. componentDidMount () {
23. let location = this.state
24. 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.pathname
17. 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:true
8. })
9. re.test('/foo/1') // true
10.
11. //生成的正则
12. /^\/detail(?:\/)?$/i
13. /^\/detail(?:\/(?=$))?(?=\/|$)/i
14.
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.children
16. 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 child
22. }
23. }
24. return null
25. }}
26. </Consumer>
27. );
28. }
29. }
30.
31.
32. //使用Switchs
33.
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 null
16. }}
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.queue
25. let {location} = this.state
26. 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.state
41. 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. 复制代码