路由组件和一般组件
写法不同 一般组件:<Demo> 路由组件:<Route path=’/demo’ component={Demo}>
存放位置不同 一般组件:src/component 路由组件:src/pages
接受到的 props 不同 一般组件:写组件标签时传递了什么,就能接收到什么 路由组件:接收到三个固定的 props:history、location、match
本文会讲到的 react-router API:
history:
action: “PUSH”
block: function block(prompt)
createHref: function createHref(location)
[√] go: function go(n)
[√] goBack: function goBack()
[√] goForward: function goForward()
length: 3
listen: function listen(listener)
location: Object { pathname: “/home”, search: “”, key: “m9oksj”, … }
[√] push: function push(path, state)
[√] replace: function replace(path, state)
location:
hash: “”
key: “rj45aa”
[√] pathname: “/about”
[√] search: “”
[√] state: undefined
match:
isExact: true // 精准匹配
[√] params: Object { }
[√] path: “/about”
[√] url: “/about”
路由组件 BrowerRouter 和 HashRouter react-router 的工作方式:在组件树顶层放一个 Router 组件,然后在组件树中散落着很多 Route 组件,顶层的 Router 组件负责分析监听 URL 的变化,在它之下的 Route 组件可以直接读取这些信息 。
底层原理不一样:
BrowserRouter 使用的是 H5 的 history API ,不兼容 IE9 以下版本。
HashRouter 使用的是 URL 的哈希值(锚点) ,# 的英文为 hash。
MemoryHistory:常用于非 DOM 环境,例如 React Native 或者测试,History 存于内存 。
url 表现形式不一样
刷新后对路由 state 参数的影响
对 BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
对 HashRouter 刷新后会导致路由 state 参数的丢失
何时选用
对于浏览器项目 我们通常选用: <BrowserRouter> 和 <HashRouter> 组件来实现 Router
react-native 项目 我们通常选用:MemoryHistory
备注:HashRouter 可以用于解决一些路径错误相关的问题
react-router-dom 的基本使用 react-router-dom 的安装
基本使用的示例代码 src 下目录如下
src
pages(用于存放路由组件)
App.jsx
index.js
src/index.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // src/index.js // 1、引入App.jsx和模块 import React from 'react' import ReactDOM from 'react-dom' import App from './App' /* 页面只能使用一个路由器进行管理,所以在最外层(App层)包裹一层router */ import { BrowserRouter as Router } from 'react-router-dom' // 2、渲染App组件 ReactDOM.render( <Router> <App /> </Router>, document.getElementById('root' ) )
src/App.jsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // src/App.jsx: // 1、引入模块和路由组件 import React, { Component } from 'react' import { Route, Link } from 'react-router-dom' import Home from './pages/Home/index.jsx' import About from './pages/About/index.jsx' // 2、声明路由 /* 原生html中,靠<a>跳转到不同的页面 <a class="list-group-item active" href="./about.html" >About</a> <a class="list-group-item" href="./home.html" >Home</a> */ /* 编写路由连接,在react中靠路由链接Link实现切换组件,整个页面需要用一个路由器管理, 所以Link、NavLink、Route组件都需要用Router组件包裹,这里的Router写在了index.js中,包裹在最外层 */ <Link className="list-group-item" to="/about" >About</Link> <Link className="list-group-item" to="/home" >Home</Link> // 3、注册路由 <Route exact path="/" component={About}></Route> <Route path="/about" component={About}></Route> <Route path="/home" component={Home}></Route>
src/pages/Home/index.js 1 2 3 4 5 6 7 import React, { Component } from 'react' export default class Home extends Component { render () { return <h3>我是Home的内容</h3> } }
src/pages/About/index.js 1 2 3 4 5 6 7 import React, { Component } from 'react' export default class About extends Component { render () { return <h3>我是About的内容</h3> } }
NavLink 和 Link Link 和 NavLink 都可以用来指定路由跳转,NavLink 属性更多
Link 方法一:通过字符串执行跳转路由 1 2 3 4 5 6 7 8 9 10 11 12 13 // 1、引入组件和模块 import Home from './pages/Home/index.jsx' import { Link } from 'react-router-dom' // 2、声明路由 // to="/要跳转到的页面路由" 与 Route 中的 path 一致 <Link to="/about" >About</Link> // 3、注册路由 // component={要展示的组件} <Route path="/home" component={Home}></Route>
方法二:通过字符串执行跳转路由 1 2 3 4 5 6 7 8 9 10 <Link to={{ pathname: '/login' , search: '?name=cedric' , hash : '#someHash' , state: { fromWechat: true } }}> <span>登录</span> </Link>
pathname: 表示要链接到的路径的字符串。
search: 表示查询参数的字符串形式。
hash: 放入网址的 hash,例如 #a-hash。
state: 状态持续到 location。通常用于隐式传参(埋点),可以用来统计页面来源
点击链接 进入 Login 页面后,就可以在 this.props.location.state 中看到 fromWechat: true
NavLink 当 NavLink 与当前 URL 匹配时,使用 activeClassName 为其渲染元素添加样式属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 1、引入组件 import { NavLink } from 'react-router-dom' import Home from './pages/Home/index.jsx' // 2、声明路由 <NavLink activeClassName="active" className="list-group-item" to="/home" > Home </NavLink> // 3、注册路由 <Route exact path="/" component={Home}></Route>
NavLink 的封装 为了优化在编写多个路由链接时,<NavLink> 组件中 className 和 activeClassName 的代码冗余,需要将 < NavLink > 组件封装起来。
使用时导入 <MyNavLink> 并传入 to 和 title 就能显示多个路由链接
在 src/component 下新建文件夹,名为 MyNavLink,在里面新建名为 index 的 jsx 文件
如何向 <MyNavLink> 传递 title 呢?
封装 NavLink 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React , { Component } from 'react' import { NavLink } from 'react-router-dom' export default class MyNavLink extends Component { render ( ) { { } { } return ( <NavLink activeClassName ="active" className ="list-group-item" {...this.props } > </NavLink > ) } }
使用封装好的组件 MyNavLink 1 2 3 4 5 6 7 8 // 声明路由时,向MyNavLink传递props,名为title,值为Home // 方法一:传递标签属性: <MyNavLink to='/home' title="Home" /> // 方法二:传递标签体内容 <MyNavLink to='/home' >Home</MyNavLink>
Switch 和 Router Switch:渲染与该地址匹配的第一个子节点 <Route> 或者 <Redirect>,只是匹配到第一个路由后,就不再继续匹配。
Router:渲染时会从上至下检索路由地址是否匹配,若是匹配,则渲染全部组件
1、通常情况下,patch 和 component 是一一对应的关系
2、用 Switch 能提高路由匹配效率 (单一匹配)
1 2 3 4 5 6 7 8 9 10 import { Switch } from 'react-router-dom' // 此时:只显示Home组件 <Switch> <Route path="/home" component={Home}></Route> <Route path="/home" component={Test}></Route> </Switch>
Redirect 重定向 导航到一个新的地址。即重定向。
1 2 3 4 5 6 7 8 9 import { Route, Redirect } from 'react-router-dom' // 注册路由 <Route path="/home" component={Home}></Route> <Redirect to="/home" from='/' /> // 当访问路由'/' 时,会直接重定向到'/home' 。
路由器的模糊匹配和严格匹配
模糊匹配(默认)
默认为模糊匹配模式
输入的路径要包含匹配路径,并且顺序一致
可以在默认的模糊匹配中向路由组件传递参数
严格(精确)匹配 —exact
使用 exact = {true} 或 exact 开启严格匹配模式
精确匹配路由,不要随便开启,有些时候开启会导致无法继续匹配二级路由
向路由组件传参的三种方式 在 src/pages/Home 下新建两个文件夹,名为 Message 和 News,在里面新建名为 index 的 jsx 文件。是 Home 页面的两个子页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React , { Component } from 'react' import Detail from '../Home_Message_Detail/index' import { Route , Link } from 'react-router-dom' class Message extends Component { state = { messageArr : [ { id : '01' , title : '消息1' }, { id : '02' , title : '消息2' }, { id : '03' , title : '消息3' }, ], } render ( ) { const { messageArr } = this .state return ( <div > <ul > {messageArr.map((messageObj) => { return ( <li key ={messageObj.id} > {/* 向路由组件传递params参数 代码区域*/} </li > ) })} </ul > <hr /> {/* 声明接收params参数 代码区域*/} </div > ) } } export default Message
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React , { Component } from 'react' class News extends Component { render ( ) { return ( <ul > <li > news001</li > <li > news002</li > <li > news003</li > </ul > ) } } export default News
在 src/pages/Home/Message 下新建文件夹,名为 Detail,在里面新建名为 index 的 jsx 文件。是 Message 的子页面,用来接收 Message 页面传递过来的的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React , { Component } from 'react' const DetailData = [ { id : '01' , content : '你好111' }, { id : '02' , content : '你好2222' }, { id : '03' , content : '你好33333' }, ] class Detail extends Component { render ( ) { return ( {} <div> <ul > <li > Id:???</li > <li > Title:???</li > <li > Content:???</li > </ul > </div> ) } } export default Detail
在 src/pages/Home/index.jsx 组件中重新配置路由,根据路由显示 Message 和 News 两个子页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import React , { Component } from 'react' import News from '../Home_News/index' import Message from '../Home_Message/index' import MyNavLink from '../../component/MyNavLink/index' import { Route , Switch , Redirect } from 'react-router-dom' export default class Home extends Component { render ( ) { return ( <div > <h2 > Home组件内容</h2 > <div > <ul className ="nav nav-tabs" > <li > {/* 每次点击链接都从最开始的路由开始匹配 点击news后,路径被更改为/news, 然后又开始从App.jsx中匹配路由, 于是又匹配到重定向,跳转到about页面 */} {/* <MyNavLink to ='/news' > News</MyNavLink > */} {/* 需要改为to="/home/news" */} {<MyNavLink to ="/home/news" > News</MyNavLink > } </li > <li > <MyNavLink to ="/home/message" > Message</MyNavLink > </li > </ul > {/* 注册路由 */} <Switch > <Route path ="/home/news" component ={News} > </Route > <Route path ="/home/message" component ={Message} > </Route > <Redirect to ="/home/news" > </Redirect > </Switch > </div > </div > ) } }
向路由组件传递 params 参数 Message 组件向 Detail 子组件传递 params 参数 在 “向路由组件传递 params 参数 代码区域” 中补充如下代码通过 ${messageObj.id} 的方式传参
1 2 3 // src/pages/Home/Message/index.jsx <Link to={`/home/message/detail/${messageObj.id} /${messageObj.title} `}>{messageObj.title}</Link>
在 “声明接收 params 参数 代码区域” 中补充如下代码通过 :id 的方式声明接收
1 2 3 // src/pages/Home/Message/index.jsx <Route path="/home/message/detail/:id/:title" component={Detail} />
Detail 子组件接收父组件 Message 的 params 参数 路由组件传递进来的参数在:this.props.match.params 中,是一个对象 Object 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Detail extends Component { render ( ) { console .log (this .props .match .params ) const { id, title } = this .props .match .params const findResult = DetailData .find ((detailObj ) => { return detailObj.id === id }) return ( <div > <ul > <li > Id:{id}</li > <li > Title:{title}</li > {/* 根据接收的参数id,匹配并显示data数组中的content */} <li > Content:{findResult.content}</li > </ul > </div > ) } }
向路由组件传递 search 参数 Message 组件向 Detail 子组件传递 search 参数 在 “向路由组件传递 search 参数 代码区域” 中补充如下代码通过?id=${messageObj.id}&title=${messageObj.title} 的方式传参
1 2 3 // src/pages/Home/Message/index.jsx <Link to={`/home/message/detail/?id =${messageObj.id} &title=${messageObj.title} `}>{messageObj.title}</Link>
在 “声明接收 search 参数 代码区域” 中补充如下代码 search 参数无需接收,正常注册 即可
1 2 3 4 // src/pages/Home/Message/index.jsx {/* search参数无需接收,正常注册即可 */} <Route path="/home/message/detail" component={Detail} />
Detail 子组件接收父组件 Message 的 search 参数 路由组件传递进来的 search 参数在:this.props.location.search 中。
由于接收到的参数为 key=value$key=value 的形式(urlencoded 编码) ,所以需要使用 querystring 库来拆分 获取需要的数据。
queryString 的用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 1、导入:querystring是react自带的库,无需下载 import qs from 'querystring' // 2、querystring用法 // object => urlencoded(即xxx=xxx&xxx=xxx的格式) let obj = {name: 'tom' ,age: 18}console.log(qs.stringify(obj)); // 输出一段字符串:name=tom&age=18 // urlencoded => object let str = 'carname=大众&price=19977' console.log(qs.parse(str)); // 输出一个对象:{carname: '大众' ,price: '19977' }
在 Detail 组件中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React , { Component } from 'react' import qs from 'querystring' const DetailData = [ { id : '01' , content : '你好111' }, { id : '02' , content : '你好2222' }, { id : '03' , content : '你好33333' }, ] class Detail extends Component { render ( ) { console .log (this .props ) const { search } = this .props .location const { id, title } = qs.parse (search.slice (1 )) const findResult = DetailData .find ((detailObj ) => { return detailObj.id === id }) return ( <div > <ul > <li > Id:{id}</li > <li > Title:{title}</li > {/* 根据接收的参数id,匹配并显示data数组中的content */} <li > Content:{findResult.content}</li > </ul > </div > ) } } export default Detail
向路由组件传递 state 参数 Message 组件向 Detail 子组件传递 state 参数 在 “向路由组件传递 state 参数 代码区域” 中补充如下代码通过 state: {id: messageObj.id, title: messageObj.title} 的方式传参
1 2 3 // src/pages/Home/Message/index.jsx <Link to={{ pathname: '/home/message/detail' , state: { id : messageObj.id , title: messageObj.title } }}>{messageObj.title}</Link>
在 “声明接收 state 参数 代码区域” 中补充如下代码 state 参数无需接收,正常注册 即可
1 2 3 4 // src/pages/Home/Message/index.jsx {/* state参数无需接收,正常注册即可 */} <Route path="/home/message/detail" component={Detail} />
Detail 子组件接收父组件 Message 的 state 参数 state 参数在地址栏没有显示数据,刷新也能保留住参数, 路由组件传递进来的 state 参数在:this.props.location.state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Detail extends Component { render ( ) { const { id, title } = this .props .location .state || {} const findResult = DetailData .find ((detailObj ) => { return detailObj.id === id }) || {} return ( <div > <ul > <li > Id:{id}</li > <li > Title:{title}</li > {/* 根据接收的参数id,匹配并显示data数组中的content */} <li > Content:{findResult.content}</li > </ul > </div > ) } }
路由的 push 和 replace
路由默认使用 push 模式 ,在 history 堆栈添加 一个新条目。
在 路由中使用 replace 或者 replace={true},替换 在 history 堆栈中的当前条目。
1 <MyNavLink replace to='/about' >About</MyNavLink>
编程式路由导航 借助 this.props.history 对象上的 Api 操作路由的跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go (前进或后退的步数)
withRouter
withRouter 可以将一个非路由组件包裹为路由组件,使这个非路由组件也能访问到当前路由的 match, location, history 对象,解决了在一般组件中使用路由组件 API 的问题。
withRouter 的返回值是一个新组件