react-router學(xué)習(xí)筆記

前端路由

  • 路由這個(gè)概念最早出現(xiàn)在后端,通過?戶請求的url導(dǎo)航到具體的html??。
  • 現(xiàn)在的前端路由不同 于傳統(tǒng)路由,它不需要服務(wù)器解析,?是可以通過hash函數(shù)或者h(yuǎn)istory API來實(shí)現(xiàn)。
  • 在前端開發(fā)中,可以使?路由設(shè)置訪問路徑,并根據(jù)路徑與組件的映射關(guān)系切換組件的顯示
  • 這整個(gè)過程都是在同 ?個(gè)??中實(shí)現(xiàn)的,不涉及??間的跳轉(zhuǎn),這也就是常說的單?應(yīng)?(spa)。
React-Router.png

1、查看源碼姿勢

1.1 代碼倉庫

https://github.com/ReactTraining/react-router

2.2 包說明

  • react-router 公共基礎(chǔ)包
  • react-router-dom 在瀏覽器中使?,依賴react-router
  • react-router-native 在react-native中使用,依賴react-router

2.3 源碼位置

  • react-router 源碼位置 react-router/packages/react-router/modules/
  • react-router-dom 源碼位置 react-router/packages/react-router-dom/modules/

2.4 文件結(jié)構(gòu)

  • Hooks暫不關(guān)注
  • <Link>
    • <NavLink>
  • <Prompt> 暫不關(guān)注
  • <Redirect>
  • <Route>
  • <Router>
    • <HashRouter>
    • <BrowserRouter>
    • <MemoryRouter> 暫不關(guān)注
    • <StaticRouter> 暫不關(guān)注
  • <Switch>
  • generatePath 暫不關(guān)注
  • history
  • location 暫不關(guān)注
  • match 暫不關(guān)注
  • matchPath 暫不關(guān)注
  • withRouter

3、React-Router源碼分析

3.1 四種 Router 源碼對比

<!--HashRouter-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
import React from "react";
import { Router } from "react-router";
import { createHashHistory as createHistory } from "history";
/**
 * The public API for a <Router> that uses window.location.hash.
 */
class HashRouter extends React.Component {
  history = createHistory(this.props);
  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
export default HashRouter;

<!--BrowserRouter-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
/**
 * The public API for a <Router> that uses HTML5 history.
 */
class BrowserRouter extends React.Component {
  history = createHistory(this.props);
  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
export default BrowserRouter;

<!--MemoryRouter-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
import React from "react";
import { createMemoryHistory as createHistory } from "history";
import Router from "./Router.js";
/**
 * The public API for a <Router> that stores location in memory.
 */
class MemoryRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
export default MemoryRouter;

<!--StaticRouter-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
import React from "react";
import { createLocation, createPath } from "history";
import Router from "./Router.js";
/**
 * The public top-level API for a "static" <Router>, so-called because it
 * can't actually change the current location. Instead, it just records
 * location changes in a context object. Useful mainly in testing and
 * server-rendering scenarios.
 */
class StaticRouter extends React.Component {
  render() {
    const { basename = "", context = {}, location = "/", ...rest } = this.props;
    const history = {
      createHref: path => addLeadingSlash(basename + createURL(path)),
      action: "POP",
      location: stripBasename(basename, createLocation(location)),
      push: this.handlePush,
      replace: this.handleReplace,
      go: staticHandler("go"),
      goBack: staticHandler("goBack"),
      goForward: staticHandler("goForward"),
      listen: this.handleListen,
      block: this.handleBlock
    };
    return <Router {...rest} history={history} staticContext={context} />;
  }
}
export default StaticRouter;

都是基于<Router>組件實(shí)現(xiàn),區(qū)別就是傳遞不同的參數(shù)。重點(diǎn)關(guān)注 <HashRouter> 和 <BrowserRouter>

3.2 <Router> 源碼分析

<!--Router-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
class Router extends React.Component {
  render() {
    return (
      <RouterContext.Provider
        value={{
          history: this.props.history,
          location: this.state.location,
          match: Router.computeRootMatch(this.state.location.pathname),
          //只有StaticRouter在使用,暫不考慮
          staticContext: this.props.staticContext
        }}
      >
        {/*HistoryContext在hooks中使用,暫不考慮*/}
        <HistoryContext.Provider
          children={this.props.children || null}
          value={this.props.history}
        />
      </RouterContext.Provider>
    );
  }
}

通過源碼可以看出Router的核心功能就是提供以下數(shù)據(jù)

