React Router

2020-06-10  本文已影响0人  依然还是或者其他

HashRouter与BrowserRouter

HashRouter:

原理:基于url的hash值,通过监听hash值的改变,从而模拟路由的跳转。

class HashRouter extends React.Component {
  history = createHistory(this.props);

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

BrowserRouter:

H5对history进行了增强,添加了pushState,replaceStae方法和popstate事件。
pushState是添加一个新的历史记录;repalceState是替换当前history;两者都不会导致刷新。
popstate事件则是当历史记录条目更改时,将会触发。

ps:需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)

利用pushState、repalceState实现前端路由跳转,通过对popstate事件的监听,从而补充了浏览器前进后退的功能,从而实现路由。

class BrowserRouter extends React.Component {
  history = createHistory(this.props);

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

在两者组件中,都是使用了history库,来实现路由的基本功能

history库

createHashHistory

//解析hash值得到path
function getHashPath() {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  const href = window.location.href;
  const hashIndex = href.indexOf('#');
  return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
}

listen是实现路由监听,并对外暴露。transitionManager是由createTransitionManager创建,用于统一收集和处理listener;checkDOMListeners是用于创建和销毁hashchange事件。

  function listen(listener) {
    const unlisten = transitionManager.appendListener(listener);
    checkDOMListeners(1);

    return () => {
      checkDOMListeners(-1);
      unlisten();
    };
  }
  function checkDOMListeners(delta) {
    listenerCount += delta;

    if (listenerCount === 1 && delta === 1) {
    //创建hashchange监听事件
      window.addEventListener(HashChangeEvent, handleHashChange);
    } else if (listenerCount === 0) {
    //销毁hashchange事件
      window.removeEventListener(HashChangeEvent, handleHashChange);
    }
  }

  //监听的事件处理
  function handleHashChange() {
    //拿到path
    const path = getHashPath();
    const encodedPath = encodePath(path);

    //是否是相同path
    if (path !== encodedPath) {
      // Ensure we always have a properly-encoded hash.
      replaceHashPath(encodedPath);
    } else {
      const location = getDOMLocation();
      const prevLocation = history.location;

      if (!forceNextPop && locationsAreEqual(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;
      
      //path入栈
      handlePop(location);
    }
  }
  
  function handlePop(location) {
    if (forceNextPop) {
      forceNextPop = false;
      setState();
    } else {
      const action = 'POP';
      
      //通过transitionManager 进行确认是否跳转
      transitionManager.confirmTransitionTo(
        location,
        action,
        getUserConfirmation,
        ok => {
          if (ok) {
            setState({ action, location });
          } else {
            revertPop(location);
          }
        }
      );
    }
  }
  
  //更新history,执行所有listener
  function setState(nextState) {
    Object.assign(history, nextState);
    history.length = globalHistory.length;
    transitionManager.notifyListeners(history.location, history.action);
  }
  

createBrowserHistory

逻辑跟createHashHistory类似,具体处理上有些不同

  function checkDOMListeners(delta) {
    listenerCount += delta;

    if (listenerCount === 1 && delta === 1) {
      window.addEventListener(PopStateEvent, handlePopState);

      if (needsHashChangeListener)
        window.addEventListener(HashChangeEvent, handleHashChange);
    } else if (listenerCount === 0) {
      window.removeEventListener(PopStateEvent, handlePopState);

      if (needsHashChangeListener)
        window.removeEventListener(HashChangeEvent, handleHashChange);
    }
  }
  function handleHashChange() {
    handlePop(getDOMLocation(getHistoryState()));
  }
  
  function handlePop(location) {
    if (forceNextPop) {
      forceNextPop = false;
      setState();
    } else {
      const action = 'POP';

      transitionManager.confirmTransitionTo(
        location,
        action,
        getUserConfirmation,
        ok => {
          if (ok) {
            setState({ action, location });
          } else {
            revertPop(location);
          }
        }
      );
    }
  }
  
  function setState(nextState) {
    Object.assign(history, nextState);
    history.length = globalHistory.length;
    transitionManager.notifyListeners(history.location, history.action);
  }

createTransitionManager

function createTransitionManager() {
  let prompt = null;

  function setPrompt(nextPrompt) {
    warning(prompt == null, 'A history supports only one prompt at a time');

    prompt = nextPrompt;

    return () => {
      if (prompt === nextPrompt) prompt = null;
    };
  }
  //确认跳转
  function confirmTransitionTo(
    location,
    action,
    getUserConfirmation,
    callback
  ) {
    // TODO: If another transition starts while we're still confirming
    // the previous one, we may end up in a weird state. Figure out the
    // best way to handle this.
    if (prompt != null) {
      const result =
        typeof prompt === 'function' ? prompt(location, action) : prompt;

      if (typeof result === 'string') {
        if (typeof getUserConfirmation === 'function') {
          getUserConfirmation(result, callback);
        } else {
          warning(
            false,
            'A history needs a getUserConfirmation function in order to use a prompt message'
          );

          callback(true);
        }
      } else {
        // Return false from a transition hook to cancel the transition.
        callback(result !== false);
      }
    } else {
      callback(true);
    }
  }

  let listeners = [];
  //添加listener,并返回一个删除listener的方法
  function appendListener(fn) {
    let isActive = true;

    function listener(...args) {
      if (isActive) fn(...args);
    }

    listeners.push(listener);

    return () => {
      isActive = false;
      listeners = listeners.filter(item => item !== listener);
    };
  }
  //执行所有的listener
  function notifyListeners(...args) {
    listeners.forEach(listener => listener(...args));
  }
  
  //返回创建的对象
  return {
    setPrompt,
    confirmTransitionTo,
    appendListener,
    notifyListeners
  };
}

export default createTransitionManager;

优劣

HashRouter:
优点:

缺点:

BrowserRouter:
优点:

缺点:

参考:
React-Router 源码解析
你真的了解前端路由吗?
web前端hash路由的实现机制
React SPA 应用 hash 路由如何使用锚点

上一篇下一篇

猜你喜欢

热点阅读