如今搭建項目通常都是使用腳本手架,其次項目完成搭建以後一些基礎的配置信息以及配置如何生效的,對於普通開發人員來講不太接觸的到,相似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的複習