  • history,父組件傳入,由history庫生成
  • location,組件內(nèi)計(jì)算生成
  • match,組件內(nèi)靜態(tài)方法計(jì)算生成

3.3 <Route> 源碼分析

<!--Route-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
class Route extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          return (
            <RouterContext.Provider value={props}>
              {props.match
                ? children
                  ? typeof children === "function"
                    ? __DEV__
                      ? evalChildrenDev(children, props, this.props.path)
                      : children(props)
                    : children
                  : component
                  ? React.createElement(component, props)
                  : render
                  ? render(props)
                  : null
                : typeof children === "function"
                ? __DEV__
                  ? evalChildrenDev(children, props, this.props.path)
                  : children(props)
                : null}
            </RouterContext.Provider>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}

由于三項(xiàng)表達(dá)式嵌套不便于閱讀,代碼可以轉(zhuǎn)換成

/*
  * 1、檢測是否 match
  * 1.1 不匹配,children 為函數(shù)則返回 children(props),否則返回 null
  * 1.2 匹配,進(jìn)行第2步
  *
  * 2、檢查 children
  * 2.1 存在, children 為函數(shù)則返回 children(props),否則返回 children
  * 2.2 不存在,進(jìn)行第3步
  *
  * 3、檢查component
  * 3.1 存在,返回React.createElement(component, props)
  * 3.2 不在存,進(jìn)行第4步
  *
  * 4、檢查render
  * 4.1 存在,返回 render(props)
  * 4.2 不存在,返回 null
  * */
getComponent(props, children, component, render) {
  if (props.match) {
    if (children) {
      if (typeof children === "function") {
        return children(props);
      } else {
        return children;
      }
    } else {
      if (component) {
        return React.createElement(component, props);
      } else {
        if (render) {
          return render(props);
        } else {
          return null;
        }
      }
    }
  } else {
    if (typeof children === "function") {
      return children(props);
    } else {
      return null;
    }
  }
}

通過源碼可以看出Reoute的核心功能是渲染組件,并有以下特點(diǎn)

  • 三者優(yōu)先級(jí) children > component > render
  • children 為函數(shù)可以做到匹配與否都顯示
  • children 為組件只能在匹配顯示
  • component 只能為組件
  • render 只能為函數(shù)

3.4 <Redirect> 源碼分析

<!--Switch-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
function Redirect({ computedMatch, to, push = false }) {
  const method = push ? history.push : history.replace;
  return (
    <RouterContext.Consumer>
      {context => {
        return (
          <Lifecycle
            onMount={() => {
              method(location);
            }}
            onUpdate={(self, prevProps) => {
              method(location);
            }}
            to={to}
          />
        );
      }}
    </RouterContext.Consumer>
  );
}

通過源碼可以看出 Redirect 的核心功能是跳轉(zhuǎn)到 to 指向的頁面。

3.5 <Switch> 源碼分析

<!--Switch-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
class Switch extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const location = this.props.location || context.location;
          let element, match;
          // 匹配第一個(gè)Route
          React.Children.forEach(this.props.children, child => {
            if (match == null && React.isValidElement(child)) {
              element = child;
              const path = child.props.path || child.props.from;
              match = path
                ? matchPath(location.pathname, { ...child.props, path })
                : context.match;
            }
          });
          //匹配則返回React生成的元素,否則返回null
          return match
            ? React.cloneElement(element, { location, computedMatch: match })
            : null;
        }}
      </RouterContext.Consumer>
    );
  }
}

