博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React 路由状态管理总结
阅读量:6757 次
发布时间:2019-06-26

本文共 4580 字,大约阅读时间需要 15 分钟。

一、依赖(Dependencies)

在一般 SPA 开发中,路由的管理十分重要。作为 React 技术体系中的一部分,官方维护的 React-Router 则是首选的路由库。

在应用 Redux 模式后,React-Router 与 Redux 的配合引发了新的问题,是否需要将路由纳入 store 进行管理?如何将路由纳入 store 进行管理?这些都是需要考虑的问题。我们将在后文讨论第一个问题,而为了解决上述第二个问题,React-Router-Redux 这个轻量级的扩展库应运而生并得到广泛应用。

另外需要说明的是,长久以来 React-Router 与 React-Router-Redux 是两个独立的库,但在 React-Router 4.x 版本以后,React-Router-Redux 已经成为了 React-Router 的一部分。

本文并不旨在介绍两种依赖库的具体用法(具体用法请参考官方文档和教程),而主要阐述其实现方式和原理,总结具体的实践方式和注意事项。在主要内容之前,首先简要介绍下两个库的功能:

  1. React-Router

    React-Router 做的最重要的事就是将浏览器 URL 与程序联系起来(借助 history 库),它为 React 提供了声明式的路由系统,通过其提供的导航组件,我们能够方便地使用 URL 来控制状态的变化和组件的切换。

  2. React-Router-Redux

    按照官方的说法,其实现了「deep integration of react-router and redux」,即 React-Router 与 Redux 的深度集成,它将路由完全纳入 store 中进行管理,使 store 成为了 URL(或者说是 history)的数据来源,也使我们能够通过 dispatch action 的方式来修改 URL。我们将在后文介绍它的实现原理。

二、实践

路由状态并非一定要介入 Redux 架构中。在一些简单的应用场景下,只需要使用 React-Router 提供的声明式组件(Router, Route, Link 等)即可方便的实现 URL 导航。在一些稍复杂的场景中,只要保证遵循 React 单向数据流动方式,遵照使用方法,也可以完成进行路由信息的读取和触发变更,其过程如下图所示。(使用方法请参照 React-Router 文档和教程)

640?wx_fmt=png

但在这里,我们主要讨论将路由状态纳入 Redux 架构中的情况。本部分的下文将分为两部分:

  1. 手动管理,也就是不使用 React-Router-Redux;

  2. 借助 React-Router-Redux 管理,这也是讨论的重点。

2.1、手动管理 (Mannually)

在不借助其他库,一种简单的做法是手动将路由状态纳入 store 中管理,当 URL 改变时同步修改 store 中的状态。

640?wx_fmt=png

如上图,在手动同步环节,通过一套 Redux 机制,实现了路由信息在 store 中的存储。history 作为数据来源,通过监听 history,当 URL 状态改变时 dispatch 相应 action (例如 type = LOCATION_CHANGE),通过添加的 reducer 将 location 信息同步到 store。通过这种方式,组件就可以获取 store 中的 location 状态信息,这也是目前 react-redux-starter-kit 采用的方式。

这种相对原始的方式有一定弊端:

  • 没有将路由完全纳入 Redux 管理。

  • 路由不支持 time travel。

  • history 实际也是 react-router 的路由数据来源,这就导致我们 store 中存储的 location 数据与 react-router 并不一定同步。(例如,这会导致文末讨论的重复渲染问题)

2.2、使用 React-Router-Redux

下面我们讨论文首提出的问题一:是否需要将路由纳入 store 进行管理。虽然在 react-router 4.x 版本后,react-router-redux 已经成为其一部分,但官方还是就其是否应该在项目中使用进行了建议:

  1. 希望在项目中使用完全使用 store 管理路由数据

  2. 希望使用 dispatch action 的方式进行导航(修改路由)

  3. 希望调试时路由支持 time travel

上面是使用 React-Router-Redux 的原则,当然一定程度上也可以是决定将路由纳入 store 管理的原则。我觉得还可以增加两条:

  1. 项目抽象中,路由信息应该作为一种全局的状态管理

  2. 有 Redux 强迫症

2.2.1、原理

通过一张图的方式来了解一下 React-Router-Redux 的实现原理。

640?wx_fmt=png

