美文网首页
React Router

React Router

作者: 依然还是或者其他 | 来源:发表于2020-06-10 00:45 被阅读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:
    优点:

    • 是兼容性好,兼容到ie8,且大多数前端框架均有hash路由的实现
      
    • 不需要服务端进行设置
      
    • 除资源加载和ajax请求外,无其他请求
      

    缺点:

    • 对于部分需要重定向的操作,后端无法获取hash部分内容,导致后台无法取得url中的数据,典型的例子就是微信公众号的oauth验证
      
    • 服务器端无法准确跟踪前端路由信息
      
    • 对于需要锚点功能的需求会与目前路由机制冲突
      

    BrowserRouter:
    优点:

    • 对于重定向过程中不会丢失url中的参数。后端可以拿到这部分数据
    • 绝大多数前段框架均提供了browser的路由实现
    • 后端可以准确跟踪路由信息
    • 可以使用history.state来获取当前url对应的状态信息

    缺点:

    • 兼容性不如hash路由(只兼容到IE10)
    • 需要后端支持,每次返回html文档(可以通过nginx代理映射到index.html)

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

    相关文章

      网友评论

          本文标题:React Router

          本文链接:https://www.haomeiwen.com/subject/mpwytktx.html