资讯专栏INFORMATION COLUMN

react-router 升级小记

isLishude / 876人阅读

摘要:前言最近将公司项目的从版本升到了版本,跟完全不兼容,是一次彻底的重写。升级过程中踩了不少的坑,也有一些值得分享的点。没有就会匹配所有路由最后不得不说升级很困难,坑也很多。

前言

最近将公司项目的 react-router 从 v3 版本升到了 v4 版本,react-router v4 跟 v3 完全不兼容,是一次彻底的重写。这也给升级造成了极大的困难,与其说升级不如说是对 router 层重写。之前我也将项目的 react 从 v15 版本升级到了 v16 版本,相较而言升级 react-router 比升级 react 困难多了。升级过程中踩了不少的坑,也有一些值得分享的点。写成一篇小文,供大家参考。

依赖升级

react-router v4 跟 react 一样拆成了两部分,核心的 react-router 和依运行环境而定的 react-router-dom 或 react-router-native(跟 react-dom 和 react-native 一样)。本文要说的是浏览器环境,也就是 react-router + react-router-dom

先安装依赖(推荐使用 yarn)

</>复制代码

  1. yarn add react-router react-router-dom history

为什么要安装 history 后面会解释。

组件外导航与 react-router-redux

之前我们项目中使用了 react-router-redux 你有很多理由使用它,但对于我们来说唯一的理由或者用处就是用于在页面组件之外导航,react-router-redux 让你可以在任何地方通过 dispatch 处理页面跳转,如:store.dispatch(push("/"))。因为这个我们就必须使用 react-router-redux 吗?当然不需要,有更简单的办法实现这个需求。所以这次升级我移除了react-router-redux, 写作此文时支持 react-router v4 的 react-router-redux 还处于 v5.0.0-alpha.7 也是原因之一。

还记得之前安装的 history 吗?history 是 react-router 唯二的主要依赖之一,之所以要显式安装,是因为我们要使用它来实现页面组件外导航。以下以 browser history 为例(hash history 和 memory history 都是一样的):

我们不使用 react-router-dom 提供的 BrowserRouter 而是自己实现一个

</>复制代码

  1. // history.js
  2. import createHistory from "history/createBrowserHistory";
  3. const history = createHistory();
  4. export default history;

</>复制代码

  1. // index.js
  2. import React from "react";
  3. import ReactDOM from "react-dom";
  4. import { Router } from "react-router";
  5. import history from "./history";
  6. import App from "./app";
  7. ReactDOM.render(
  8. ,
  9. document.getElementById("app")
  10. );

搞定!就这么简单,这样在任何地方只要引用 history 就可以使用它进行导航操作,如 history.push("/"),更多使用方式请参考 history 文档。其实 react-router-dom 的 BrowserRouter 跟我们做了同样的事,区别在于我们这么做能把 history 暴露出来。这个 history 就是页面组件 props 里面的 history 自然也就能做同样的事情。

静态配置

react-router v3 是面向配置的,组件写法只是一种语法糖。而 react-router v4 是完全面向组件的,提供的 Route Switch 等都是真正的组件。这也就导致只能按组件的方式写路由,不能写配置。但是 v3 那样的配置确实有一些方便之处,如统一管理、使用方便等。

多亏 JSX 灵活的语法,我们依然有办法按配置的方式写 react-router v4 的路由。

</>复制代码

  1. // routes.js
  2. import Home from "./home";
  3. import About from "./about";
  4. import Help from "./help";
  5. export default [{
  6. path: "/",
  7. exact: true,
  8. component: Home
  9. }, {
  10. path: "/about",
  11. component: About
  12. }, {
  13. path: "/help",
  14. component: Help
  15. }];

</>复制代码

  1. // app.js
  2. import React from "react";
  3. import { Switch, Route } from "react-router";
  4. import routes from "./routes";
  5. import NotFound from "./not-found";
  6. class App extends React.Component {
  7. render() {
  8. return (
  9. {routes.map((route, i) => )}
  10. );
  11. }
  12. }
  13. export default App;

