react-router路由机制

原理

无刷新的更改地址栏地址 ,保证视图和URL的同步。基本原理是H5History API 。
浏览器的历史记录,以栈的形式存储,后进先出,按照栈的规律,必须有的方法:进栈(pushstate)、出栈(popstate)、替换当前的(replacestate) 。这里对H5History API就不做详细解释了,可以查看这篇文章。

实现过程


当我们点击了Link组件或者调用了history.push跳转路由时,react-router做了上图的处理流程。

假定使用BrowserRoutercreateBrowserHistory,使用history.push跳转路由

1、调用history.push跳转路由时,内部执行window.history.pushState在浏览器history栈中新增一条记录,修改了应用的 URL,执行组件注册的回调函数。
2、createBrowserHistory中注册popstate事件,用户点击浏览器前进、回退时,在popstate事件中获取当前的event.state,重新组装一个location,执行组件注册的回调函数。
3、history库对外暴露createBrowserHistory方法,react-router中实例化createBrowserHistory方法对象,在组件中注册history.listen()回调函数,当路由有变化时,组件中匹配location,同步UI。

分析

1、history.push
在react中,我们可以调用history.push(path,state)来跳转路由,实际执行的就是createBrowserHistory中的push方法。
在这个方法中主要做三件事:
一、根据传递的path,state参数创建一个location,不同于window.location。(具体的可以看代码,点击上面的createBrowserHistory

location = {
  pathname, // 当前路径,即 Link 中的 to 属性
  search, // search
  hash, // hash
  state, // state 对象
  action, // location 类型,在点击 Link 时为 PUSH,浏览器前进后退时为 POP,调用 replaceState 方法时为 REPLACE
  key, // 用于操作 sessionStorage 存取 state 对象
};

 const location = createLocation(path, state, createKey(), history.location);
//生成location
createLocation(path, state, key, currentLocation) {
  let location;
  if (typeof path === 'string') {
    // Two-arg form: push(path, state)
    location = parsePath(path);
    location.state = state;
  } else {
    // One-arg form: push(location)
    location = { ...path };

    if (location.pathname === undefined) location.pathname = '';

    if (location.search) {
      if (location.search.charAt(0) !== '?')
        location.search = '?' + location.search;
    } else {
      location.search = '';
    }

    if (location.hash) {
      if (location.hash.charAt(0) !== '#') location.hash = '#' + location.hash;
    } else {
      location.hash = '';
    }

    if (state !== undefined && location.state === undefined)
      location.state = state;
  }

  if (key) location.key = key;

  if (currentLocation) {
    // Resolve incomplete/relative pathname relative to current location.
    if (!location.pathname) {
      location.pathname = currentLocation.pathname;
    } else if (location.pathname.charAt(0) !== '/') {
      location.pathname = resolvePathname(
        location.pathname,
        currentLocation.pathname
      );
    }
  } else {
    // When there is no prior location and pathname is empty, set it to /
    if (!location.pathname) {
      location.pathname = '/';
    }
  }

  return location;
}

这个location会在组件中使用,来根据location中的值和中的path匹配,匹配成功的Route组件渲染指定的component;
二、执行globalHistory.pushState({ key, state }, null, href),添加一条记录;并执行 setState({ action, location })

三、在setState({ action, location })中执行Router中注册的listener,transitionManager.notifyListeners(history.location, history.action)

  function setState(nextState) {
    Object.assign(history, nextState);
    history.length = globalHistory.length;
    transitionManager.notifyListeners(history.location, history.action);
  }
  function notifyListeners(...args) {
    listeners.forEach(listener => listener(...args));
  }

2、popstate事件
popstate事件触发时,可以得到event.state,createBrowserHistory中会根据这个state和当前window.location重新生成一个location对象,执行Router组件注册的listener,同步UI。
3、组件
BrowserRouter组件中会实例化一个createBrowserHistory对象,传递给Router组件

import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";
import PropTypes from "prop-types";
import warning from "tiny-warning";

/**
 * The public API for a  that uses HTML5 history.
 */
class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return ;
  }
}

Router组件中要注册history.listen()的一个监听函数,并且保存一份子组件(Route)使用的数据

  Router.prototype.componentWillMount = function componentWillMount() {
    var _this2 = this;

    var _props = this.props,
        children = _props.children,
        history = _props.history;
    (0, _invariant2.default)(children == null || _react2.default.Children.count(children) === 1, "A  may have only one child element");

    // Do this here so we can setState when a  changes the
    // location in componentWillMount. This happens e.g. when doing
    // server rendering using a .
    this.unlisten = history.listen(function () {
      _this2.setState({
        match: _this2.computeMatch(history.location.pathname)
      });
    });
  };
  Router.prototype.getChildContext = function getChildContext() {
    return {
      router: _extends({}, this.context.router, {
        history: this.props.history,
        route: {
          location: this.props.history.location,
          match: this.state.match
        }
      })
    };
  };

当调用history.push或触发popstate事件时,这里注册的listener都会被createBrowserHistory执行,触发setState,然后Router的子组件中匹配的会重新渲染.
4、组件
在Route中有一个match状态,在父组件props发生变化时会重新计算

  Route.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps, nextContext) {
    (0, _warning2.default)(!(nextProps.location && !this.props.location), ' elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.');

    (0, _warning2.default)(!(!nextProps.location && this.props.location), ' elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.');

    this.setState({
      match: this.computeMatch(nextProps, nextContext.router)
    });
  };

//computeMatch主要工作就是匹配当前组件上指定的path和当前浏览器的路径是否一致,一致就渲染组件

  Route.prototype.computeMatch = function computeMatch(_ref, router) {
    var computedMatch = _ref.computedMatch,
        location = _ref.location,
        path = _ref.path,
        strict = _ref.strict,
        exact = _ref.exact,
        sensitive = _ref.sensitive;

    if (computedMatch) return computedMatch; //  already computed the match for us

    (0, _invariant2.default)(router, "You should not use  or withRouter() outside a ");

    var route = router.route;

    var pathname = (location || route.location).pathname;

    return (0, _matchPath2.default)(pathname, { path: path, strict: strict, exact: exact, sensitive: sensitive }, route.match);
  };

总结

总结一下,react-router的路由机制就是:
1、借助history库,history中实现了push、go、goBack等方法,注册了popstate事件,当路由跳转时,使用浏览器内置的history api 操作 history栈。
2、history库对外暴露的history对象提供了listen方法,组件会注册一个listener;
3、当调用hsitory.push或popstate事件触发时,执行listener。
4、注册的监听函数内部会setState更新状态
5、的子组件的componentWillReceiveProps生命周期函数中能得到Router中context,根据当前path和浏览器当前location来判断当前route是否match,匹配就渲染component。

你可能感兴趣的:(react-router路由机制)