前端路由的原理大体相同:当页面的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 */], }