这样我们就用配置的方式写出了面向组件的路由,兼顾两者的优点。如果有嵌套路由需求,可以参考官方示例。官方也提供了一个 react-router-config, 不过我没有使用,一来觉得没必要,二来写作此文时它还处于 v1.0.0-beta.4 版本。

异步组件与 Code Splitting

Web 应用最大的一个优势就是不必下载整个应用,只用下载需要的部分就可以使用。要达到这样的目标,就需要对代码进行分片,异步加载组件。可惜 react-router v4 没有像 v3 一样提供加载异步组件的接口。这部分工作就需要我们自己来处理。

我们可以创建一个高阶组件 Bundle,专门用来加载异步组件。

</>复制代码

  1. // bundle.js
  2. import React from "react";
  3. class Bundle extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = { Component: null };
  7. props.load().then(Component => this.setState({ Component: Component.default }));
  8. }
  9. render() {
  10. const { load, ...props } = this.props;
  11. const Component = this.state.Component;
  12. return Component ? : null;
  13. }
  14. }
  15. export default Bundle;

然后修改一下 routes.js

</>复制代码

  1. // routes.js
  2. import React from "react";
  3. import Bundle from "./bundle";
  4. export default [{
  5. path: "/",
  6. exact: true,
  7. component(props) {
  8. // 这里的 component 函数也是一个高阶组件
  9. return import("./home")} />;
  10. }
  11. }, {
  12. path: "/about",
  13. component(props) {
  14. return import("./about")} />;
  15. }
  16. }, {
  17. path: "/help",
  18. component(props) {
  19. return import("./help")} />;
  20. }
  21. }];

这样每个页面都会打包成多带带的 JS,访问相应页面才会去异步加载对应的组件。这样也可以做精细化缓存控制。
需要注意的是 import() 语法在写作本文时还处于 Stage 2 的状态,需给 Babel 添加 syntax-dynamic-import 插件才能正常工作,另外需 webpack 2 及以上才支持。

查询参数

因为各种原因 react-router v4 不再解析 ?key=value 这样的 URL 的查询参数,页面组件 props.location 中只有 search 字符串。这跟 v3 不兼容,而且很不方便。我们有办法兼容一下吗?当然有,这时候之前写的 histroy.js 又有新的用处了。

</>复制代码

  1. // history.js
  2. import qs from "qs";
  3. import createHistory from "history/createBrowserHistory";
  4. function addQuery(history) {
  5. const location = history.location;
  6. history.location = { ...location, query: qs.parse(location.search, { ignoreQueryPrefix: true }) };
  7. }
  8. const history = createHistory();
  9. addQuery(history);
  10. export const unlisten = history.listen(() => {
  11. // 每次页面跳转都会执行
  12. addQuery(history);
  13. });
  14. export default history;

这样我们就能在页面组件 props.location.query 拿到解析好的 URL 查询参数了,跟 v3 完美兼容。还有个额外的好处是在任何地方引用 history 都可以拿到解析好的 URL 查询参数。需要注意的是,在 history 的设计中,history 对象是 Mutable 的,所以我们可以直接修改 history。但是 history.location 是 Immutable 的,所以我们要确保每一个 location 对象都是全新的。

搭配 Redux

react-router v4 跟 redux 搭配有一个大坑(mobx 应该也有同样的问题),详情请看这篇文章,这里就不再赘述。简单来说,如果一个组件用 redux 的 connect 包装过,又️不是 Route 的子组件,那么 history 的变更就不会触发这个组件的更新,它的子组件自然也不会更新。比如应用的根组件(上文的 App)。

解决方案也很简单,可以用 react-router v4 提供的 withRouter 再包装一遍:withRouter(connect(...)(App)),或者让 App 做为 Router 的子组件,原理都一样。我采用的后者。