通過源碼可以看出 Switch 的核心功能是渲染匹配到第一個(gè) Route 組件。

3.6 withRouter 源碼分析

<!--withRouter-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
function withRouter(Component) {
  const C = props => {
    const { wrappedComponentRef, ...remainingProps } = props;
    return (
      //通過 Context.Consumer 傳遞 Router 屬性給 Component,達(dá)到增強(qiáng)目的
      <RouterContext.Consumer>
        {context => {
          return (
            <Component
              {...remainingProps}
              {...context}
              ref={wrappedComponentRef}
            />
          );
        }}
      </RouterContext.Consumer>
    );
  };
  //靜態(tài)屬性拷貝
  return hoistStatics(C, Component);
}

通過源碼可以看出 withRouter 的核心功能是 傳遞 Router 屬性給 Component。

3.7 <Link> 源碼分析

<!--Link-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
const Link = forwardRef(
  (
    {
      component = LinkAnchor,
      replace,
      to,
      innerRef, // TODO: deprecate
      ...rest
    },
    forwardedRef
  ) => {
    return (
      <RouterContext.Consumer>
        {context => {
          const { history } = context;

          //把 to 屬性轉(zhuǎn)換成 href 屬性
          const location = normalizeToLocation(
            resolveToLocation(to, context.location),
            context.location
          );
          const href = location ? history.createHref(location) : "";
          
          //組裝 props 數(shù)據(jù)
          const props = {
            ...rest,
            href,
            navigate() {
              const location = resolveToLocation(to, context.location);
              const method = replace ? history.replace : history.push;

              method(location);
            }
          };
          // 設(shè)置forwardedRef
          props.ref = forwardedRef
          //創(chuàng)建并返回組件
          return React.createElement(component, props);
        }}
      </RouterContext.Consumer>
    );
  }
);
<!--LinkAnchor-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
const LinkAnchor = forwardRef(
  (
    {
      innerRef, // TODO: deprecate
      navigate,
      onClick,
      ...rest
    },
    forwardedRef
  ) => {
    const { target } = rest;

    //組裝 props 數(shù)據(jù)
    let props = {
      ...rest,
      onClick: event => {
        //處理點(diǎn)擊事件
        try {
          if (onClick) onClick(event);
        } catch (ex) {
          event.preventDefault();
          throw ex;
        }
        //執(zhí)行路由跳轉(zhuǎn)事件
        if (
          !event.defaultPrevented && // onClick prevented default
          event.button === 0 && // ignore everything but left clicks
          (!target || target === "_self") && // let browser handle "target=_blank" etc.
          !isModifiedEvent(event) // ignore clicks with modifier keys
        ) {
          event.preventDefault();
          navigate();
        }
      }
    };

    // 設(shè)置forwardedRef
    props.ref = forwardedRef;

    /* eslint-disable-next-line jsx-a11y/anchor-has-content */
    return <a {...props} />;
  }
);

通過源碼可以看出 Link 的核心功能如下

  • 通過 component 自定義 <Link>,否則默認(rèn)使用 <LinkAnchor>
  • <Link> 的主要功能就是把 to 屬性轉(zhuǎn)換成 href 屬性
  • <LinkAnchor> 主要功能是屏蔽 <a> 默認(rèn)點(diǎn)擊事件,使用 history 進(jìn)行路由跳轉(zhuǎn)

3.8 <NavLink> 源碼分析

