React Router 从v3升级到v4的踩坑之旅

2023-11-12

React 应用很少不用react-router这个包的。marknoteapp.com之前一直用v3,看到v4出来后一直心痒。最近,抱着 用新不用旧 的理念,手贱升了一下级。这一升级,差不多2天功夫花掉啦。

概述

和 Angular 那改朝换代般的升级相比,React技术栈一直以其稳定的 API 而备受好评。不过,这次 react-router 从v3到v4的升级,简直是砸 React 的好名声的。

如果你以为这个升级只是在 package.json 中简单的改一下版本号就好了,那你就大错特错了。我相信,你改完版本号之后,会像我当初一样面对成堆的问题而惊奇不已。

事实上,不少开发者对v4的的变化感到惊诧,比如下面的这位就抱怨v3升v4花了10天。

图片描述

总之,v3到v4不是一件几分钟就可以搞定的事。一个中型的项目,花2-3天在升级上很正常。

重要的事情说三遍:升级之前先备份!升级之前先备份!!升级之前先备份!!!

备份完之后,就和我一起来踩坑吧!

坑一: 包的选择

react router v4 是对v3的重写。现在分为三个包:

  • react-router:只提供核心的路由和函数。一般的应用不会直接使用;
  • react-router-dom:供浏览器/Web应用使用的API。依赖于react-router, 同时将react-router的API重新暴露(export)出来;
  • react-router-native:供 React Native 应用使用的API。同时将react-router的API重新暴露(export)出来;

对于一般的web应用,直接引入第二个包即可,安装之前先删除老版本react-router:

npm uninstall react-router --save
npm install --save react-router-dom

或者你像我一样,更亲睐yarn的话:

yarn remove react-router
yarn add react-router-dom

引入完之后,要做的第一件事就是将所有原先引入react-router的地方都换成react-router-dom。

// 原先
import { Route, Redirect } from 'react-router';
// 现在
import { Route, Redirect } from 'react-router-dom';

坑二:不再用,要用具体的实现

在V3中,一般使用,只是在其history属性中指定是哪种实现: hashHistory或者browserHistory之类的。

// v3
import { Router, hashHistory } from 'react-router';
const MyApp = () => (
     <Router history={hashHistory}>
       ...
    </Router>
);

而在v4中,你需要直接用具体的实现,比如HashRouter或者BrowserRouter:

// v4
import { HashRouter } from 'react-router-dom';
const MyApp = () => (
    <HashRouter>
        ...
    </HashRouter>
);

坑三: 对于redux应用,你需要用react-router-redux

如果你像我一样,也用了redux的话,那么恭喜你,你还得用react-router-redux。

而且,这个包的正式版4.x不支持react-router v4。你需要用 alpha 版 的react-router-redux。在package.json 里加入react-router-redux~5.0.0或者用yarn:

yarn add react-router-redux@5.0.0

虽然我用的是alpha 6,貌似也没啥毛病。

而且,对于redux应用,你不可以用BrowserRouter,或者HashRouter,而应该使用ConnectedRouter。

比如,我的代码:

import { Route } from 'react-router-dom';
import history from 'history/createBrowserHistory';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import App from './components/App';
ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history()}>
      <Route path="/" component={App} />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

坑四:嵌套 Route和Index Route 全失效啦

在V3中,嵌套Route和Index Route的使用是很常见的。比如marknoteapp.com中,博客部分的路由定义如下:

<Route path="/blog/:bloggerId" component={BlogLayout}>
          <IndexRoute component={BlogList}/>
          <Route path="/blog/:bloggerId/settings" component={BlogConfig}/>
          <Route path="/blog/:bloggerId/about" component={BlogAbout}/>
          <Route path="/blog/:bloggerId/dashboard" component={BlogDashboard}/>
         <Route path="/blog/:bloggerId/:postId" component={BlogPost}/>
</Route>

这样/blog/123 会触发BlogLayout,和BlogList,而/blog/123/456则会触发BlogLayout和BlogPos。很方便。

在 v4 中 IndexRoute没了,然后不允许Route嵌套。

要实现类似的功能,需要两步:

  • 在父组件中将路由给”/blog/:blggerId”给”BlogLayout”,代码如下:
<Switch>
           <Route path="/blog/:bloggerId" component={BlogLayout} />
</Switch>
  • 然后在BlogLayout中定义Blog这部分的路由:
<div className='content' id="blog-content">
     <Switch>
          <Route exact path="/blog/:bloggerId/settings" component={BlogConfig} />
          <Route exact path="/blog/:bloggerId/about" component={BlogAbout} />
         <Route exact path="/blog/:bloggerId/dashboard" component={BlogDashboard} />
         <Route exact path="/blog/:bloggerId/:postId" component={BlogPost} />
         <Route exact path="/blog/:bloggerId" component={BlogList} />
     </Switch>
</div>

这里有几点需要注意:

  • 在 v3 中,Router 的定义一般是全局的,所有的路由都在一个文件中定义;而在 v4 中Router可以出现在任何组件中。
  • 在 v4 中,Router 在UI组件中加载对应的子 UI 组件。比如,我的上面的代码中,路由会根据URL 匹配来在BlogConfig/BlogAbout/BlogDashboard/BlogPost/BlogList选择一个,加载进来,并显示在id为 blog-content 的 div 中。
  • Switch 用来从多个Route中选出一个。否则会触发多个组件。
  • exact 这个属性来表示精确匹配,否则,URL /blog/123/456 也可以触发BlogList。
  • 由于v4中没有了IndexRoute,将BlogList 的 path声明得和它的父组件一样就可以了。
  • 在v4中,path属性永远是绝对路径。如果你不想重复当前级别的路径,可以使用match这个属性。

比如在我的代码中, 在BlogLayout中,我可以 match.url 来代替“/blog/:bloggerId”,代码如下:

