Skip to content

react-router 几种模式及实现原理

  • 利用关键 api,替换路由组件,但是不触发页面刷新

1. 路由模式概述

React Router 主要提供三种路由模式:

模式原理适用场景URL 示例
BrowserRouterHTML5 History API现代浏览器单页应用https://example.com/about
HashRouterURL Hash传统浏览器、静态部署https://example.com/#/about
MemoryRouter内存路由测试、非浏览器环境无 URL 变化

2. BrowserRouter 实现原理

核心:HTML5 History API

js
// History API 主要方法
window.history.pushState(state, title, url); // 添加历史记录
window.history.replaceState(state, title, url); // 替换当前历史记录
window.history.back(); // 后退
window.history.forward(); // 前进
window.history.go(n); // 跳转到指定历史记录

// popstate 事件监听浏览器前进后退
window.addEventListener("popstate", (event) => {
  console.log("Location changed:", window.location.pathname);
});

BrowserRouter 实现源码解析

js
class BrowserRouter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      location: window.location,
    };

    // 监听 popstate 事件(浏览器前进后退)
    this.unlisten = history.listen((location) => {
      this.setState({ location });
    });
  }

  componentWillUnmount() {
    this.unlisten();
  }

  render() {
    return (
      <Router
        location={this.state.location}
        navigator={history}
        navigationType={this.state.action}
      >
        {this.props.children}
      </Router>
    );
  }
}

创建自定义 History 对象

js
// 自定义 history 实现
function createBrowserHistory() {
  const globalHistory = window.history;
  let listeners = [];
  let action = "POP";
  let state;

  function push(path, state) {
    action = "PUSH";
    const location = createLocation(path, state);
    globalHistory.pushState(state, "", path);
    notifyListeners(location);
  }

  function replace(path, state) {
    action = "REPLACE";
    const location = createLocation(path, state);
    globalHistory.replaceState(state, "", path);
    notifyListeners(location);
  }

  function go(n) {
    globalHistory.go(n);
  }

  function listen(listener) {
    listeners.push(listener);

    // 返回取消监听函数
    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  }

  function notifyListeners(location) {
    listeners.forEach((listener) => listener(location));
  }

  // 处理浏览器前进后退
  window.addEventListener("popstate", () => {
    action = "POP";
    const location = createLocation(window.location.pathname);
    notifyListeners(location);
  });

  return { push, replace, go, listen };
}

3. HashRouter 实现原理

核心:URL Hash 变化

js
// Hash 变化的监听
window.addEventListener("hashchange", () => {
  console.log("Hash changed:", window.location.hash);
});

// 手动修改 hash
window.location.hash = "#/about"; // 会触发 hashchange

HashRouter 实现源码解析

js
class HashRouter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      location: {
        pathname: window.location.hash.slice(1) || "/",
        search: window.location.search,
        hash: window.location.hash,
        state: null,
      },
    };

    // 监听 hashchange 事件
    this.hashChangeHandler = () => {
      this.setState({
        location: {
          ...this.state.location,
          pathname: window.location.hash.slice(1) || "/",
        },
      });
    };

    window.addEventListener("hashchange", this.hashChangeHandler);
  }

  componentWillUnmount() {
    window.removeEventListener("hashchange", this.hashChangeHandler);
  }

  navigate = (to, state) => {
    window.location.hash = to;
  };

  render() {
    return (
      <Router
        location={this.state.location}
        navigator={{
          push: this.navigate,
          replace: (to) => {
            // replace 实现较复杂,需要操作历史记录
            const hash = to.startsWith("#") ? to : `#${to}`;
            window.location.replace(window.location.pathname + hash);
          },
        }}
      >
        {this.props.children}
      </Router>
    );
  }
}

4. MemoryRouter 实现原理

核心:内存中管理路由状态

js
class MemoryRouter extends React.Component {
  constructor(props) {
    super(props);
    this.navigate = this.navigate.bind(this);
    this.history = {
      entries: [{ pathname: props.initialEntries?.[0] || "/" }],
      index: 0,
      push: (to) => this.navigate(to, "PUSH"),
      replace: (to) => this.navigate(to, "REPLACE"),
      go: (n) => {
        const newIndex = this.history.index + n;
        if (newIndex >= 0 && newIndex < this.history.entries.length) {
          this.history.index = newIndex;
          this.setState({ location: this.history.entries[newIndex] });
        }
      },
    };

    this.state = {
      location: this.history.entries[this.history.index],
    };
  }

