十四、react路由
React Router官网:https://reactrouter.com/
安装
1 npm i -S react-router-dom
1、相关组件
- Router组件:包裹整个应用(单个具体的组件/根组件),一个React应用只需要使用一次
- Router类型: HashRouter和BrowserRouter
- HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
- BrowserRouter:使用H5的history API实现(localhost3000/first)
- HashRouter和BrowserRouter的不同点:
- HashRouter使用URL的哈希值实现,BrowserRouter使用H5的history API实现
- HashRouter路径中有#,BrowserRouter没有#
- HashRouter刷新后会导致路由state参数的丢失,BrowserRouter不会
- Router类型: HashRouter和BrowserRouter
- Link/NavLink组件:用于指定导航链接(a标签),就是做声明式导航的
- 最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
- NavLink可以实现路由链接的高亮显示,通过属性activeClassName指定样式名
- Route组件:指定路由展示组件相关信息(组件渲染)
- path属性:路由规则,这里需要跟Link组件里面to属性的值一致
- component属性:展示的组件
注意:Link
和Route
组件必须被Router
组件给包裹,否则报错。
2、封装NavLink组件
1、NavLink标签是一个高亮显示的导航标签
2、NavLink标签体内容是一个特殊的标签属性,通过this.props.children属性可以获取标签体内容(即Home或About)
NavLink封装
1 import React,{Component} from 'react' 2 import {NavLink} from 'react-router-dom' 3 4 export default class MyNavLink extends Component{ 5 render(){ 6 return( 7 <NavLink activeClassName="link-name" className="link-item" {...this.props}/> 8 ) 9 } 10 }
使用
1 <MyNavLink to="/home">Home</MyNavLink> 2 <MyNavLink to="/about">About</MyNavLink>
3、Switch组件
Switch组件,让其去包裹路由的Route
组件(Switch组件保证只渲染其中一个子路由)
1 <Switch> 2 {/*Switch只渲染第一个匹配的组件*/} 3 <Route path="/home" component={Home}></Route> 4 <Route path="/home" component={Test}></Route> 5 <Route path="/about" component={About}></Route> 6 </Switch>
1、声明式导航
在src/index.js
入口文件中定义一个路由模式(Router组件包裹根组件)
1 import React from "react"; 2 import ReactDOM from "react-dom"; 3 4 // 设置路由模式 5 import {HashRouter as Router} from 'react-router-dom' 6 7 // 定义 provider 8 import { Provider } from "react-redux"; 9 import store from "./Store/index"; 10 11 import App from "./App"; 12 13 ReactDOM.render( 14 <Provider store={store}> 15 // 使用Router包裹根组件 16 <Router> 17 <App></App> 18 </Router> 19 </Provider>, 20 document.getElementById("root") 21 );
在根组件src/App.jsx
中引入路由相关组件(也可以在具体的某个组件中使用Router组件)
1 import React, { Component } from "react"; 2 import { HashRouter as Router, Route, Link } from "react-router-dom"; 3 4 import Cmp10 from "./Components/Cmp10"; 5 import Cmp11 from "./Components/Cmp11"; 6 7 class App extends Component { 8 render() { 9 return ( 10 <Router> 11 <div> 12 <h1>导航区域</h1> 13 <div> 14 <ul> 15 <li> 16 <Link to="/home">首页</Link> 17 </li> 18 <li> 19 <Link to="/news">新闻</Link> 20 </li> 21 </ul> 22 </div> 23 </div> 24 <Route path="/home" component={Cmp10}></Route> 25 <Route path="/news" component={Cmp11}></Route> 26 </Router> 27 ); 28 } 29 } 30 export default App;
需要注意:
刨除样式的影响,Route
组件在HTML代码中的位置决定了渲染后其在页面中显示的位置。如果Route
放在最后,则其显示的时候也在最后;若其放在渲染内容的最前面,相应的显示也会在最开始。
2、编程式导航
路由组件与一般组件有三个重要的不同属性:history、location、match
- react-router-dom中通过history对象中的push/replace/go等方法实现编程式导航功能,这一点与之前的vue路由还是很相似的。
- push:先进后出,可以返回查看历史跳转情况
- replace:替换,不能返回查看历史页面
1 <MyNavLink replace to="/home">Home</MyNavLink>
1 this.props.history.push({ 2 pathname: "/home", 3 search: "from=404", // 表示传递查询字符串 4 state: { // 隐式传参,地址栏不体现 5 username: "admin", 6 }, 7 }); 8 9 this.props.history.go(-1)
不要在根组件中使用编程式导航。
三、路由匹配1、模糊匹配与严格匹配
模糊匹配(默认)
1 {/*MyNavLink中的/about/a/b匹配到Route中的/about"*/} 2 <MyNavLink to="/home">Home</MyNavLink> 3 <MyNavLink to="/about/a/b">About</MyNavLink> 4 5 <Switch> 6 <Route path="/home" component={Home}></Route> 7 <Route path="/home" component={Test}></Route> 8 <Route path="/about" component={About}></Route> 9 </Switch>
严格匹配:exact属性
1 <MyNavLink to="/home">Home</MyNavLink> 2 <MyNavLink to="/about/a/b">About</MyNavLink> 3 4 <Switch> 5 <Route exact path="/home" component={Home}></Route> 6 <Route exact path="/home" component={Test}></Route> 7 <Route exact path="/about" component={About}></Route> 8 </Switch>
2、重定向路由
React的重定向路由有以下两种写法:
方式一:推荐
1 import { Redirect } from "react-router-dom" 2 3 <Redirect from="/from" to="/to"></Redirect> 4 <Route path="/to" component={xxxx}></Route>
方式二 :不推荐
1 import { Route, Redirect } from "react-router-dom" 2 3 <Route path="/from"> 4 <Cmp></Cmp> 5 <Redirect to="/to" /> 6 </Route>
3、404路由
项目中少不了404页面的配置,在React里面配置404页面需要注意:
1 import NotFound from "./Components/404"; 2 3 <Route> 4 <NotFound></NotFound> 5 </Route> 6 // 或 7 <Route component={NotFound}></Route>
- 需要用到Switch组件,让其去包裹路由的
Route
组件(Switch组件保证只渲染其中一个子路由) - 在404路由的位置,不需要给定具体的路由匹配规则,不给
path
表示匹配*
,即所有的路由都会匹配,因此用404路由一定要加Switch
匹配一个路由。 - 并不会因为当前是404路由/重定向路由而改变状态码,因为当前写的是前端的内容,状态码是后端提供的,只有等后期上线以后才能有状态码。
1 <div> 2 <Link to="/home">家</Link>   3 <Link to="/news">新闻</Link>  4 <Link to="/about">关于</Link>  5 <Redirect from="/" to="/home"></Redirect> 6 <Switch> 7 <Route path="/home" component={Cmp11}></Route> 8 <Route path="/news" component={Cmp12}></Route> 9 <Route path="/about" component={Cmp13}></Route> 10 <Route component={NotFound}></Route> 11 </Switch> 12 </div>
在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。
例如,路由规则如下
1 admin/user 2 admin/goods
它们路由前缀的admin是相同的,不同的只是后面一部份。
实现方式
- 先定义个组件,用于负责匹配同一前缀的路由,将匹配到的路由指向到具体的模块
1 <Route path="/admin" component={Admin}></Route>
- 创建模块路由组件负责指定各个路由的去向
1 render() { 2 // 获取前缀,供后续地址做路由拼接 3 let prefix = this.props.match.path; 4 return ( 5 <div> 6 <h1>欢迎使用后台管理程序</h1> 7 <Route path={`${prefix}/user`} component={User}></Route> 8 <Route path={`${prefix}/goods`} component={Goods}></Route> 9 </div> 10 ); 11 }
优化性能,让路由按需加载
- 引入react上的
Suspense
,包裹注册的路由组件 Suspense
用于展示路由未加载出来展示的组件
1 import React, {Component, lazy, Suspense} from 'react' 2 import {NavLink, Route} from 'react-router-dom' 3 4 // Loading用于懒加载组件未加载出来的展示 5 import Loading from './Loading' 6 7 // import Home from './Home' 8 // import News from './News' 9 10 const Home = lazy(() => {import('./Home')}) 11 const News = lazy(() => {import('./News')}) 12 13 export default class Demo extends Component{ 14 <div> 15 <h1>导航区域</h1> 16 <div> 17 <ul> 18 <li> 19 <Link to="/home">首页</Link> 20 </li> 21 <li> 22 <Link to="/news">新闻</Link> 23 </li> 24 </ul> 25 </div> 26 <Suspense fallback={<Loading/>}> 27 <Route path="/home" component={Home}></Route> 28 <Route path="/news" component={News}></Route> 29 </Suspense> 30 </div> 31 }
路由参数:在Route定义渲染组件时给定动态绑定的参数。
React路由传参方式有三种:
1、动态路由参数(param)
以/film/detail/:id
形式传递的数据
1 <Link to={`/film/detail/${detailObj.id}`}></Link>
在目标页面路由中传递
1 <Router path="/film/detail/:id" component={Detail}></Router>
在落地组件中通过this.props.match.params
得到
2、查询字符串(search,也可叫query)
- 通过地址栏中的
?key=value&key=value
传递 - 在落地组件中通过
this.props.location.search
得到
3、隐式传参(state),通过地址栏是观察不到的
- 不适合写在声明式导航中,写在编程式导航中更加合适
1 <Link to={{pathname:`/film/detail`,state={id:detailObj.id,title:detailObj.title}}}></Link>
- 埋点数据
- 在落地组件中通过
this.props.location.state
得到
接收示例:
1 constructor(props){ 2 super(props) 3 this.state = { 4 // 接收动态路由参数 5 news_id: this.props.match.params.id, 6 // 接收查询字符串并处理 7 query: querystring.parse(this.props.location.search.slice(1)), 8 // 接收state 9 state: this.props.location.state 10 }; 11 }
1、component属性(组件对象/函数)
1 <Route path="/home" component={Home} />
1 <Route path="/home" component={() => <Home />} />
2、render属性(函数)
1 <Route path="/home" render={props => <Home />} />
3、children属性(组件/函数)
1 <Route path="/about" children={props => { 2 if(props.match){ 3 return <div>children渲染</div> 4 } 5 }} />
1 <Route path="/about" children={<About />} />
注意
- component可以使用组件类渲染或内联方式渲染,render只能使用函数,children使用函数或直接使用组件
- 当children的值是一个函数时,无论当前地址和path路径匹不匹配,都将会执行children对应的函数,当children的值为一个组件时,当前地址和path不匹配时,路由组件不渲染
- children函数方式渲染,会在形参中接受到一个对象,对象中match属性如果当前地址匹配成功返回对象,否则null
- children函数方式渲染,在自定义导航组件的上面会非常好用
作用:把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上
- 默认情况下,必须是经过路由匹配渲染的组件才存在this.props才拥有路由参数,才能使用编程式导航的写法,才能执行this.props.history.push('/uri')跳转到对应路由的页面
- 然而不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
1 // 引入withRouter 2 import { withRouter} from 'react-router-dom' 3 4 // 执行一下withRouter 5 export default withRouter(Cmp)
该高阶组件是路由包自带的东西,因此只需要引入+使用就可以了,不需要自己定义。
九、自定义导航组件在项目中往往不是所有的声明式导航都是需要a标签
完成,有时候可能需要别的标签
此时如果在需要的地方去写编程式导航就会有代码重复可能性,就在对于公共代码进行提取
思路:
- 定义一个普通组件可以是类组件也可以是函数式组件
- 父组件能向子组件传值props
- 不管路由规则是否匹配都要显示组件Route,children渲染方式(函数式)
- 注意点:react中组件是大写字母开头,html标签也是组件
声明式导航参考
1 import React, { Component, Fragment } from "react"; 2 import { Route } from "react-router-dom" 3 // 引入自定义导航组件 4 import MyLink from "./Components/MyLink" 5 import Cmp1 from "./Components/Cmp1"; 6 import Cmp2 from "./Components/Cmp2"; 7 8 class App extends Component { 9 render() { 10 return ( 11 <Fragment> 12 <MyLink tag="h1" to="/cmp1"> 13 去1 14 </MyLink> 15 <MyLink tag="h1" to="/cmp2"> 16 去2 17 </MyLink> 18 <Route path="/cmp1" component={Cmp1}></Route> 19 <Route path="/cmp2" component={Cmp2}></Route> 20 </Fragment> 21 ); 22 } 23 } 24 export default App;
自定义导航组件参考
1 import React, { Component, Fragment } from "react"; 2 import { withRouter, Route } from "react-router-dom"; 3 4 class MyLink extends Component { 5 // 点击跳转动作 6 goUrl = () => { 7 this.props.history.push(this.props.to); 8 }; 9 render() { 10 // 获取参数 11 var Tag = this.props.tag ? this.props.tag : "a"; 12 return ( 13 <Fragment> 14 <Route 15 path={this.props.to} 16 children={({ match }) => { 17 if (match) { 18 // 匹配 19 return ( 20 <Tag 21 onClick={this.goUrl} 22 style={{ color: "red" }} 23 > 24 {this.props.children} 25 </Tag> 26 ); 27 } else { 28 // 不匹配 29 return ( 30 <Tag onClick={this.goUrl}> 31 {this.props.children} 32 </Tag> 33 ); 34 } 35 }}> 36 </Route> 37 </Fragment> 38 ); 39 } 40 } 41 42 export default withRouter(MyLink);
css样式引入:public文件下自定义样式引入
- 不要写相对路径,例如
./css/index.css
写成/css/index.css
- 路径写成
href="%PUBLIC_URL%/css/index.css"
- 导航方式:BrowserRouter改成HashRouter