两只小蚂蚁

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

使用

version: 5.2.0

官网的使用示例如下:

import React from "react";
import {
  BrowserRouter,
  Switch,
  Route
} from "react-router-dom";

export default function root() {
  return (
    <BrowserRouter>
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
    </BrowserRouter>
  );
}

function Home() {
  return (
    <div>
      <Link to="/">Home</Link>
    </div>
  );
}

以使用最多的BrowserRouter来分析。

源码

把下载的源码打开可以发现在packages下有4个目录:

-packages
--react-router
--react-router-config
--react-router-dom
--react-router-native

React-Router-Dom

//packages/react-router-dom/modules/BrowserRouter.js
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
class BrowserRouter extends React.Component {
  //这里使用createBrowserHistory创建了一个history对象
  history = createHistory(this.props);

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

History包

//node_modules/history/cjs/history.js
function createBrowserHistory(props) {
	...
	//省略其他代码
	var globalHistory = window.history;
	...
	//方法定义
	function getDOMLocation(historyState) {...}
	function setState(nextState) {...}
	....
	//我们挑一个最常用的push方法来看
	function push(path, state) {
    var action = 'PUSH';
    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
			...
      if (canUseHistory) {
        globalHistory.pushState({
          key: key,
          state: state
        }, null, href);

        if (forceRefresh) {
          window.location.href = href;
        } else {
          ...
          setState({
            action: action,
            location: location
          });
        }
      } else {
        window.location.href = href;
      }
    });
  }
  
  //再看看listen
  function listen(listener) {
    var unlisten = transitionManager.appendListener(listener);
    checkDOMListeners(1);
    return function () {
      checkDOMListeners(-1);
      unlisten();
    };
  }
  function checkDOMListeners(delta) {
    listenerCount += delta;
    if (listenerCount === 1 && delta === 1) {
      //监听popstate事件
      window.addEventListener(PopStateEvent, handlePopState);
      if (needsHashChangeListener) window.addEventListener(HashChangeEvent, handleHashChange);
    } else if (listenerCount === 0) {
      window.removeEventListener(PopStateEvent, handlePopState);
      if (needsHashChangeListener) window.removeEventListener(HashChangeEvent, handleHashChange);
    }
  }                       
                                
	var history = {
    length: globalHistory.length,
    action: 'POP',
    location: initialLocation,
    createHref: createHref,
    push: push,
    replace: replace,
    go: go,
    goBack: goBack,
    goForward: goForward,
    block: block,
    listen: listen
  };
  return history;
}

看完上面的代码核心的东西就出来了:history包对window.history进行一层包装,使用pushState和监听popState事件等方式,提供给上层应用进行处理。

React-Router

//packages/react-router/modules/Router.js
import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";
class Router extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      location: props.history.location
    };

    this._isMounted = false;
    this._pendingLocation = null;

    if (!props.staticContext) {
      this.unlisten = props.history.listen(location => {
        if (this._isMounted) {
          this.setState({ location });
        } else {
          this._pendingLocation = location;
        }
      });
    }
  }

  componentDidMount() {
    this._isMounted = true;
    if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation });
    }
  }

  componentWillUnmount() {
    if (this.unlisten) {
      this.unlisten();
      this._isMounted = false;
      this._pendingLocation = null;
    }
  }

  render() {
    return (
      <RouterContext.Provider
        value={{
          history: this.props.history,
          location: this.state.location,
          match: Router.computeRootMatch(this.state.location.pathname),
          staticContext: this.props.staticContext
        }}
      >
        <HistoryContext.Provider
          children={this.props.children || null}
          value={this.props.history}
        />
      </RouterContext.Provider>
    );
  }
}

添加history.listen事件监听,通过Router类组件的setState来处理React的重新渲染。

//RouterContext和HistoryContext分别如下
const context = /*#__PURE__*/ createNamedContext("Router");
const historyContext = /*#__PURE__*/ createNamedContext("Router-History");
//
import createContext from "mini-create-react-context";
const createNamedContext = name => {
  const context = createContext();
  context.displayName = name;

  return context;
};

这里有2个provider,一个是叫Router的React Context,另外一个是叫Router-History的React Context。

所以Router就是一个包装过的React Context的Provider。

Route

再来看看Route,这个应该是Context的Consumer

//packages/react-router/modules/Route.js
import RouterContext from "./RouterContext.js";
class Route extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          ...
          const location = this.props.location || context.location;
          const match = this.props.computedMatch
            ? this.props.computedMatch
            : this.props.path
            ? matchPath(location.pathname, this.props)
            : context.match;

          const props = { ...context, location, match };

          let { children, component, render } = this.props;
          if (Array.isArray(children) && isEmptyChildren(children)) {
            children = null;
          }

          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>
    );
  }
}

总结

React-Router使用React Context作为数据中心在整个应用中传递包装过的history对象,并监听浏览器的popstate通过setState(Router.js)改变location来触发重新渲染。

posted on 2021-12-27 18:33  两只小蚂蚁  阅读(70)  评论(0编辑  收藏  举报