</>复制代码

  1. // app.js
  2. import React from "react";
  3. import { connect } from "react-redux";
  4. import { Switch, Route } from "react-router";
  5. import routes from "./routes";
  6. import NotFound from "./not-found";
  7. class App extends React.Component {
  8. render() {
  9. return (
  10. {routes.map((route, i) => )}
  11. );
  12. }
  13. }
  14. function mapStateToProps(state) {
  15. return {
  16. someState: state.someState
  17. };
  18. }
  19. export default connect(mapStateToProps)(App);

</>复制代码

  1. // index.js
  2. import React from "react";
  3. import ReactDOM from "react-dom";
  4. import { Provider } from "react-redux";
  5. import { Router, Route } from "react-router";
  6. import store from "./store";
  7. import history from "./history";
  8. import App from "./app";
  9. ReactDOM.render(
  10. {/* 没有 path 就会匹配所有路由 */}
  11. ,
  12. document.getElementById("app")
  13. );
最后

不得不说升级 react-router 很困难,坑也很多。但是把坑一个个填完,最终完美升级也是一件很有意思,很有成就感的事。希望这篇文章能对你有所帮助。

另外完整的 Demo 请戳我的 GitHub,喜欢的话点个 Star 吧 :P

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90533.html

相关文章

  • Vuex 升级入坑小记

    摘要:升级入坑小记场景描述引入的版本为,开启调试工具默认升级后可以调试。遂升级,发现大量使用失效,报,的中文文档,没有及时更新。机票订单和用户信息。 Vuex 升级入坑小记 场景描述 引入Vuex的版本为0.3,开启调试工具默认升级后可以调试Vuex。给作者一个大大的赞。为提高开发体验也是操碎了心 (๑•̀ㅂ•́)و✧ (8。安利下(Vue Devtools)。 Vue Devtools ...

    ziwenxie 评论0 收藏0
  • gulp4.0升级小记

    摘要:前言周日在公司的新电脑在以前配置的目录按下时发现报了错,百度了一下得知原来已经到了版本,就花了一点时间去升了个级,顺便记下我个人使用到的配置文件新版本的不同点,文笔和水平有限,多多见谅新引入新引入的可替换老版的和,代码更简洁是任务监听是任务 前言 周日在公司的新电脑在以前gulp3.9配置的目录按下npm install时发现报了错,百度了一下得知原来gulp已经到了4.0版本,就花了...

    zorpan 评论0 收藏0
  • ReactRouter升级 v2 to v4

    摘要:概述相对于几乎是重写了新版的更偏向于组件化。汲取了很多思想,路由即是组件,使路由更具声明式,且方便组合。如果你习惯使用,那么一定会很快上手新版的。被一分为三。不止是否有意义参考资料迁移到关注点官方文档 概述 react-router V4 相对于react-router V2 or V3 几乎是重写了, 新版的react-router更偏向于组件化(everything is comp...

    JasonZhang 评论0 收藏0
  • async语法升级踩坑小记

    摘要:普通的回调函数调用执行后续逻辑使用了以后的复杂逻辑获取到正确的结果输出两个文件拼接后的内容虽说解决了的问题,不会出现一个函数前边有二三十个空格的缩进。所以直接使用关键字替换原有的普通回调函数即可。 从今年过完年回来,三月份开始,就一直在做重构相关的事情。 就在今天刚刚上线了最新一次的重构代码,希望高峰期安好,接近半年的Node.js代码重构。 包含从callback+async.w...

    VioletJack 评论0 收藏0
  • electron + react + react-router + mobx + webpack 搭

    摘要:调试集成环境选择模块,简单分离开发,测试,线上环境。程序保护开机自启托盘最小化崩溃监控升级一行代码接入升级平台,实现客户端升级功能打包构建一个指令搞定打包项目地址 项目地址 : https://github.com/ConardLi/electron-react electron-react electron + react + react-router + mobx + webpac...

    pingan8787 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<