找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1679

积分

0

好友

215

主题
发表于 3 天前 | 查看: 15| 回复: 0

原文地址:https://javascript.plainenglish.io/stop-using-react-router-30-lines-does-the-same-thing-174999058825
原文作者: Ignatius Sani

你知道吗?React Router v6 在 gzip 压缩后的大小仍有大约 18KB。这个数字看似不大,但对于许多功能简单的单页应用来说,其代码量甚至超过了应用本身的业务逻辑

想象一下你的应用:可能只有五个路由,或者野心大一点,有十个。一个着陆页、一个关于页面、一个联系表单,或许再加一个小型仪表盘。但很多时候,我们在一开始就会不假思索地引入完整的大型路由框架,却从未停下来问一个简单的问题:我们真的需要它吗?

上个月,我从三个生产环境的 React 应用中移除了 React Router。这些并非玩具项目,而是拥有真实用户的真实应用。结果呢?打包体积减少了 18KB。用户行为没有任何变化,没有引入新的 Bug,代码反而变得更容易理解

这并非在贬低 React Router。它是一个成熟、维护良好的库,确实解决了复杂问题。当你的应用确实需要它时,我仍然推荐使用。

但本文的观点是,大多数 React 应用其实并不需要如此重量级的路由方案。这不是一篇“永远不要用 React Router”的檄文,而是帮助你理解:什么时候你并不需要它,以及当你选择“简单优先”时,一个可行的替代方案是什么样子。


React Router 到底提供了什么?

在替换任何工具之前,先诚实地看看它究竟解决了哪些问题。从本质上讲,React Router 提供了几个核心能力:

  • 将 URL 映射到 React 组件。
  • 保持 UI 与浏览器历史记录同步。
  • 在不刷新页面的情况下进行导航。
  • 访问查询参数 (query parameters)。
  • 提供 404 页面作为后备。

这些都是合理的需求,路由也确实是一个需要解决的问题。但这些问题并非神秘莫测或尚未被攻克。浏览器本身就提供了强大的 History API,而 React 本身也已经具备了 state 管理和 context 机制。当你把这两者结合起来看,React Router 所做的很多事情,本质上就是一层“胶水代码”。

这并不意味着 React Router 糟糕,只是说明:它并非总是必要的


路由本质上就是共享 state

如果我们把路由问题拆解到最基础的层面,其实就一句话:当前的 URL 决定了应该渲染哪个组件

仅此而已。而在 React 的世界里,这本质上就是一个共享 state 的问题。我们早就知道如何处理共享 state:使用 context

一旦你从这个角度思考路由,问题就变小了。我们不再需要引入一个完整的框架,而是可以利用 React 的状态管理和浏览器的 History API,自己构建一个极简的路由器。


一个仅30行的最小化 Router 实现

下面就是一个完整、最小化实现的路由器。它没有任何外部依赖,没有多余的抽象,只有 React 和浏览器在做它们本来就擅长的事情。

import { useState, useEffect, useContext, createContext } from 'react';

const RouterContext = createContext(null);

function RouterProvider({ children }) {
  const [currentPath, setCurrentPath] = useState(
    typeof window !== 'undefined'
      ? window.location.pathname
      : '/'
  );

  useEffect(() => {
    const handlePopState = () => {
      setCurrentPath(window.location.pathname);
    };

    window.addEventListener('popstate', handlePopState);
    return () =>
      window.removeEventListener('popstate', handlePopState);
  }, []);

  const navigate = (path) => {
    window.history.pushState({}, '', path);
    setCurrentPath(path);
  };

  return (
    <RouterContext.Provider value={{ currentPath, navigate }}>
      {children}
    </RouterContext.Provider>
  );
}

function useRouter() {
  const context = useContext(RouterContext);
  if (!context) {
    throw new Error(
      'useRouter must be used inside RouterProvider'
    );
  }
  return context;
}

function Router({ routes }) {
  const { currentPath } = useRouter();
  return routes[currentPath] || routes['/404'] || null;
}

function Link({ to, children, ...props }) {
  const { navigate } = useRouter();

  const handleClick = (e) => {
    e.preventDefault();
    navigate(to);
  };

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

export { RouterProvider, Router, Link };

这就是一个完整路由器的核心。 它没有复杂的配置文件,没有层层嵌套的抽象,也没有大版本升级带来的破坏性变更。


在真实应用中的使用方式

在实际项目中,你可以这样使用上面实现的路由器:

import { RouterProvider, Router, Link } from './router';

function Home() {
  return (
    <>
      <h1>Home</h1>
      <Link to="/about">About</Link>
    </>
  );
}

function About() {
  return (
    <>
      <h1>About</h1>
      <Link to="/">Home</Link>
    </>
  );
}

function NotFound() {
  return <h1>404 — Page Not Found</h1>;
}

const routes = {
  '/': <Home />,
  '/about': <About />,
  '/404': <NotFound />
};

function App() {
  return (
    <RouterProvider>
      <Router routes={routes} />
    </RouterProvider>
  );
}

这样,你的应用就具备了基本的前端路由功能:

  • 后退按钮可用。
  • 前进按钮可用。
  • 链接点击即时响应,没有整页刷新。

从用户的视角来看,这完全是一个标准的单页应用 (SPA) —— 因为它确实就是


如何处理 Query Parameters?

一个常见的疑问是:查询参数 (query parameters) 怎么办?

React Router 为此提供了专门的辅助工具,但查询字符串本身也是浏览器状态的一部分。我们可以用一个非常简洁的自定义 Hook 来处理它:

function useQueryParams() {
  const [params, setParams] = useState(
    new URLSearchParams(window.location.search)
  );

  useEffect(() => {
    const handlePopState = () => {
      setParams(
        new URLSearchParams(window.location.search)
      );
    };

    window.addEventListener('popstate', handlePopState);
    return () =>
      window.removeEventListener('popstate', handlePopState);
  }, []);

  return params;
}

使用方式非常直观:

function SearchPage() {
  const params = useQueryParams();
  const query = params.get('q');

  return <h1>Searching for: {query}</h1>;
}

现在,访问 /search?q=javascript 这样的 URL 将完全符合预期,组件可以正确地获取并显示查询参数。


权衡取舍

当然,这种极简方案并非没有代价。选择之前,你需要清楚地了解自己放弃了什么。

你会失去什么

  • 嵌套路由:这个路由器只支持扁平的路由结构。
  • 动态路由参数:像 /users/:id 这样的路径模式,需要你额外编写路径匹配逻辑。
  • 自动滚动恢复:React Router 已经帮你处理好了页面切换时的滚动位置。
  • 代码分割的便利性:你仍然可以使用 React.lazy(),但 React Router 与之集成更顺手。
  • 久经考验的成熟方案:React Router 被数百万应用使用和测试过,而这个方案没有。

如果你的应用确实需要上述能力,那么 React Router 绝对是正确的、值得使用的工具。

本文的目的在于提供一种思考方式:对于许多中低复杂度的项目,路由的本质可以如此简单。理解这一点,能帮助你在技术选型时做出更明智的决定,避免不必要的依赖和复杂度。希望这篇文章能为你构建更轻量、更高效的 Web 应用带来启发。欢迎在云栈社区与更多开发者交流前端工程化实践。




上一篇:React组件库设计:通过工厂模式实现类型安全的复合组件实践
下一篇:Spring Context应用上下文深度解析:Java配置、XML与微服务实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 02:48 , Processed in 0.285382 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表