  navigate(to, action) {
    const location = typeof to === "string" ? { pathname: to } : to;

    if (action === "PUSH") {
      this.history.entries = this.history.entries.slice(
        0,
        this.history.index + 1
      );
      this.history.entries.push(location);
      this.history.index = this.history.entries.length - 1;
    } else if (action === "REPLACE") {
      this.history.entries[this.history.index] = location;
    }

    this.setState({ location });
  }

  render() {
    return (
      <Router
        location={this.state.location}
        navigator={this.history}
        navigationType={this.state.action}
      >
        {this.props.children}
      </Router>
    );
  }
}

5. Router 核心组件实现

基础 Router 组件

js
const NavigationContext = React.createContext();
const LocationContext = React.createContext();

class Router extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      location: props.location,
      action: props.navigationType || "POP",
    };

    // 监听导航变化
    this.unlisten = props.navigator.listen((location, action) => {
      this.setState({ location, action });
    });
  }

  componentWillUnmount() {
    this.unlisten();
  }

  render() {
    return (
      <NavigationContext.Provider value={this.props.navigator}>
        <LocationContext.Provider value={this.state}>
          {this.props.children}
        </LocationContext.Provider>
      </NavigationContext.Provider>
    );
  }
}

6. 路由匹配原理

Route 组件实现

js
function Route({ path, element, children }) {
  const { location } = useContext(LocationContext);
  const match = matchPath(path, location.pathname);

  if (!match) return null;

  return React.cloneElement(element, {
    match,
    location,
    params: match.params,
  });
}

// 路径匹配算法
function matchPath(pattern, pathname) {
  const keys = [];
  const regex = pathToRegexp(pattern, keys);
  const match = regex.exec(pathname);

  if (!match) return null;

  const params = {};
  keys.forEach((key, index) => {
    params[key.name] = match[index + 1];
  });

  return {
    params,
    path: pattern,
    url: pathname,
    isExact: match[0] === pathname,
  };
}

// 路径转正则表达式(简化版)
function pathToRegexp(path, keys) {
  const pattern = path
    .replace(/:(\w+)/g, (_, key) => {
      keys.push({ name: key });
      return "([^/]+)";
    })
    .replace(/\*/g, "(.*)");

  return new RegExp(`^${pattern}$`);
}

7. 导航组件实现

js
function Link({ to, children, ...props }) {
  const navigator = useContext(NavigationContext);

  const handleClick = (event) => {
    event.preventDefault();
    navigator.push(to);
  };

  return (
    <a href={to} onClick={handleClick} {...props}>
      {children}
    </a>
  );
}

// NavLink 增强版(支持 active 状态)
function NavLink({
  to,
  style,
  className,
  activeStyle,
  activeClassName,
  ...props
}) {
  const { location } = useContext(LocationContext);
  const isActive = location.pathname === to;

  const linkStyle = isActive ? { ...style, ...activeStyle } : style;
  const linkClassName = isActive
    ? `${className} ${activeClassName}`.trim()
    : className;

  return (
    <Link to={to} style={linkStyle} className={linkClassName} {...props} />
  );
}

8. 编程式导航

useNavigate Hook 实现

js
function useNavigate() {
  const navigator = useContext(NavigationContext);
  const { location } = useContext(LocationContext);

  return function navigate(to, options = {}) {
    if (options.replace) {
      navigator.replace(to, options.state);
    } else {
      navigator.push(to, options.state);
    }
  };
}

// useLocation Hook
function useLocation() {
  return useContext(LocationContext).location;
}

// useParams Hook
function useParams() {
  const { match } = useContext(RouteContext);
  return match ? match.params : {};
}

9. 路由守卫实现

自定义路由保护

js
function ProtectedRoute({ children, requiredRole }) {
  const location = useLocation();
  const user = useUser(); // 自定义 hook 获取用户信息

  if (!user.isAuthenticated) {
    // 重定向到登录页
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (requiredRole && user.role !== requiredRole) {
    // 权限不足
    return <div>Access Denied</div>;
  }

  return children;
}

// 使用示例
<Route
  path="/admin"
  element={
    <ProtectedRoute requiredRole="admin">
      <AdminPage />
    </ProtectedRoute>
  }
/>;

10. 性能优化

路由懒加载

js
import { lazy, Suspense } from "react";

const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

上次更新于: