前端路由庫

前端路由的原理大體相同:當頁面的URL發生變化時,頁面的顯示結果能夠根據URL的變化而變化,可是頁面不會刷新。javascript

要實現URL變化頁面不刷新有兩種方法:經過hash實現、經過History API實現。html

1. 實現方法

  • hash實現原理

    改變頁面的hash值不會刷新頁面,而hashchange的事件,能夠監聽hash的變化,從而在hash變化時渲染新頁面。前端

  • History API實現原理

    History API中pushState、replaceState方法會改變當前頁面url,可是不會伴隨着刷新,可是調用這兩個方法改變頁面url沒有事件能夠監聽。有個history庫加強了history API,採用發佈訂閱模式來對url的變化做出反映。其暴露出一個listen方法來添加訂閱者,經過重寫push、replace方法,使得這兩個方法調用時通知訂閱者,從而在url變化時渲染新頁面。java

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 (
    <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

  1. 外層使用<Router>包裹整個app,主要類型有<BrowserRouter><HashRouter>,分別對應上面兩種實現方法;首先把location、history對象(加強的)經過react context API注入到子組件中,而後在<Router>中會調用history.listen方法監聽location變化,當location變化時採用setState改變location觸發子組件的更新。
  2. <Link>標籤作導航用,點擊時會調用history.pushhistory.replace方法,並改變context中的location。
  3. context變化致使<Switch>從新渲染,找到匹配的<Route>渲染。
  4. Route組件根據Swtich的匹配結果渲染component,並經過React context API將location、history對象注入到子組件。

2.2 StaticRouter

服務端渲染時頁面是靜態的,沒有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} />;
      }}
    />

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 }) => (
  <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")
);

3. Universal Router庫

Universal Router是一個輕量化的靜態路由庫,可使用在客戶端和服務端。

client端的處理:

  1. 引入history庫,經過history.location得到當前location並進行初始渲染。
  2. 調用history.listen監聽url變化,url變化時觸發從新渲染函數。
  3. 渲染函數中首先獲得location.pathname,調用router.resolve({pathname})獲得匹配的route,最後調用render方法進行渲染。

server端的處理:

  1. 服務端沒有url狀態的變化,能夠直接從req.path的的獲得路由信息
  2. 調用router.resolve({pathname})獲得匹配的route,最後調用render方法進行渲染。

路由配置代碼的基本結構:

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實例,此時能夠經過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 */],
}
相關文章
相關標籤/搜索