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的踩坑之旅 的相关文章

随机推荐

  • 【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 那改朝