Router和解析常用库

使用和常规API

note

// globalHistory
**var globalHistory = window.history;**
**// hash handleHashChange
function handleHashChange() {
  handlePop(getDOMLocation(getHistoryState()));
}**

hash和browser通用

function handlePop(location) {
    if (forceNextPop) {
      forceNextPop = false;
      setState();
    } else {
      var action = 'POP';
      transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
        if (ok) {
          setState({
            action: action,
            location: location
          });
        } else {
          revertPop(location);
        }
      });
    }
  }
**// browser handleHashChange
function handleHashChange() {
    var path = getHashPath();
    var encodedPath = encodePath(path);

    if (path !== encodedPath) {
      // Ensure we always have a properly-encoded hash.
      replaceHashPath(encodedPath);
    } else {
      var location = getDOMLocation();
      var prevLocation = history.location;
      if (!forceNextPop && locationsAreEqual$$1(prevLocation, location)) return; // A hashchange doesn't always == location change.

      if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.

      ignorePath = null;
      handlePop(location);
    }
  }**

browser router做了一个判断,如果

var path = getHashPath();
var encodedPath = encodePath(path);

if (path !== encodedPath) {...}

这里的encodePath

encodePath: function encodePath(path) {
  return path.charAt(0) === '!' ? path : '!/' + stripLeadingSlash(path);
},
function getHashPath() {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  var href = window.location.href;
  var hashIndex = href.indexOf('#');
  return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
}

就是判断当前的hash路径是否可以被正确解析,如果可以

function replaceHashPath(path) {
  window.location.replace(stripHash(window.location.href) + '#' + path);
} // 添加了hash的路由效果

否则

var location = getDOMLocation();
var prevLocation = history.location;
if (!forceNextPop && locationsAreEqual$$1(prevLocation, location)) return; // A hashchange doesn't always == location change.

if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.

ignorePath = null;
handlePop(location);

从Link出发看这两种模式的实现

var Link = forwardRef(function (_ref2, forwardedRef) {
  var _ref2$component = _ref2.component,
  component = _ref2$component === void 0 ? LinkAnchor : _ref2$component, // component 取值取决于是否为Link传入component 见下方 例-10
  replace = _ref2.replace,
  to = _ref2.to,
  innerRef = _ref2.innerRef,
  rest = _objectWithoutPropertiesLoose(_ref2, ["component", "replace", "to", "innerRef"]);

  return React.createElement(__RouterContext.Consumer, null, function (context) {
    !context ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <Link> outside a <Router>") : invariant(false) : void 0;
    var history = context.history;
    var location = normalizeToLocation(resolveToLocation(to, context.location), context.location);
    var href = location ? history.createHref(location) : "";

    var props = _extends({}, rest, {
      href: href,
      navigate: function navigate() {
        var location = resolveToLocation(to, context.location);
        var method = replace ? history.replace : history.push;
        method(location);
      }
    }); // React 15 compat

    if (forwardRefShim !== forwardRef) {
      props.ref = forwardedRef || innerRef;
    } else {
      props.innerRef = innerRef;
    }

    return React.createElement(component, props); // 返回一个element
  });
});
// 例-10
const FancyLink = React.forwardRef((props, ref) => (
  <a ref={ref} {...props}>💅 {props.children}</a>
))

<Link to="/" component={FancyLink} />

我们在Link上面添加的 to 、 replace 或者component的含义就是如此

  1. to 链接到某个地址 参数可以是[string, object, function]形式
  2. replace 跳转某个地址,但是不加入历史栈中
  3. component 自定义Link渲染出来的标签

我们来思考个问题? Link被点击会跳转到to 写的地址且正确拼接如何实现的?

其实就是LinkAnchor 这个组件中实现的下面我们来详细分析这个组件

var LinkAnchor = forwardRef(function (_ref, forwardedRef) {
  var innerRef = _ref.innerRef,
      navigate = _ref.navigate,
      _onClick = _ref.onClick,
      rest = _objectWithoutPropertiesLoose(_ref, ["innerRef", "navigate", "onClick"]);

  var target = rest.target;

  var props = _extends({}, rest, {
    onClick: function 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();
        }
    }
  }); // React 15 compat

  if (forwardRefShim !== forwardRef) {
    props.ref = forwardedRef || innerRef;
  } else {
    props.ref = innerRef;
  }
  /* eslint-disable-next-line jsx-a11y/anchor-has-content */

  return React.createElement("a", props);
});

抽出点击事件的处理逻辑

 onClick: function 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(); // 调用此函数
        }
    }

上面可以看出来如果没有设置点击事件的情况下,会走到navigate的函数,而这个函数是在Link组件中定义的,对应如下

navigate: function navigate() {
    var location = resolveToLocation(to, context.location); // 获取to的值
    var method = replace ? history.replace : history.push; // 判断是替换还是压入栈中
    method(location);
  }

// 对应的resolveToLocation 
var resolveToLocation = function resolveToLocation(to, currentLocation) {
  return typeof to === "function" ? to(currentLocation) : to;
};

比如我们 的Link组件是 Home,则navigate对应的location 就是 '/'

上面的总结下来就是假设我们有 <Link to="/">Home</Link> 会经历如下步骤:

  1. 对应的HashRouter或者BrowserRouter 将生成的history 作为context中的history传递到Link组件中

    1. 对应生成的history的不同 主要在push这类会更改location的方法上面
    2. Hash模式下
    	
    var path = createPath(location);
    var encodedPath = encodePath(basename + path);
    var hashChanged = getHashPath() !== encodedPath;
    
    if (hashChanged) {
    	...
    	pushHashPath(encodedPath);
    	...
    }
    
    // 相关的方法
    
    var basename = props.basename ? stripTrailingSlash(addLeadingSlash(props.basename)) : '';
    
    function stripTrailingSlash(path) {
      return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path;
    }
    
    function pushHashPath(path) {
      window.location.hash = path;
    }
    
    1. history(BrowserHistory)下
    if (canUseHistory) {
            globalHistory.pushState({
              key: key,
              state: state
            }, null, href);
    
            if (forceRefresh) {
              window.location.href = href;
            } else {...}
          }
    
  2. Link组件经过包装LinkAnchor后提供了组合后的默认a标签的组件添加

posted @ 2021-03-10 09:47  O噗寺的小和尚  阅读(63)  评论(0)    收藏  举报