路由
1.介绍
- Hash模式 :兼容性好,但是带#,上线部署无需考虑服务器配置问题
- History模式:兼容性差,但是地址栏无#,但是上线时服务器一定要部署
2.路由使用
1. 安装
npm i -S react-router-dom@5
2. 相关组件:
-
路由模式组件:包裹整个应用,一个react只需使用一次
HashRouter:hash BrowserRouter:history
-
导航组件:
Link ==><a></a>Link:不会有激活样式 NavLink:如果地址栏中的地址和to属性相匹配,则会有激活样式
-
路由规则定义组件:指定路由规则和对应匹配成功后要渲染的组件
Route:
path属性:路由路径,在地址栏中访问的地址
component属性:和规则匹配成功后渲染的组件
//App.jsx
// 类组件
import React, { Component } from 'react'
// 1. 定义路由规则
import { Route } from 'react-router-dom'
// 2. 引入需要渲染的组件
import Home from './views/Home'
import About from './views/About'
class App extends Component {
render() {
return (
<div>
<h3>App应用</h3>
<hr />
{/*
3. 定义路由规则 (写在jsx语法中)
path 字符串 路由匹配的路径
component 路由匹配成功后要渲染的组件 默认用类
*/}
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</div>
)
}
}
export default App
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
// BrowserRouter history 注:此方案上线时一定要设置服务器配置
// 对于路由模式组件,一般在使用时,定义一个别名,方便后续的路由模式的切换
import {BrowserRouter as Router} from 'react-router-dom'
// import { HashRouter as Router } from 'react-router-dom'
ReactDOM.render(
<Router>
<App/>
</Router>,
document.getElementById('root'))
import React, { Component } from 'react'
import { Route, Link, NavLink } from 'react-router-dom'
import Home from './views/Home'
import About from './views/About'
export default class App extends Component {
render() {
return (
<div>
<h2>导航区域</h2>
<hr />
<ul>
<li>
<Link to="/home">首页</Link>
</li>
<li>
<NavLink to="/about">关于我们</NavLink>
</li>
</ul>
<hr />
<Route path="/home" component={Home}/>
<Route path="/about" component={About} />
</div>
)
}
}
3.声明式导航
-
使用Link或者NavLink组件完成声明式导航定义
-
Link和NavLink区别
Link组件不会根据路由的变化而添加或修改编译后html标签中的属性
NavLink会根据路由的变化而自动修改编译后html标签中的属性 它有激活样式 当前地址栏中的地址和to属性一样时,则默认会有一个class名称为active,当然你可以修改
activeClassName 如果使用此属性,则自定义激活样式名称,一般情况下不建议去修改
import React, { Component } from 'react'
import { Route, Link, NavLink } from 'react-router-dom'
import Home from './views/Home'
import News from './views/News'
export default class App extends Component {
render() {
return (
<div>
<h2>导航区域</h2>
<hr />
<ul>
<li>
<Link to="/home">首页</Link>
</li>
<li>
<NavLink to="/news">新闻</NavLink>
</li>
</ul>
<hr />
<Route path="/home" component={Home}/>
<Route path="/news" component={News} />
</div>
)
}
}
4. 严格模式匹配
- 定义路由规则默认情况下,react-route-dom它的path匹配是模糊匹配,而且会一直向下匹配下去,直到没有规则停止
- 严格匹配:
exact = {true} <Switch></Switch>:只匹配一次,后面不再匹配,使用时将范围较大的规则写在下面,精准的写在上面- 404页面处理,此时一定将 / 弄成严格模式,否则不会到此规则匹配
// 类组件
import React, { Component } from 'react'
// Route 定义路由规则所用
// Link/NavLink 声明式导航 NavLink它有激活样式 统一编译出来的html都为a标签
// Switch 它只会匹配成功一次,后续就不会继续匹配
import { Route, NavLink, Switch } from 'react-router-dom'
// 引入需要渲染的组件
import Home from './views/Home'
import About from './views/About'
import NotFound from './views/NotFound'
class App extends Component {
render() {
return (
<div>
<ul>
<li>
{/* 它也有一个严格模式 */}
<NavLink exact={true} to="/">
home
</NavLink>
</li>
<li>
<NavLink to="/about">about</NavLink>
</li>
</ul>
<hr />
{/*
定义路由规则 默认情况下,react-route-dom它的path匹配是模糊匹配,而且会一直向下匹配下去,直到没有规则停止
严格匹配 exact={true}
*/}
{/* <Route exact={true} path="/" component={Home} />
<Route path="/about" component={About} /> */}
{/* Switch使用后,要把范围较大的规则写在下面,精准的写在上面 */}
{/* <Switch>
<Route path="/about" component={About} />
<Route path="/" component={Home} />
</Switch> */}
<Switch>
<Route path="/about" component={About} />
<Route exact={true} path="/" component={Home} />
{/* 404页面处理,此时一定要把 / 弄成严格模式,否则不会到此规则匹配 */}
<Route path="*" component={NotFound} />
</Switch>
</div>
)
}
}
export default App
5. 编程式导航 (先声明再使用)
- 编程式导航通过
this.props.history对象来完成的,此对象有一个要求- 在react路由中this.props要想得到路由中的对象,则默认必须要通过路由规则匹配渲染的组件才能有此对象 - 必须是直接渲染的组件
- 只有当前组件是路由直接匹配成功后渲染的组件才有此对象
- 定时器写在生命周期李,挂在之后,
componentDidMount,用一个成员属性来保存当前计时器,componentWillUnmount里面清理计时器
//App.jsx
// 类组件
import React, { Component } from 'react'
// Route 定义路由规则所用
// Link/NavLink 声明式导航 NavLink它有激活样式 统一编译出来的html都为a标签
// Switch 它只会匹配成功一次,后续就不会继续匹配
import { Route, NavLink, Switch } from 'react-router-dom'
// 引入需要渲染的组件
import Home from './views/Home'
import About from './views/About'
import NotFound from './views/NotFound'
class App extends Component {
render() {
return (
<div>
<ul>
<li>
{/* 它也有一个严格模式 */}
<NavLink exact={true} to="/">
home
</NavLink>
</li>
<li>
<NavLink to="/about">about</NavLink>
</li>
</ul>
<hr />
<Switch>
{/* About组件它是由路由规则匹配成功后直接渲染的,所以它就会有this.props.history对象 */}
<Route path="/about" component={About} />
<Route exact={true} path="/" component={Home} />
<Route path="*" component={NotFound} />
</Switch>
</div>
)
}
}
export default App
class Home extends Component {
render() {
return (
<div>
<h3>首页页面</h3>
<br />
{/* <Btn history={this.props.history} /> */}
{/* 把父组件中的props属性全部传给子组件 */}
<Btn {...this.props} />
</div>
)
}
}
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
// 404页面,在3秒后,自动跳转到首页去,此时就需要js来完成
// 通过js来完成的导航切换,编程式导航
// 编程式导航通过this.props.history对象来完成的,此对象有一个要求
// 只有当前组件是路由直接匹配成功后渲染的组件才有此对象
class NotFound extends Component {
// 挂载完成后,弄一个定时器,3秒后到首页
componentDidMount() {
// 用一个成员属性来保存当前的计数器
this.timer = setTimeout(() => {
this.props.history.push('/')
}, 3000)
}
// 销毁进行清理工作
componentWillUnmount() {
console.log('走了')
this.timer && clearTimeout(this.timer)
}
render() {
// 通过this.props得到当前组件中的路由对象
// console.log(this.props)
// 其中this.props.history方法就是编程式导航所用到的对象
// this.props.history(string|{pathname:string,search:string,state:object})
return (
<div>
<h3>页面丢失</h3>
<br />
<Link to="/">去首页</Link>
<br />
<button
onClick={() => {
this.props.history.push('/')
}}
>
去首页
</button>
</div>
)
}
}
export default NotFound
6. 页面路由传参
-
动态路由传参,必须先定义规则在使用,一般用于详情页
-
React传参方式有三种:
-
动态路由传参(param)
以
"/detail/:id"形式传递数据在落地组件中通过
this.match.params得到(对象) -
查询字符串(search)
通过地址栏中
?key = value&key = value传递在落地组件中通过
this.props.location.search得到(字符串),需要自行来解析对象 -
隐式传参(state),通过地址栏是观察不到的
通过路由对象中的state属性进行数据传递
在落地组件中通过
this.props.location.state得到(对象),在hash模式中此方案可能会刷新丢失数据,,在history模式下没有任何问题
-
import React, { Component } from 'react'
import Info from './ui/Info'
import { searchToObject } from '../../utils/tools'
// 获取动态路由参数数据 this.props.match.params 对象来完成获取 对象
// 获取search字符串数据 this.props.location.search 字符串,需要自行来解析为对象
// 获取state数据 this.props.location.state 对象 在hash模式中此方案可能会有刷新丢失数据的情况,在history模式下没有任何问题
class Detail extends Component {
render() {
console.log('动态路由参数', this.props.match.params)
console.log('search字符串', this.props.location.search)
console.log('state数据', this.props.location.state)
// 可以使用es6提供一个URL对象来完成对于search字符串的解析,从而方便调用
// const search = new URLSearchParams(this.props.location.search)
// console.log('得到search字符串中的name字段的值', search.get('name'),search.get('age'))
// item它是一个数组件,数组的元素1是key,元素2是value
// for(let item of search.entries()){
// 解构去写
// for (let [key, value] of search.entries()) {
// console.log(key, value)
// }
console.log('search字符串', searchToObject(this.props.location.search))
return (
<div>
<div>动态路由参数,必须先定义规则,才能使用,一般用于详情页面中使用 -- 它的url地址较为美观</div>
<hr />
<h3>动态中路由参数数据:{this.props.match.params.id}</h3>
<Info {...this.props} />
</div>
)
}
}
export default Detail
import React, { Component } from 'react'
import Info from './ui/Info'
import { searchToObject } from '../../utils/tools'
export default class Detail extends Component {
render() {
return (
<div>
<h3>动态中路由参数数据 {this.props.match.params.id} </h3>
<Info {...this.props} />
</div>
)
}
}
7. 嵌套路由
- 路由前缀相同可以嵌套起来
- 子路由上一定写上父路由的地址
注意:如果当前路由有子路由,一定不可以给父路由添加严格匹配
-
通过
this.props.match.path得到当前子路由的父路由路径,用此完成路由拼接const prefixUrl = this.props.match.path<Switch> {/* 子路由中一定要写上父路由的地址 */} <Route path={`${prefixUrl}/dashboard`} component={Dashboard} /> <Route path={`${prefixUrl}/user`} component={User} /> </Switch>
8. 三种路由渲染方式
-
component(类渲染或者函数)
-
component类渲染方式:
<Route path="/rd" component = {Render} />1.它会自动的把路由对象映射到渲染组件的props属性中
2.类在路由规则匹配的环境中不能写业务判断,如当前页面只有登录才能访问,它不可写判断
3.它不会重复去创建渲染组件
-
函数渲染方式:一定返回一个jsx对象
<Route path="/rd" component={router => { // 在路由规则匹配成功后,还可以进行别的业务判断 if ('?username=admin' === router.location.search) { return <Render {...router} /> } else { // return <div>你没有权限</div> // 如果你没有权限则跳转到登录页 return <Redirect to="/login" /> } }}1.函数的方式,则需要你手动的把router对象传入到渲染组件的props属性中
2.函数的方式,它可以在匹配规则后,进行对应的业务判断,如果满足则渲染
3.当前路由宿主中有数据变化后,它则会销毁之前的组件再重新创建一个新的组件,性能较差
-
-
render(函数):它集合了component类和函数组件中的优点 ,需要手动的传递路由对象给渲染的组件的props属性中
<Route path="/rd" render={router => { // 在路由规则匹配成功后,还可以进行别的业务判断 if ('?username=admin' === router.location.search) { return <Render {...router} /> } else { return <div>你没有权限</div> } }} -
children(函数或者组件)
-
jsx方式,精确匹配方式,当前path必须和地址栏地址一样时才来渲染
children的jsx方式,默认情况下它没有返回给所匹配的渲染组件中this.props中注入路由对象,所以一般不用
-
回调函数方式:全匹配,它渲染不关心当前的path和地址栏规则是否一致,它都会渲染出来
如果当前地址栏中的地址和path路径要是一致时,则router对象中的match属性则为对象,如果不匹配则为null
<Route path="/rd" children={router => { console.log(router.match) return <Render {...router} /> }}<Route path="/rd" children={router => { console.log(router); if (router.match) { return <Render {...router} /> } return <div>没有找到</div> }} />
-
9.withRouter高阶组件
- 作用:把不是通过路由直接渲染出来的组件,将react-router的history、location、match三个对象传入props对象上
- 默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行
this.props.history.push('/uri')跳转到对应路由的页面,然而不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
import React, { Component } from 'react'
import { Route, withRouter } from 'react-router-dom'
import Render from './views/Render'
import Login from './views/Login'
class App extends Component {
render() {
console.log('App', this.props)
return (
<div>
<Route path="/login" component={Login} />
{/* <Route path="/rd" component={Render} /> */}
{/* 让宿主组件有路由对象,可以向下传下去 */}
<Route path="/rd" children={<Render {...this.props} />} />
</div>
)
}
}
// 使用高阶组件进行了增强 当前此组件就有了路由对象
export default withRouter(App)
在函数组件中使用hooks函数来完成数据的获取
import React from 'react'
// useLocation:得到location对象 search字符串 state
// useHistory:得到history对象 编程式导航
// useParams: 得到params数据 动态路由参数
import { useLocation, useHistory, useParams } from 'react-router-dom'
const Form = props => {
// console.log('login', props)
// console.log(props.location.search)
const location = useLocation()
const history = useHistory()
const params = useParams()
console.log(location, history, params)
return (
<div>
<button
onClick={() => {
history.push('/rd')
}}
>
去rd页面
</button>
</div>
)
}
export default Form
10. 自定义导航组件
因为有时候,在项目中给非a标签添加导航,默认Link或NavLink它编译后都只能是a标签,这样就不能满足项目需要,此时就需要自定义导航组件,来完成指定编译后生成的html标签
-
定义组件:
通过props接受数据
给props设置类型限制
n npm i -S prop-types给props进行默认设置
import React from 'react'
// 针对于props进行类型限制
import types from 'prop-types'
import { useHistory, Route } from 'react-router-dom'
import {Container} from './style'
// Mylink它仿 Link组件
// tag进行别名设置,因为在react组件中,首字母必面大写
const Mylink = ({ to, children, tag: Tag }) => {
const history = useHistory()
const goto = () => {
history.push(to)
}
return (
<Container>
<Route
path={to}
children={({ match }) => {
let activeClass = match ? 'active' : ''
return (
<Tag className={activeClass} onClick={goto}>
{children}
</Tag>
)
}}
/>
</Container>
)
}
// 针对于当前的组件对于props类型进行限制
Mylink.propTypes = {
// 属性名:类型限制
// to: types.oneOfType([types.string,types.object])
// to属性必须要传入的属性,类型为字符串
to: types.string.isRequired,
tag: types.string
}
// 设置默认值
Mylink.defaultProps = {
tag: 'a'
}
export default Mylink
// 类组件
import React, { Component } from 'react'
// Route 定义路由规则所用
// Link/NavLink 声明式导航 NavLink它有激活样式 统一编译出来的html都为a标签
// Switch 它只会匹配成功一次,后续就不会继续匹配
// Redirect 重定向
// withRouter 高阶组件,作用:让不是路由匹配的组件拥有路由对象
import { Route, withRouter } from 'react-router-dom'
import Render from './views/Render'
import Login from './views/Login'
// 自定义导航组件
import Mylink from './components/Mylink'
class App extends Component {
render() {
return (
<div>
<Mylink to='/login' tag="h3">登录</Mylink>
<Mylink to='/rd'>渲染</Mylink>
<hr />
<Route path="/login" component={Login} />
<Route path="/rd" component={Render} />
</div>
)
}
}
// 使用高阶组件进行了增强 当前此组件就有了路由对象
export default withRouter(App)
11. 使用装饰器调用高阶组件
-
npm i -D customize-cra@1 react-app-rewired@2 @babel/plugin-proposal-decorators@7 -
////config-overrides // 此文件是运行在nodejs中,所以用commonjs规范来写 // 此文件的修改一定要重启项目 // 此文件的作用,就是对于当前项目的增量配置,类似于vue中的vue.config.js文件作用 const { // 如果原项目中有此配置,则覆盖,如果没有则新增 override, // 让项目支持装饰器 addDecoratorsLegacy, // 设置引用的路径别名,让它像使用vue中的@一样的去使用 addWebpackAlias } = require('customize-cra') const path = require('path') module.exports = override( // 支持装饰器 addDecoratorsLegacy(), // 添加webpack别名 addWebpackAlias({ ['@']: path.resolve('./src') }) ) -
"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-scripts test", "eject": "react-scripts eject" }, -
import React, { Component } from 'react' // 装饰器 es7提出来的,用于装饰类所用,它只能装饰类 // 装饰器它就是一个函数,此函数可以用来对于类来完成装饰 // // target参数就是当前Demo类 // const fn = target => { // // console.log(target) // // 扩展一个成员属性 给类扩展一个成员属性 // target.prototype.name = '李四' // // 成员方法 // target.prototype.run = function () { // console.log('run方法') // } // // 静态方法 通过类来调用 // target.test = function () { // console.log('test方法') // } // } // // 装饰类 // @fn // class Demo {} // const d = new Demo() // // console.log(d) // // 调用成员方法 // d.run() // // 调用静态方法 // Demo.test() // 装饰器,装饰时有写小括号,则需要返回一个函数 // 第1个函数中的参数,则为装饰器中小括号的传入过来的内容 // 返回的函数的参数就是当前的装饰类 // const fn = (age, name) => target => { // // console.log(age, name) // // console.log(target) // target.prototype.name = name // target.prototype.age = age // } // @fn(1, '张三') // class Demo {} // const d = new Demo() // console.log(d) // target当前类的实例对象 // key就是当前的成员属性的名称 // description 关于此成员属性的描述 // const readonly = (target, key, description) => { // // console.log(target,key,description) // // Object.defineProperty // description.writable = false // } // class Demo { // // 通过装饰器来让成员属性只读 // @readonly // name = '张三' // } // const d = new Demo() // d.name = '李四' const fn = (target, key, description) => { // console.log(target,key,description) // 对于原有的方法时行增量添加 // 对象中的原方法进行暂时的保存起来 let oldFn = description.value description.value = function (...arg) { // 调用原方法时,要注意this的指向问题 call/apply // oldFn.apply(this, arg) oldFn.call(this, ...arg) console.log('装饰器中的方法') } } class Demo { name = '赵六' @fn push(arg) { console.log(this) console.log('demo -- push', arg, this.name) } } const d = new Demo() d.push('变异') class App extends Component { render() { return <div>App</div> } } export default App
\