如今搭建项目通常都是使用脚本手架,其次项目完成搭建以后一些基础的配置信息以及配置如何生效的,对于普通开发人员来讲不太接触的到,相似react-router的配置,长时间不配置的容易健忘html
因此写一篇文章记录下react-router的各类功能使用,主要是根据reacttraning网站的菜单目录来开发的各个demo,以便于全面的理解react-router使用的知识点前端
文章开始以前,先留几个问题,能够边看demo边理解:react
全部的DEMO我集中放在了一个文件夹下,戳DEMO地址查看git
DEMO地址github
import { BrowserRouter, Route, Link, Switch } from 'react-router-dom'
<BrowserRouter>
<ul style={{listStyle: 'none'}}>
<li>
<Link to="/dashboard">dashboard</Link>
</li>
</ul>
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/">
<Dashboard />
</Route>
</Switch>
</BrowserRouter>
复制代码
简单的react-router-demo就搭建完成了。那么有几个问题?web
下面来解答下这些疑问redux
问题场景:bash
咱们通常都须要定义一个根路径的路由,可是根路径的路由若是只是这样写的话,会致使输入路由 /dashboard 的时候 渲染了两个Dashboardreact-router
⚠️⚠️⚠️ Switch的做用就来了,Switch的做用是只渲染第一个匹配到的路由的项,解决了以上重复渲染的问题dom
那其实还有一种解决方案,放在下面【根路径的配置须要注意什么?】去解释
Route下面有个exact属性,exact属性为true时,表示精确匹配到/才会进根路径的Component,否则/dashboard路由也会同时匹配到/路由
<Route path="/" exact>
复制代码
不要配置path,当找不到路由配置时就会走这个
<Route>
<div>404</div>
</Route>
复制代码
这个涉及到同构的概念,客户端和服务端的结构不同
App:能够理解这是个Route集合,服务端和客户端通用
服务端: ReactServerDom + StaticRouter + App
客户端:ReactDom + BrowserRouter + App
涉及react-router使用的源码实现以下:
import ReactServerDom from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from '../client/App'
const markup = ReactServerDom.renderToString(
<StaticRouter location={ctx.req.url}>
<App />
</StaticRouter>
)
复制代码
import React from 'react'
import ReactDom from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDom.render(<BrowserRouter>
<App />
</BrowserRouter>, document.getElementById('root'))
复制代码
import React from 'react';
import { Switch, Route, Link } from 'react-router-dom'
import Dashboard from './router/dashboard'
import Home from './router/home/index'
function App() {
return (
<React.Fragment>
<div style={{width: 200, float: 'left', listStyle: 'none'}}>
<h3>菜单</h3>
<ul style={{listStyle: 'none'}}>
<li>
<Link to="/dashboard">dashboard</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
</ul>
</div>
<div style={{paddingLeft: 200}}>
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/home">
<Home />
</Route>
<Route path="/">
<Dashboard />
</Route>
</Switch>
</div>
</React.Fragment>
);
}
export default App;
复制代码
koa-nunjucks-2 html被渲染成了字符串
解决方案 autoescape: false
同构作完以后发现路由切换仍是发起了请求,而且是404,如何解决?
这实际上是由于客户端的同构没有生效,检查客户端代码,保证注入的client.js可以正常运行,路由切换就不会再发起请求了
同构实现正确,页面出现以后,其实页面的控制权已经交到前端js即BrowserRouter的手中,因此后续的history切换跟服务端没有关系了
路由代码拆分有三种方式:
import loadable from '@loadable/component'
const Home = loadable(() => import('./router/home'))
const Dashboard = loadable(() => import('./router/dashboard'))
复制代码
import loadable from 'react-loadable'
const Loading = () => <div>loading</div>
const Dashboard = loadable({
loader: () => import('./router/dashboard'),
loading: Loading,
});
const Home = loadable({
loader: () => import('./router/home'),
loading: Loading,
});
复制代码
import { Switch, Route, Link } from 'react-router-dom'
const Home = lazy(() => import('./router/home'))
const Dashboard = lazy(() => import('./router/dashboard'))
复制代码
实现一个Suspense.js和lazy.js,功能相似react原生的suspense,lazy
使用以下
<!--注意:这是伪代码,要执行的话参考demo地址,或者本身修改下️-->
import Suspense from './suspense'
import lazy from './lazy'
const Dashboard = lazy(() => import('./router/dashboard'))
const Home = lazy(() => import('./router/home'))
<Suspense fallback={<div>loading</div>}>
<div style={{paddingLeft: 200}}>
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
</Switch>
</div>
</Suspense>
复制代码
两个js的实现以下
Suspense.js
import React from 'react'
import PropTypes from 'prop-types'
export default class Suspense extends React.Component{
getChildContext(){
return {
fallback: this.props.fallback || <div>loading...</div>
}
}
render(){
return this.props.children
}
}
Suspense.childContextTypes = {
fallback: PropTypes.object
}
复制代码
lazy.js
import React from 'react'
import PropTypes from 'prop-types'
export default load => {
class Lazy extends React.Component{
state = { Comp: null }
componentDidMount(){
// 加个定时器,看下fallback的效果
setTimeout(() => {
load().then(module => {
this.setState({
Comp: module.default
})
})
}, 3000)
// load().then(module => {
// this.setState({
// Comp: module.default
// })
// })
}
render(){
let { Comp } = this.state
return Comp ? <Comp /> : this.context.fallback
}
}
Lazy.contextTypes = {
fallback: PropTypes.object
}
return Lazy
}
复制代码
⚠️这里有个注意点是:Suspense和lazy加载的组件非直接父子组件关系,因此这里采用context实现
import { Prompt } from 'react-router-dom'
<Prompt
when={isBlocking}
message={location => `Are you sure you want to go to ${location.pathname}`
} />
复制代码
其实就是利用了Prompt组件,when为true时拦截,为false时放行
这个DEMO解决的问题
有些时候在一个路由下操做,而后滚动到下面,致使顶部内容看不到了,此时切换菜单,但是页面没有自动滚动到顶部
使用withRouter把history,location,match塞入ScrollToTop组件的props中,当路由切换以后进入ScrollToTop的componentDidUpdate事件,在这个事件中window.scrollTo(0, 0)
ScrollToTop组件定义
class ScrollToTop extends React.Component{
componentWillUpdate(prevProps){
if(this.props.location.pathname != prevProps.location.pathname){
window.scrollTo(0, 0)
}
}
render(){
return this.props.children
}
}
复制代码
ScrollToTop组件的使用
<Router>
<ScrollToTopComp>
<!--...其余代码-->
</ScrollToTopComp>
</Router>
复制代码
改进方案是使用hooks的useEffect
ScrollToTop组件定义
import { useEffect } from 'react'
function ScrollToTop({children, location: { pathname }}){
useEffect(() => {
window.scrollTo(0, 0)
}, [pathname])
return children
}
const ScrollToTopComp = withRouter(ScrollToTop)
复制代码
ScrollToTop组件的使用和第一种方案同样
第三种方案是在每一个路由页面加一个ScrollToTop组件,在ScrollToTop的componentDidMount事件中加window.scrollTo(0, 0)
这个方案就不具体演示代码了
⚠️为何会有整合的问题❓❓❓
-> react-router:withRouter(Home)
-> react-redux:connect(state => {
return { name: state.name }
})(Home)
复制代码
两个都须要再次封装Home,那么怎么办❓❓❓
connect(state => {
return { name: state.name }
})(withRouter(Home))
复制代码
<Route path="/home" component={Home} />
connect(state => {
return { name: state.name }
})(Home)
复制代码
import { useLocation, useHistory, useParams, useRouteMatch } from 'react-router'
const location = useLocation()
const history = useHistory()
const params = useParams()
const match = useRouteMatch()
就能够获取到各个对象了
复制代码
ok,到这里差很少把reacttraining的功能都用demo实现了一遍,可是没有具体解释例如Route的属性的含义等,这篇demo文章就看成是对react-router的复习