<div className='content' id="blog-content">
      <Switch>
            <Route exact path={"${match.url}/settings"}} component={BlogConfig} />
            <Route exact path={"${match.url}/about"} component={BlogAbout} />
            <Route exact path={"${match.url}/dashboard"} component={BlogDashboard} />
            <Route exact path={"${match.url}/:postId"} component={BlogPost} />

            <Route exact path={"${match.url}}component={BlogList} />

      </Switch>
</div>

坑五:不再可以从params中取URL参数了

在 v3 中,你可以从params这个属性中取到URL 中传递过来的参数。比如我在 BlogLayout中获取bloggerID,v3的代码如下:

//v3
const bloggerId = this.props.params.bloggerId;

这个属性在v4中不再被自动注入了,需要从match属性中获取。代码如下:

//v4
const bloggerId = this.props.match.params.bloggerId;

这里还有一个小小的差异。在v3中,参数会自动解码,而在v4中不会。所以如果你的URL参数中有特殊字符的话,你可能需要自己调decodeURI之类的方法解码。

坑六:Route的onEnter, onUpdate和onLeave之类的事件没有了

在v3中,我可以使用使用 Route的 onEnter, onUpdate和 onLeave事件来做一些事情。

比如,我可以在onUpdate中触发google analytics来跟踪用户行为:

//v3
<Router history={browserHistory} onUpdate={doLogPageView} >

这里doLogPageView实现如下:

function doLogPageView(){
  const sUrl = window.location.pathname + window.location.search;

  ReactGA.set({ page: sUrl });
  ReactGA.pageview(sUrl);
}

在v4中,Route的事件没了。我查了很多资料,貌似有两种解决办法:

  • 将代码移到每个组件的componentWillMount 方法中去。这意味着每个组件都要做这样的事情,代码冗余;
  • 使用withRouter 这个HOC (高阶组件);

我用的后一种方法,代码如下:

import { withRouter } from 'react-router-dom';
import ReactGA from 'react-ga';
class App extends Component {

  componentWillMount() {
    this.setState(
      {
         height: window.innerHeight,
        sidebarOpen: false,
        docked: true,
      }
    );
    this.onSetSidebarOpen = this.onSetSidebarOpen.bind(this);

    this.props.history.listen((location, action) => {
      const url = location.pathname + location.search + location.hash;
      ReactGA.set({ page: url });
      ReactGA.pageview(url);
      console.log(`current URL:${url}`);

     });
  }
}
export default withRouter(App);

总结

简单的说,react-router v4 简直不像是v3的升级,而更像一次天马行空的重写。其理念是完全不一样的。v3 更像是AOP (面向切面编程)的样子:路由全局配置,可以通过 事件进行一些共通的处理;v4 用react-router 团队的话说是 real component,可以嵌入任何组件中。

据说 facebook 的react 团队对 v3之前的react-router 一直没有好感,而v4之后则颇多溢美之词。

但是客观的讲,从v3升级到 v4遇到的问题还是比较多的。尤其是官方居然没有一个全面的指南。虽然在 这里 有一个文档,但是很多实际升级时会遇到的问题都没有涉及。

你的项目用的v3 还是 v4?需要升级吗?升级遇到什么问题,可以在下面留言,一起探讨。

参考

感谢作者marknote授权发布。
作者简介: marknote,个人开发者,专注iOS和web相关技术,喜好尝试新技术,热爱分享。 个人邮箱:marknote@aliyun.com,点击查看: 博客地址简书个人主页

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React Router 从v3升级到v4的踩坑之旅 的相关文章

  • React 定时刷新接口

    通过 useEffect 在页面加载时调用 getNodeDetailList 列表接口 useEffect gt getNodeDetailList change 然后通过 setInterval 来进行定时刷新 useEffect gt
  • reactJS 干货(reactjs 史上最详细的解析干货)

    一 State和 Props state是状态机 应该包括 那些可能被组件的事件处理器改变并触发用户界面更新的数据 譬如需要对用户输入 服务器请求或者时间变化等作出响应 不应该包括 计算所得数据 React组件 在render 里使用pro
  • Bookmarklet:将隐藏的 iframe 附加到页面并加载 url

    我有一个用于重置路由器的书签 我只需要访问并让页面完成加载 然后我的路由器开始重置 javascript function w window open http 192 168 0 1 goform formReboot width 1 h
  • ZF2:获取应用程序布局中的模块名称(或路由)以突出显示菜单

    如何在 ZF2 中获取应用程序布局中当前 选定的 模块名称 目的 我的应用程序布局包括zf2应用程序的主菜单 每次选择模块时我需要突出显示该模块的菜单语音 另外 当用它制作菜单时 我需要设置正确的路线 url action 每个模块都有菜单
  • 如何将数据注入到从路由器创建的 Angular2 组件中?

    我目前正在尝试为我们的 Angular1 应用程序 相当复杂的一个 构建 Angular2 原型 基于 alpha44 并且我正在尝试在使用路由和子路由时找到最佳的模型 数据架构 在我的示例中 从从路由创建的子组件中 我想访问父组件的属性
  • RouterModule.forRoot(ROUTES) 与 RouterModule.forChild(ROUTES)

    这两者之间有什么区别 各自的用例是什么 The docs并不完全有帮助 forRoot 创建一个包含所有指令的模块 给定的 路由 以及路由器服务本身 forChild 创建一个模块 包含所有指令和给定的路由 但不包括 路由器服务 我模糊的猜
  • 找出路由器和接收器之间的距离?

    一个一般性问题 是否可以检索有关距离的信息 例如计算机来自 wifi 路由器 例如 如果我距离我的家庭 wifispot 10 米或 2 米 我想在我的计算机上获取数据 知道这是否可能吗 编辑 蓝牙怎么样 是否可以获得有关蓝牙连接设备之间的
  • Angular:路由器、子项(可选)参数

    所以 我希望生成这样的路径 比赛 页面 球队 赛季 其中 team 和 season 是可选参数 所以我可以有一个像这样的网址 matches results 4 2017 or matches results 4 or matches r
  • React Jsx转换成真实DOM过程?

    面试官 说说React Jsx转换成真实DOM过程 一 是什么 react 通过将组件编写的 JSX 映射到屏幕 以及组件中的状态发生了变化之后 React 会将这些 变化 更新到屏幕上 在前面文章了解中 JSX 通过 babel 最终转化
  • 如何在角度应用程序中对 router.navigate 进行单元测试 [重复]

    这个问题在这里已经有答案了 我正在为角度应用程序运行单元测试 我想对导航在角度应用程序中是否正常工作进行单元测试 if this customer length 0 this router navigate nocustomer 以及这个的
  • 如何从父路由器或父路由器检查活动子路由器?

    如何检查子路由器是否活动 在角度4中显示状态真或假 现在我正在使用 角度 cli 1 4 4 节点 8 6 0 打字稿 2 3 4 角度 路由器 4 4 4 我的家长路线是 const routes Routes path componen
  • Express GET 路由不适用于参数

    我是 Express 和 Mongoose 的新手 我目前正在开发我的第一个项目 这不是教程 我遇到了问题 我有多个路由 它们在 index js 中定义如下 app use api client require routes client
  • 在反应路由器中刷新时出现空白页面

    当我从 home 导航到 dashboard 时 路由器工作正常 但是当我从 home 导航到 profile id 时 路由器将我导航到也工作正常的配置文件页面 但是当我刷新它时 它会变成空白页面并且没有给我任何 404 或重定向回主页
  • 如何以正确的方式在 Angular 8 中重新加载页面

    注意 我通过谷歌搜索得到了一组结果 但是 正如我在最后解释的那样 我觉得由于多样性 它们并不可靠 我有两种实用方法 一种用于导航到父节点 另一种用于重新加载自身 第一个按预期工作 而另一个则无法导致重新加载 navigateToParent
  • Angular 2:使用函数生成路由链接/动态生成routerLink

    我想动态创建路线链接 我已经随页面加载了 html 但是 我需要在 URL 中使用 id 该 id 稍后可用 HTML a href Add Content to Cartoon Box a 此路由链接中的链接包含卡通盒的 id carto
  • 主干路由器侦听器未命中哈希标签

    我的问题是 当我在链接中使用哈希标签时 它只是将哈希附加到 url 而不是转到路由 例如 ToDo public offline2 html test 如果我离开tag 由于某种原因它确实会转到路由器 我已经用以下代码关闭了 jquery
  • 有条件地禁用 Angular 中的路由器链接

    我在 Angular 项目 使用 Angular 2 中有许多链接 类似于 a Link a 我想根据上下文 状态禁用其中一些 通过更改颜色并防止操作发生 对于样式 我已添加到链接 class disabled isValidLink 这让
  • ZMQ 模式经销商/路由器心跳

    我在客户端有一个经销商套接字 它连接到服务器端的路由器套接字 我经常看到心跳机制 服务器定期向客户端发送消息 以便客户端知道自己是否正确连接到服务器 以便客户端在一段时间内没有收到消息时可以重新连接 例如这里的偏执海盗模式 http zgu
  • Angular2 条件路由

    这可能是一个基本问题 但是在 Angular2 中是否有任何方法可以进行条件路由 或者 有人会在路由器之外这样做吗 我知道 ui router 有一定的能力做到这一点 但我在 Angular2s 路由器中没有看到类似的东西 如上所述 角路线
  • 我可以从 HTTP 请求中找到无线接入点的 BSSID(MAC 地址)吗?

    假设有人在咖啡店里无线连接到互联网 并向 johnsveryownserver com 发送 HTTP 请求 服务器端 有什么方法可以确定我的MAC地址吗 无线接入点他们连接到什么 请注意 我对他们机器的 MAC 地址不感兴趣 如果我无法使

随机推荐

  • 【binkw32.dll下载】binkw32.dll缺失怎么办

    binkw32 dll文件对一些电脑软件 电脑游戏等程序的正常运行起到关键性作用 对于弹出缺少此类文件的弹窗 用户们很多时候也摸不着头脑 程序明明上次都能正常运行 突然就弹出缺少binkw32 dll文件的提醒窗口 通过小编此次编辑的文章
  • 修改DIV滚动条样式

    滚动条样式 div webkit scrollbar 滚动条整体样式 width 5px 高宽分别对应横竖滚动条的尺寸 height 5px div webkit scrollbar thumb 滚动条里面小方块 border radius
  • 剖析ElasticSearch的评分计算过程

    剖析elasticsearch的评分计算过程 es搜索结果是怎样的排序的 准备测试数据 搜索 剖析参数含义 结论 es搜索结果是怎样的排序的 es的排序准则的相关度 根据搜索 关键词 计算关键词在一个文档中的得分 得分越高结果越靠前 那么计
  • github服务器停止响应,如何解决“git pull,致命:无法访问'https://github.com ... \':服务器空回复”...

    当我使用Git命令 git pull 更新我的存储库时 消息如下 致命 无法访问 来自服务器的空回复 如何解决 git pull 致命 无法访问 https github com 服务器空回复 而且我尝试使用GitHub的应用程序 但此提醒
  • QT5开发之 信号与槽机制

    文章目录 什么是信号与槽 信号与槽原理 如何实现信号与槽机制 实现方式 UI方式 代码方式 QT4 QObject类 connect和disconnect 连接函数 QT4 QT5使用 找到类与类的信号与槽函数 QT4 QT5使用 举例 总
  • Windows下 Cppcheck 的使用教程

    1 Cppcheck是什么 CppCheck是一个C C 代码缺陷静态检查工具 不同于C C 编译器及其它分析工具 CppCheck只检查编译器检查不出来的bug 不检查语法错误 所谓静态代码检查就是使用一个工具检查我们写的代码是否安全和健
  • 计算机窗口移动不了怎么办,电脑鼠标拖不动文件怎么办 电脑鼠标拖动不灵敏如何解决...

    在我们使用电脑的时候 往往都会用到鼠标拖动文件 不知道有没有遇到过电脑鼠标拖不动文件的时候 这种情况大家是怎么解决的呢 不知道没关系 下面小编为大家带来电脑鼠标拖不动文件的解决方法 大家可以按照下面的步骤即可解决 电脑鼠标拖不动文件怎么办
  • 支付宝支付回调,回调日志记录

    1 支付报支付回调方法 public function aliPayNotify try app PayService alipay collect app gt verify collectData collect gt all 获取支付
  • 【Zabbix实战之运维篇】Zabbix监控平台的简单性能调优

    Zabbix实战之运维篇 Zabbix监控平台的简单性能调优 一 Zabbix性能优化介绍 1 造成Zabbix服务器变慢原因 2 Zabbix性能调优的方法 二 检查Zabbix服务器的资源占用情况 1 检查Zabbix各组件容器的资源占
  • [转载]YUV格式纹理贴图的例子

    frameworks native opengl tests gl2 yuvtex gl2 yuvtex cpp 是Android提供的yuv格式纹理贴图的例子 前面先申请存放纹理数据的buffer yuvTexBuffer new Gra
  • 根据哈夫曼树构建哈夫曼编码

    实验题 构造哈夫曼树生成哈夫曼编码 编写一个程序 构造一棵哈夫曼树 输出对应的哈夫曼编码和平均查找长度 并对下表 所示的数据进行验证 单词及出现的频度 单词 The of a to and in that he is at on for H
  • Github下载任意版本的VsCode

    下载历史版本VsCode zip 下载链接由三部分组成 固定部分 commit id VSCode win32 x64 版本号 zip 固定部分 https vscode cdn azure cn stable Commit id 打开 v
  • 嵌入式linux通过systemd自启动一个python代码

    一直想实现一段自启动的代码 今天尝试了下 成功了 做个记录 首先 我用的是imx6ull处理器 嵌入式linux内核版本为4 9 88 然后 上位机用的虚拟机ubuntu22 04 01 先在ubuntu上面试了试 能够自启动 然后再下载到
  • linux系统调用(持续更新....)

    随着自己接触越来越多的linux的系统函数发现自己在linux系统调用方面有很多不足 每次遇到系统调用函数都要百度一遍看一下用法 所以我打算写一篇博客来记录在开发过程遇到的系统调用函数 方便自己查阅 本文持续更新 1 popen 函数 2
  • Unity-协同程序

    知识点一 Unity是否支持多线程 1 首先要明确一点Unity是支持多线程的 只是新开线程无法访问Unity相关对象的内容 Unity中的多线程 要记住关闭 Unity中去使用 如果说 我们一开始在Start内创建一个多线程 那么我们无法
  • 【算法】最近点对问题(暴力破解法)

    简单的画了一张图 通过暴力方式 进行一次比较获取两个点之间的最短距离 点对最近问题 暴力破解法 include
  • Maven Dependency设置,详解!

    come from http www javaeye com topic 240424 用了Maven 所需的JAR包就不能再像往常一样 自己找到并下载下来 用IDE导进去就完事了 Maven用了一个项目依赖 Dependency 的概念
  • 赶紧来修炼内功~字符串函数详解大全(二)

    目录 1 strncpy 重点 模拟实现 2 strncat 重点 模拟实现 3 strncmp 重点 模拟实现 写在最后 1 strncpy 该函数包含三个参数 前两个参数与上一篇文章中讲解的strcpy函数一样 一个目的地 一个源 第三
  • 【算法】分治、动态规划和贪心算法

    这三种算法非常相似 但是又有一些区别 理解如下 分治 把一个问题划分为若干子问题 求出子问题的最优解 再把子问题的最优解进行merge 最终得到原问题的最优解 动态规划 原问题的最优解包含子问题的最优解 即 拥有最优子结构 同时 求子问题的
  • React Router 从v3升级到v4的踩坑之旅

    React 应用很少不用react router这个包的 marknoteapp com之前一直用v3 看到v4出来后一直心痒 最近 抱着 用新不用旧 的理念 手贱升了一下级 这一升级 差不多2天功夫花掉啦 概述 和 Angular 那改朝