前端路由的原理大體相同:當頁面的URL發生變化時,頁面的顯示結果能夠根據URL的變化而變化,可是頁面不會刷新。javascript
要實現URL變化頁面不刷新有兩種方法:經過hash實現、經過History API實現。html
改變頁面的hash值不會刷新頁面,而hashchange的事件,能夠監聽hash的變化,從而在hash變化時渲染新頁面。前端
History API中pushState、replaceState方法會改變當前頁面url,可是不會伴隨着刷新,可是調用這兩個方法改變頁面url沒有事件能夠監聽。有個history庫加強了history API,採用發佈訂閱模式來對url的變化做出反映。其暴露出一個listen方法來添加訂閱者,經過重寫push、replace方法,使得這兩個方法調用時通知訂閱者,從而在url變化時渲染新頁面。java
import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; export default function App() { return ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/users">Users</Link> </li> </ul> </nav> <Switch> <Route path="/about"> <About /> </Route> <Route path="/users"> <Users /> </Route> <Route path="/"> <Home /> </Route> </Switch> </div> </Router> ); } function Home() { return <h2>Home</h2>; } function About() { return <h2>About</h2>; } function Users() { return <h2>Users</h2>; }
react-router使用的基本結構是:react
<Router>
包裹整個app,主要類型有<BrowserRouter>
和<HashRouter>
,分別對應上面兩種實現方法;首先把location、history對象(加強的)經過react context API注入到子組件中,而後在<Router>
中會調用history.listen
方法監聽location變化,當location變化時採用setState改變location觸發子組件的更新。<Link>
標籤作導航用,點擊時會調用history.push
或history.replace
方法,並改變context中的location。<Switch>
從新渲染,找到匹配的<Route>
渲染。Route
組件根據Swtich
的匹配結果渲染component,並經過React context API將location、history對象注入到子組件。服務端渲染時頁面是靜態的,沒有state,不能經過state改變去觸發子組件更新。在服務端是根據req.url
來渲染頁面的,其基本使用方式以下:git
import http from "http"; import React from "react"; import ReactDOMServer from "react-dom/server"; import { StaticRouter } from "react-router-dom"; import App from "./App.js"; http .createServer((req, res) => { const context = {}; const html = ReactDOMServer.renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); //重定向時觸發 if (context.url) { res.writeHead(context.status, { Location: context.url }); res.end(); } else { res.write(` <!doctype html> <div id="app">${html}</div> `); res.end(); } }) .listen(3000);
在<StaticRouter>
中沒有使用history庫了,而是建立了一個簡單的history對象,其對應history庫建立的history對象,可是其中的方法大多數爲空的,例如:github
handleListen = () => {};
只是爲了將history傳遞時不報錯。其中的push和replace方法是有效的,調用時會給context.url、context.location賦值。如上所示,但context.url有值時會重定向。react-router
因爲<StaticRouter>
內部會app
return <Router {...rest} history={history} staticContext={context} />;
而statusContext屬性在客戶端渲染時不存在,能夠經過這個條件去增長返回碼:dom
<Route render={({ staticContext }) => { if (staticContext) staticContext.status = status; // Redirect會調用push或replace return <Redirect from={from} to={to} />; }} />
import { renderRoutes } from "react-router-config"; const routes = [ { component: Root, routes: [ { path: "/", exact: true, component: Home }, { path: "/child/:id", component: Child, routes: [ { path: "/child/:id/grand-child", component: GrandChild } ] } ] } ]; const Root = ({ route }) => ( <div> <h1>Root</h1> {/* child routes won't render without this */} {renderRoutes(route.routes)} </div> ); const Home = ({ route }) => ( <div> <h2>Home</h2> </div> ); const Child = ({ route }) => ( <div> <h2>Child</h2> {/* child routes won't render without this */} {renderRoutes(route.routes, { someProp: "these extra props are optional" })} </div> ); const GrandChild = ({ someProp }) => ( <div> <h3>Grand Child</h3> <div>{someProp}</div> </div> ); //renderRoutes方法對routes進行map生成<Route> ReactDOM.render( <BrowserRouter> {/* kick it all off with the root route */} {renderRoutes(routes)} </BrowserRouter>, document.getElementById("root") );
Universal Router是一個輕量化的靜態路由庫,可使用在客戶端和服務端。
client端的處理:
history.location
得到當前location並進行初始渲染。history.listen
監聽url變化,url變化時觸發從新渲染函數。location.pathname
,調用router.resolve({pathname})獲得匹配的route,最後調用render方法進行渲染。server端的處理:
路由配置代碼的基本結構:
const routes = [ { path: '/one', action: () => '<h1>Page One</h1>' }, { path: '/two', action: () => '<h1>Page Two</h1>' }, { path: '(.*)', action: () => '<h1>Not Found</h1>' } ] //context this.context = { router: this, ...options.context } const router = new UniversalRouter(routes, {context,resolveRoute}) //resolve的參數pathnameOrContext // const context = { // ...this.context, // ...(typeof pathnameOrContext === 'string' // ? { pathname: pathnameOrContext } // : pathnameOrContext), // } router.resolve({ pathname: '/one' }).then(result => { document.body.innerHTML = result // renders: <h1>Page One</h1> })
首先經過routes定義靜態路由,path屬性是必須的,action是resolve時默認的調用函數
function resolveRoute(context, params) { if (typeof context.route.action === 'function') { return context.route.action(context, params) } return undefined }
router.resolve
時的邏輯,經過context添加自定義的方法和屬性。router.resolve
去匹配pathname,該函數的參數都會加到context屬性上,函數內部返回resolveRoute(context, params)
的返回值。權限管理:
context.next()
會遍歷resolve其子路由,調用context.next(true)
會遍歷resolve全部剩餘路由。const middlewareRoute = { path: '/admin', action(context) { if (!context.user) { return null // route does not match (skip all /admin* routes) } if (context.user.role !== 'Admin') { return 'Access denied!' // return a page (for any /admin* urls) } return undefined // or `return context.next()` - try to match child routes }, children: [/* admin routes here */], }