<!--NavLink-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
const NavLink = forwardRef(
  (
    {
      "aria-current": ariaCurrent = "page",
      activeClassName = "active",
      activeStyle,
      className: classNameProp,
      exact,
      isActive: isActiveProp,
      location: locationProp,
      sensitive,
      strict,
      style: styleProp,
      to,
      innerRef, // TODO: deprecate
      ...rest
    },
    forwardedRef
  ) => {
    return (
      <RouterContext.Consumer>
        {context => {
          //根據(jù) isActive 屬性判斷是否 Active
          const isActive = !!(isActiveProp
            ? isActiveProp(match, currentLocation)
            : match);
          //根據(jù) isActive 屬性設(shè)置 className
          const className = isActive
            ? joinClassnames(classNameProp, activeClassName)
            : classNameProp;
          //根據(jù) isActive 屬性設(shè)置內(nèi)聯(lián)樣式 style
          const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
          //組織 props 數(shù)據(jù)
          const props = {
            "aria-current": (isActive && ariaCurrent) || null,
            className,
            style,
            to: toLocation,
            ...rest
          };
          // 設(shè)置forwardedRef
          props.ref = forwardedRef;
          return <Link {...props} />;
        }}
      </RouterContext.Consumer>
    );
  }
);

通過源碼可以看出 <NavLink> 的核心功能如下

  • 在 <Link> 基礎(chǔ)上允許自定義默認(rèn)和激活狀態(tài)樣式
  • isActive 優(yōu)先級(jí)高于默認(rèn)的路由匹配

4、history 源碼分析

4.1 createBrowserHistory

<!--createBrowserHistory-->
//便于梳理代碼結(jié)構(gòu),已刪除部分代碼
export function createBrowserHistory( options: BrowserHistoryOptions = {} ): BrowserHistory {
  //獲取 history
  let { window = document.defaultView! } = options;
  let globalHistory = window.history;


  window.addEventListener(PopStateEventType, handlePop);

  let action = Action.Pop;
  let [index, location] = getIndexAndLocation();
  let listeners = createEvents<Listener>();

  //pathname + search + hash
  function createHref(to: To) {
    return typeof to === 'string' ? to : createPath(to);
  }

  //用來處理事件訂閱
  function applyTx(nextAction: Action) {
    action = nextAction;
    [index, location] = getIndexAndLocation();
    listeners.call({ action, location });
  }

  //基于 pushState 方法實(shí)現(xiàn)
  function push(to: To, state?: State) {
    let nextAction = Action.Push;
    let nextLocation = getNextLocation(to, state);
    function retry() {
      push(to, state);
    }
    if (allowTx(nextAction, nextLocation, retry)) {
      let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);

      // TODO: Support forced reloading
      // try...catch because iOS limits us to 100 pushState calls :/
      try {
        globalHistory.pushState(historyState, '', url);
      } catch (error) {
        // They are going to lose state here, but there is no real
        // way to warn them about it since the page will refresh...
        window.location.assign(url);
      }
      //用來處理事件訂閱
      applyTx(nextAction);
    }
  }

  //基于replaceState方法實(shí)現(xiàn)
  function replace(to: To, state?: State) {
    let nextAction = Action.Replace;
    let nextLocation = getNextLocation(to, state);
    function retry() {
      replace(to, state);
    }

    if (allowTx(nextAction, nextLocation, retry)) {
      let [historyState, url] = getHistoryStateAndUrl(nextLocation, index);

      // TODO: Support forced reloading
      globalHistory.replaceState(historyState, '', url);

      //用來處理事件訂閱
      applyTx(nextAction);
    }
  }

    //基于go方法實(shí)現(xiàn)
  function go(delta: number) {
    globalHistory.go(delta);
  }

  let history: BrowserHistory = {
    get action() {
      return action;
    },
    get location() {
      return location;
    },
    createHref,
    push,
    replace,
    go,
    back() {
      go(-1);
    },
    forward() {
      go(1);
    },
    listen(listener) {
      return listeners.push(listener);
    },
    block(blocker) {...}
  };

  return history;
}
  • back、forward 都是基于 go 方法實(shí)現(xiàn)
  • push 基于 pushState 方法實(shí)現(xiàn)
  • replace 基于 replaceState 方法實(shí)現(xiàn)
  • 瀏覽器相關(guān)(url輸入、前進(jìn)、后退)事件通過監(jiān)聽 popstate 事件處理,然后通過事件訂閱的形式傳遞給Route組件

4.2

  • createHashHistory 同 createBrowserHistory
  • createMemoryHistory 同 createBrowserHistory

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容