前端路由的原理大致相同:当页面的URL发生变化时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。
要实现URL变化页面不刷新有两种方法:通过hash实现、通过History API实现。
1. 实现方法
- hash实现原理
改变页面的hash值不会刷新页面,而hashchange的事件,可以监听hash的变化,从而在hash变化时渲染新页面。
- History API实现原理
History API中pushState、replaceState方法会改变当前页面url,但是不会伴随着刷新,但是调用这两个方法改变页面url没有事件可以监听。有个history库增强了history API,采用发布订阅模式来对url的变化作出反映。其暴露出一个listen方法来添加订阅者,通过重写push、replace方法,使得这两个方法调用时通知订阅者,从而在url变化时渲染新页面。
2. react-route库
2.1 基本结构
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
export default function App() {
return (
);
}
function Home() {
return Home
;
}
function About() {
return About
;
}
function Users() {
return Users
;
}
react-router使用的基本结构是:
- 外层使用
包裹整个app,主要类型有
和
,分别对应上面两种实现方法;首先把location、history对象(增强的)通过react context API注入到子组件中,然后在
中会调用history.listen
方法监听location变化,当location变化时采用setState改变location触发子组件的更新。 -
标签做导航用,点击时会调用
history.push
或history.replace
方法,并改变context中的location。 - context变化导致
重新渲染,找到匹配的
渲染。 -
Route
组件根据Swtich
的匹配结果渲染component,并通过React context API将location、history对象注入到子组件。
2.2 StaticRouter
服务端渲染时页面是静态的,没有state,不能通过state改变去触发子组件更新。在服务端是根据req.url
来渲染页面的,其基本使用方式如下:
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(
);
//重定向时触发
if (context.url) {
res.writeHead(context.status, {
Location: context.url
});
res.end();
} else {
res.write(`
${html}
`);
res.end();
}
})
.listen(3000);
在
中没有使用history库了,而是创建了一个简单的history对象,其对应history库创建的history对象,但是其中的方法大多数为空的,例如:
handleListen = () => {};
只是为了将history传递时不报错。其中的push和replace方法是有效的,调用时会给context.url、context.location赋值。如上所示,但context.url有值时会重定向。
由于
内部会
return ;
而statusContext属性在客户端渲染时不存在,可以通过这个条件去增加返回码:
{
if (staticContext) staticContext.status = status;
// Redirect会调用push或replace
return ;
}}
/>
2.3 静态路由 React Router Config
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 }) => (
Root
{/* child routes won't render without this */}
{renderRoutes(route.routes)}
);
const Home = ({ route }) => (
Home
);
const Child = ({ route }) => (
Child
{/* child routes won't render without this */}
{renderRoutes(route.routes, { someProp: "these extra props are optional" })}
);
const GrandChild = ({ someProp }) => (
Grand Child
{someProp}
);
//renderRoutes方法对routes进行map生成
ReactDOM.render(
{/* kick it all off with the root route */}
{renderRoutes(routes)}
,
document.getElementById("root")
);
3. Universal Router库
Universal Router是一个轻量化的静态路由库,可以使用在客户端和服务端。
client端的处理:
- 引入history库,通过
history.location
获得当前location并进行初始渲染。 - 调用
history.listen
监听url变化,url变化时触发重新渲染函数。 - 渲染函数中首先得到
location.pathname
,调用router.resolve({pathname})得到匹配的route,最后调用render方法进行渲染。
server端的处理:
- 服务端没有url状态的变化,可以直接从req.path的的得到路由信息
- 调用router.resolve({pathname})得到匹配的route,最后调用render方法进行渲染。
路由配置代码的基本结构:
const routes = [
{ path: '/one', action: () => 'Page One
' },
{ path: '/two', action: () => 'Page Two
' },
{ path: '(.*)', action: () => 'Not Found
' }
]
//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: Page One
})
-
首先通过routes定义静态路由,path属性是必须的,action是resolve时默认的调用函数
function resolveRoute(context, params) { if (typeof context.route.action === 'function') { return context.route.action(context, params) } return undefined }
- 生成router实例,此时可以通过resolveRoute option定义
router.resolve
时的逻辑,通过context添加自定义的方法和属性。 - 调用
router.resolve
去匹配pathname,该函数的参数都会加到context属性上,函数内部返回resolveRoute(context, params)
的返回值。
权限管理:
- context对象上有next方法,调用
context.next()
会遍历resolve其子路由,调用context.next(true)
会遍历resolve所有剩余路由。 - resolve得到的返回值为undefined时将会尝试匹配其子路由,得到的返回值为null时将会尝试匹配其兄弟路由
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 */],
}