React【 Router 】

React 路由實現的兩種方式

實現機制

  • HashRouter: 基於 window.onhashchange 實現
  • BrowserRouter:基於 HTML5 popstate event 實現

相同點:

  • 都是一個 Router 組件容器,經過建立上下文的方式,傳遞以下數據給後代組件使用;
  • 都須要在組件掛載時機 componentDidMount / useEffect,訂閱 路由變化 的函數,修改相應的 路徑 匹配渲染對應的組件

不一樣點:

  • HashRouter 監聽的是 hashchange 事件
  • BrowserRouter 監聽的是 pushstate & popstate 事件

注意: HTML5 中並無 pushstate 原生事件, 須要自行實現。html

下文中實現的方式均爲 Functional Component 函數式組件react

準備上下文

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()方法).app

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 觸發事件ide

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>
複製代碼
相關文章
相關標籤/搜索