react-router学习笔记

2021-01-25  本文已影响0人  好_快

前端路由

React-Router.png

1、查看源码姿势

1.1 代码仓库

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

2.2 包说明

2.3 源码位置

2.4 文件结构

3、React-Router源码分析

3.1 四种 Router 源码对比

<!--HashRouter-->
//便于梳理代码结构,已删除部分代码
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-->
//便于梳理代码结构,已删除部分代码
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-->
//便于梳理代码结构,已删除部分代码
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-->
//便于梳理代码结构,已删除部分代码
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>组件实现,区别就是传递不同的参数。重点关注 <HashRouter> 和 <BrowserRouter>

3.2 <Router> 源码分析

<!--Router-->
//便于梳理代码结构,已删除部分代码
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的核心功能就是提供以下数据

3.3 <Route> 源码分析

<!--Route-->
//便于梳理代码结构,已删除部分代码
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>
    );
  }
}

由于三项表达式嵌套不便于阅读,代码可以转换成

/*
  * 1、检测是否 match
  * 1.1 不匹配,children 为函数则返回 children(props),否则返回 null
  * 1.2 匹配,进行第2步
  *
  * 2、检查 children
  * 2.1 存在, children 为函数则返回 children(props),否则返回 children
  * 2.2 不存在,进行第3步
  *
  * 3、检查component
  * 3.1 存在,返回React.createElement(component, props)
  * 3.2 不在存,进行第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的核心功能是渲染组件,并有以下特点

3.4 <Redirect> 源码分析

<!--Switch-->
//便于梳理代码结构,已删除部分代码
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 的核心功能是跳转到 to 指向的页面。

3.5 <Switch> 源码分析

<!--Switch-->
//便于梳理代码结构,已删除部分代码
class Switch extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const location = this.props.location || context.location;
          let element, match;
          // 匹配第一个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 的核心功能是渲染匹配到第一个 Route 组件。

3.6 withRouter 源码分析

<!--withRouter-->
//便于梳理代码结构,已删除部分代码
function withRouter(Component) {
  const C = props => {
    const { wrappedComponentRef, ...remainingProps } = props;
    return (
      //通过 Context.Consumer 传递 Router 属性给 Component,达到增强目的
      <RouterContext.Consumer>
        {context => {
          return (
            <Component
              {...remainingProps}
              {...context}
              ref={wrappedComponentRef}
            />
          );
        }}
      </RouterContext.Consumer>
    );
  };
  //静态属性拷贝
  return hoistStatics(C, Component);
}

通过源码可以看出 withRouter 的核心功能是 传递 Router 属性给 Component。

3.7 <Link> 源码分析

<!--Link-->
//便于梳理代码结构,已删除部分代码
const Link = forwardRef(
  (
    {
      component = LinkAnchor,
      replace,
      to,
      innerRef, // TODO: deprecate
      ...rest
    },
    forwardedRef
  ) => {
    return (
      <RouterContext.Consumer>
        {context => {
          const { history } = context;

          //把 to 属性转换成 href 属性
          const location = normalizeToLocation(
            resolveToLocation(to, context.location),
            context.location
          );
          const href = location ? history.createHref(location) : "";
          
          //组装 props 数据
          const props = {
            ...rest,
            href,
            navigate() {
              const location = resolveToLocation(to, context.location);
              const method = replace ? history.replace : history.push;

              method(location);
            }
          };
          // 设置forwardedRef
          props.ref = forwardedRef
          //创建并返回组件
          return React.createElement(component, props);
        }}
      </RouterContext.Consumer>
    );
  }
);
<!--LinkAnchor-->
//便于梳理代码结构,已删除部分代码
const LinkAnchor = forwardRef(
  (
    {
      innerRef, // TODO: deprecate
      navigate,
      onClick,
      ...rest
    },
    forwardedRef
  ) => {
    const { target } = rest;

    //组装 props 数据
    let props = {
      ...rest,
      onClick: event => {
        //处理点击事件
        try {
          if (onClick) onClick(event);
        } catch (ex) {
          event.preventDefault();
          throw ex;
        }
        //执行路由跳转事件
        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();
        }
      }
    };

    // 设置forwardedRef
    props.ref = forwardedRef;

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

通过源码可以看出 Link 的核心功能如下

3.8 <NavLink> 源码分析

<!--NavLink-->
//便于梳理代码结构,已删除部分代码
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 => {
          //根据 isActive 属性判断是否 Active
          const isActive = !!(isActiveProp
            ? isActiveProp(match, currentLocation)
            : match);
          //根据 isActive 属性设置 className
          const className = isActive
            ? joinClassnames(classNameProp, activeClassName)
            : classNameProp;
          //根据 isActive 属性设置内联样式 style
          const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
          //组织 props 数据
          const props = {
            "aria-current": (isActive && ariaCurrent) || null,
            className,
            style,
            to: toLocation,
            ...rest
          };
          // 设置forwardedRef
          props.ref = forwardedRef;
          return <Link {...props} />;
        }}
      </RouterContext.Consumer>
    );
  }
);

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

4、history 源码分析

4.1 createBrowserHistory

<!--createBrowserHistory-->
//便于梳理代码结构,已删除部分代码
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 方法实现
  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方法实现
  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方法实现
  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;
}

4.2

参考链接

上一篇 下一篇

猜你喜欢

热点阅读