React【 Router 】

  • HashRouter : 基于 window.onhashchange 实现
  • BrowserRouter :基于 HTML5 popstate event 实现

相同点:

  • 都是一个 Router 组件容器,通过创建上下文的方式,传递如下数据给后代组件使用;
  • 都需要在组件挂载时机 componentDidMount / useEffect ,订阅 路由变化 的函数,修改相应的 路径 匹配渲染对应的组件

不同点:

  • HashRouter 监听的是 hashchange 事件
  • BrowserRouter 监听的是 pushstate & popstate 事件

注意: HTML5 中并没有 pushstate 原生事件, 需要自行实现。

下文中实现的方式均为 Functional Component 函数式组件

准备上下文

RouterContext.js
import { createContext } from 'react';
export default createContext();
复制代码

HashRouter 实现

当 一个窗口的 hashURL# 后面的部分)改变时就会触发 hashchange 事件

import React, { useEffect, useState } from 'react';
import RouterContext from './RouterContext';

export default function () {
    const location = {
        pathname: window.location.hash.slice(1) || '/',
        state: null,
    }
    
    let locationState = null;
    
    let [ initialLocation, setInitialLocation ] = useState(location);
    
    useEffect(() => {
        window.addEventListener('hashchange', () => {
            setInitialLocation({
                ...initialLocation,
                pathname: window.location.hash.slice(1) || '/',
                state: locationState,
            })
        })
        window.location.hash = window.location.hash || '#/';
        // 赋值时加 #, 取值时无 #
    });
    
    const history = {
        location: initialLocation,
        
        push(to) {
            if (history.prompt) {
                const target = typeof to === 'string' ? { pathname: to } :to;
                const yes = window.confirm(prompt(target));
                if (!yes) return;
            }
            if (typeof to === 'object') {
                const { pathname, state } = to;
               locationState = state;
               window.location.hash = pathname;
            } else {
                window.location.hash = to;
            }
        },
        
        block(prmopt) {
            history.prompt = prompt;
        },
        
        unblock() {
            history.prompt = null;
        },
        
    }
    
    const routerValue = {
        history,
        location: initialLocation
    };
    
    return (
        <RouterContext value = { routerValue }></RouterContext>
    )
}
复制代码

BrowserRouter 实现

调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件. popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用 history.back()history.forward()history.go() 方法).

import RouterContext from "./RouterContext"
import React, { useEffect, useState } from "react"

export default function (props) {
  let location = {
    pathname: window.location.pathname,
    state: null,
  }
  let [ initialLocation, setInitialLocation ] = useState( location );

  useEffect(() => {
    window.onpushstate = (state, pathname) => {
      setInitialLocation({
        ...initialLocation,
        pathname,
        state,
      })
    }
    window.onpopstate = (event) => {
      setInitialLocation({
        ...initialLocation,
        pathname: window.location.pathname,
        state: event.state,
      })
    }
  });

  const globalHistory = window.history;
  
  let history = {
    location: initialLocation,

    push(to) {
      if (history.prompt) {
        let target = typeof to === 'string' ? { pathname: to } : to;
        let yes = window.confirm(history.prompt(target));
        if (!yes)  return;
      }
      if (typeof to === 'object') {
        let { pathname, state } = to;
        globalHistory.pushState(state, null, pathname);
      } else {
        globalHistory.pushState(null, null, to);
      }
    },
  
    block(prompt) {
      history.prompt = prompt;
    },
    
    unblock() {
      history.prompt = null;
    }
  }

  const routerValue = {
    history,
    location: initialLocation,
  };

  return (
    <RouterContext.Provider value={ routerValue }>
      { props.children }
    </RouterContext.Provider>
  )
}
复制代码

重写 pushState 方法

方法体内部增加 onpushstate 触发事件

index.html
<script>
!( history => {
    const pushState = history.pushState;
    history.pushState = function (state, title, url) {
        if (typeof window.onpushstate === 'function') {
            window.onpushState(state, url);
        }
        pushState.apply(history, arguments);
    }
})(window.history)
</script>
复制代码
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章