上图实际上也是 React-Router-Redux 如何将 URL 与 state 同步的过程,在程序中,主要是通过如下的几个重要的 API 实现的:

  • routerMiddleware 与 routerReducer 

    routerMiddleware 与 routerReducer 的共同作用,让我们能够处理两种 action 类型:一种类型为 LOCATION_CHANGE,与手动管理过程中相同,它负责修改 store 存储;另一种类型为 CALL_HISTORY_METHOD,这类 action 一般会在组件内派发,它不负责 state 的修改,通过 routerMiddleware 后,会被转去调用 history 方法(如 push, replace 等),以修改 URL 状态。

  • syncHistoryWithStore 

    顾名思义,这个方法就是处理路由与 store 中信息同步的重要方法。通过这个方法,我们能获得一个新的、增强版的 history 对象,这个对象重写了 history.listen 方法,原有的 history.listen 只负责 action (LOCATION_CHANGE) 的派发,新的 history.listen 则只监听 store 的变化(使用了 store.subscribe),所以当我们在程序内调用 history.listen 时,实际上是在监听 store 中的路由信息。

2.2.2、实践:location as a prop

在实际项目应用中,一种较为合理实践方式如下。

640?wx_fmt=png

即将 location 或子属性(如 location.pathname 等)作为属性信息逐层传递,传递给关注路由信息的子组件,这类似于 react-router 原有的使用方法,区别是,在改变 URL 时,使用了 dispatch action 的方式。

三、建议

3.1、 谨慎地使用 state.routing

一般地,在使用 React-Router-Redux 时,路由信息在 store 中会以 routing.locationBeforeTransition 的形式体现。我们在上文的实践中并没有直接从 store 中获取这个状态,实际上官方也不建议这样做,从名字来看,作者已经明确提醒了我们这是一个变化中的值。

You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.

不应该直接从 Redux store 中读取路由状态。这是因为 React-Router 的行为是异步的(例如为了处理组件动态加载等),所以你的组件树可能不能跟上 Redux 状态的变化。应该去依赖 React Router 传递的属性,这保证了这些值是在所有异步操作完成后才更新的。

当 routing 中的值已经改变时,React-Router 可能还没有将组件树进行更新完毕,如果使用这个值可能引发一些问题。所以作者依然建议我们采用传递 location 属性的方式读取路由信息,以确保 React-Router 已经处理完毕。

3.2、只传递必要的路由信息

只将必要信息作为 prop 传递,例如 location.pathname、 location.query.page,而不是传递整个 location。这能够尽量避免可能的重复渲染。

3.3、 只使用 dispatch action 的方式修改路由

实际上,除了使用 Link 组件,使用 React-Router-Redux 后有多种方式能够修改路由信息,如:

  1. history.method

  2. context.router.method

  3. dispatch ROUTER-ACTION

笔者仍然建议只使用 dispatch action 方式修改路由,这种方式更为遵循 Redux 流程,同时方便组件的解耦。在实际应用中,应该使用统一的 Action Creator 来创建修改路由的 action。

3.4、 谨慎地使用 withRouter 高阶组件(装饰器)

React-Router 提供了 withRouter 高阶组件以便组件访问路由状态信息(match, location, history),但同时一旦引用的路由属性发生变化就会触发重渲染流程,如果使用不当,则可能导致组件进行多余的重复渲染。

四、常见问题

4.1、re-render(重复渲染)问题

在使用 React-Router 和路由组件异步加载后,一个常见的问题是组件切换时发生意外的重复渲染。 一般情况下(未进行代码分割时),React-Router 在切换路由组件时,过程是这样的:

640?wx_fmt=png

在进行了代码分割后,路由组件改为异步加载,过程变成了这样:

640?wx_fmt=png

由于组件 A 将 location 或其相关属性最为属性 props 传入,location 的变化导致了 props 的改变,此时由于组件 B 还未加载成功,导致组件 A 在卸载前进行没有必要的重渲染。

这个问题一般是因为错误地使用了变化的路由信息,如上文中的 state.routing 信息,由于 state.routing 与 React-Router 路由信息不同步造成的。解决办法:参照上文提出的实践,使用 Route 组件注入的 location 数据进行路由信息传递。

原文发布时间:2018年05月12日

本文来源如需转载请紧急联系作者

你可能感兴趣的文章
JFinal学习 & Gradle配置续 & Tomcat配置
查看>>
CSS进度条
查看>>
android的color值
查看>>
对于linux下system()函数的深度理解(整理)
查看>>
软件设计和开发准备
查看>>
ROS + Kinect2 跑ORB_SLAM2 安装步骤记录
查看>>
纯CSS实现垂直居中的几种方法
查看>>
win7注册表常用设置
查看>>
amazeui学习笔记--css(常用组件3)--按钮组Button-group
查看>>
Spring简介
查看>>
new Function()
查看>>
man page分類與說明
查看>>
站立会议3
查看>>
Libvirt 版本降级过程记录 4.5.0 to 3.9.0
查看>>
net core 的Generic Host 之Generic Host Builder
查看>>
SQL Server性能杀手
查看>>
1157: 零起点学算法64——回型矩阵
查看>>
Ubuntu系统清理瘦身
查看>>
How to Analyze Java Thread Dumps
查看>>
SQL-58 获取有奖金的员工相关信息。
查看>>