资讯专栏INFORMATION COLUMN

项目实践:从react-router v3迁移到v4

zhangrxiang / 1568人阅读

摘要:详见对绑定监听事件,把的改变同步到的中用来把的更新同步到中。代码分割版本通过和实现代码分割和动态路由。笔者认为,更符合的组件思想,于是做了一个实践。

</>复制代码

  1. 原文:https://github.com/YutHelloWo...

前言

</>复制代码

  1. 今年3月初发布了react-router v4,相较之前的v3和v2版本做了一个破坏性的升级。遵循一切皆React Component的理念。静态路由变成了动态路由。这里记录下v3项目如何迁移到v4。
    项目地址:https://github.com/YutHelloWo...

迁移步骤

对React-Router和Redux同步进行重构

重写路由

代码分割

琐碎的API替换

</>复制代码

  1. 详细代码参阅这个PR

React-Router和Redux同步

</>复制代码

  1. 这里我们仍然不使用react-router-redux这个库。为了和react-routerv4版本保持一致,react-router-redux发布了v5.0.0版本,你当然也可以使用它来实现这个功能。

1. 替换依赖包

v3我们引入的是react-router包,在v4我们只引入react-router-dom这个包。安装react-router-dom时会同时安装history

package.json

</>复制代码

  1. - "react-router": "^3.0.0",
  2. + "react-router-dom": "^4.1.2",
2. 改写对browserHistory的创建和当前location的获取

location.js

</>复制代码

  1. // v3
  2. import { browserHistory } from "react-router"
  3. // 获取当前location
  4. const initialState = browserHistory.getCurrentLocation()

==>

</>复制代码

  1. // v4
  2. import createHistory from "history/createBrowserHistory"
  3. export const history = createHistory()
  4. // Get the current location.
  5. const initialState = history.location

这里替换的是history,和当前location的获取方法。在v3,browserHistory存在于react-router中,而v4把history抽离了出来,提供了createBrowserHistory ,createHashHistory ,createMemoryHistory 三种创建history的方法。v4中创建的history导出,在后面会需要用到。

</>复制代码

  1. history API详见: https://github.com/ReactTrain...

3. 对history绑定监听事件,把location的改变同步到Redux的store中

createStore

</>复制代码

  1. // v3
  2. import { browserHistory } from "react-router"
  3. import { updateLocation } from "./location"
  4. store.unsubscribeHistory = browserHistory.listen(updateLocation(store))

updateLocation用来把location的更新同步到store中。

</>复制代码

  1. export const updateLocation = ({ dispatch }) => {
  2. return (nextLocation) => dispatch(locationChange(nextLocation))
  3. }

一切似乎都很顺利,接着第一个坑来了

根据historyAPI提供的

</>复制代码

  1. // Listen for changes to the current location.
  2. const unlisten = history.listen((location, action) => {
  3. // location is an object like window.location
  4. console.log(action, location.pathname, location.state)
  5. })

修改createStore.js

==>

</>复制代码

  1. // v4
  2. import { updateLocation, history } from "./location"
  3. // 监听浏览器history变化,绑定到store。取消监听直接调用store.unsubscribeHistory()
  4. store.unsubscribeHistory = history.listen(updateLocation(store))

接着修改app.js

</>复制代码

  1. // v3
  2. // ...
  3. import { browserHistory, Router } from "react-router"
  4. // ...

==>

</>复制代码

  1. // ...
  2. import { BrowserRouter, Route } from "react-router-dom"
  3. // ...
  4. //...

我们到浏览器中查看,发现URL变化并没有触发updateLocation(store),state并没有变化。

What a f**k!
问题出在BrowserRouter在创建的时候在内部已经引入了一个historyupdateLocation(store)应该监听的是内部的这个history。这里贴下BrowserRouter.js的代码

</>复制代码

  1. import React from "react"
  2. import PropTypes from "prop-types"
  3. import createHistory from "history/createBrowserHistory"
  4. import { Router } from "react-router"
  5. /**
  6. * The public API for a that uses HTML5 history.
  7. */
  8. class BrowserRouter extends React.Component {
  9. static propTypes = {
  10. basename: PropTypes.string,
  11. forceRefresh: PropTypes.bool,
  12. getUserConfirmation: PropTypes.func,
  13. keyLength: PropTypes.number,
  14. children: PropTypes.node
  15. }
  16. history = createHistory(this.props)
  17. render() {
  18. return
  19. }
  20. }
  21. export default BrowserRouter

于是,我们放弃使用BrowserRouter,而使用Router

修改app.js

==>

</>复制代码

  1. // v4
  2. import { Router, Route } from "react-router-dom"
  3. //...

这样,这个坑算是填上了。也就完成了history和store之间的同步。

重写路由

</>复制代码

  1. v4取消了PlainRoute 中心化配置路由。Route是一个react component。
    取消了IndexRoute,通过Switch来组件提供了相似的功能,当被渲染时,它仅会渲染与当前路径匹配的第一个子

routes/index.js

</>复制代码

  1. // v3
  2. //..
  3. export const createRoutes = (store) => ({
  4. path : "/",
  5. component : CoreLayout,
  6. indexRoute : Home,
  7. childRoutes : [
  8. CounterRoute(store),
  9. ZenRoute(store),
  10. ElapseRoute(store),
  11. RouteRoute(store),
  12. PageNotFound(),
  13. Redirect
  14. ]
  15. })
  16. //...

==>

</>复制代码

  1. // ...
  2. const Routes = () => (
  3. )
  4. export default Routes
  5. //

这里路由的定义方式由PlainRoute Object改写成了组件嵌套形式,在PageLayout.js中插入

代码分割

</>复制代码

  1. v3版本通过getComponetrequire.ensure实现代码分割和动态路由。在v4版本,我们新增异步高阶组件,并使用import()替代require.ensure()

Counter/index.js

</>复制代码

  1. // v3
  2. import { injectReducer } from "../../store/reducers"
  3. export default (store) => ({
  4. path : "counter",
  5. /* 动态路由 */
  6. getComponent (nextState, cb) {
  7. /* 代码分割 */
  8. require.ensure([], (require) => {
  9. const Counter = require("./containers/CounterContainer").default
  10. const reducer = require("./modules/counter").default
  11. /* 将counterReducer注入rootReducer */
  12. injectReducer(store, { key : "counter", reducer })
  13. cb(null, Counter)
  14. }, "counter")
  15. }
  16. })

首先,新增AsyncComponent.js

</>复制代码

  1. import React from "react"
  2. export default function asyncComponent (importComponent) {
  3. class AsyncComponent extends React.Component {
  4. constructor (props) {
  5. super(props)
  6. this.state = {
  7. component: null,
  8. }
  9. }
  10. async componentDidMount () {
  11. const { default : component } = await importComponent()
  12. this.setState({
  13. component: component
  14. })
  15. }
  16. render () {
  17. const C = this.state.component
  18. return C
  19. ?
  20. : null
  21. }
  22. }
  23. return AsyncComponent
  24. }

</>复制代码

  1. 这个asyncComponent 函数接受一个importComponent 的参数,importComponent 调用时候将动态引入给定的组件。

  2. componentDidMount 我们只是简单地调用importComponent 函数,并将动态加载的组件保存在状态中。

  3. 最后,如果完成渲染,我们有条件地提供组件。在这里我们如果不写null的话,也可提供一个菊花图,代表着组件正在渲染。

接着,改写Counter/index.js

==>

</>复制代码

  1. import { injectReducer } from "../../store/reducers"
  2. import { store } from "../../main"
  3. import Counter from "./containers/CounterContainer"
  4. import reducer from "./modules/counter"
  5. injectReducer(store, { key : "counter", reducer })
  6. export default Counter

</>复制代码

  1. 一旦加载Counter/index.js,就会把counterReducer注入到Rudecer中,并加载Counter组件。

琐碎API的替换

</>复制代码

  1. v4 移除了onEnter onLeave等属性,history替换router属性,新增match

</>复制代码

  1. this.props.router.push("/")

==>

</>复制代码

  1. this.props.history.push("/")

</>复制代码

  1. this.props.params.id

==>

</>复制代码

  1. this.props.match.params.id
总结

这里可以看出,使用v4替换v3,对于大型项目并不是一件轻松的事情,有许多小坑要踩,这就是社区很多项目仍然使用v2/v3的原因。笔者认为,v4更符合React的组件思想,于是做了一个实践。最后欢迎指正拍砖,捂脸求star ? 。

参考

</>复制代码

  1. Code Splitting in Create React App

  2. Migrating from v2/v3 to v4

  3. React-Router

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

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

相关文章

  • ReactRouter升级 v2 to v4

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

    JasonZhang 评论0 收藏0
  • react-router 升级小记

    摘要:前言最近将公司项目的从版本升到了版本,跟完全不兼容,是一次彻底的重写。升级过程中踩了不少的坑,也有一些值得分享的点。没有就会匹配所有路由最后不得不说升级很困难,坑也很多。 前言 最近将公司项目的 react-router 从 v3 版本升到了 v4 版本,react-router v4 跟 v3 完全不兼容,是一次彻底的重写。这也给升级造成了极大的困难,与其说升级不如说是对 route...

    isLishude 评论0 收藏0
  • [ 一起学React系列 -- 11 ] React-Router4 (1)

    摘要:中的包中的包主要有三个和。的理念上面提到的理念是一切皆组件以下统一称组件。从这点来说的确方便了不少,也迎合一切皆组件的理念。组件是中主要的组成单位,可以认为是或的路由入口。将该标示为严格匹配路由。的属性追加一条。 2019年不知不觉已经过去19天了,有没有给自己做个总结?有没有给明年做个计划?当然笔者已经做好了明年的工作、学习计划;同时也包括该系列博客剩下的博文计划,目前还剩4篇:分别...

    tinysun1234 评论0 收藏0
  • React-router v4 路由配置方法

    摘要:使用了约等于才能匹配或者能匹配,所以说是约等于。使用了和才能匹配后续补充这是版本中新添加,主要用来做唯一匹配的功能。就是想要在众多路由中只匹配其中一个路由。 React-Router v4 一. Switch 、Router 、Route三者的区别 1、Route Route 是建立location 和 ui的最直接联系 2、Router react-router v4 中,Route...

    Coding01 评论0 收藏0
  • todo-list 项目问题总结(使用 react 进行开发)

    摘要:项目问题总结这个项目,很简单,前端使用,后端使用进行开发。方便移动端开发。当动画结束后,有一个钩子函数可以使用其他一些功能组件,都是自己尝试去编写的,像日历组件组件组件等。版本的,是没有任何的钩子函数,我就感觉懵逼了。。。 todo-list 项目问题总结 这个 todo-list 项目,很简单,前端使用 react,后端 nodejs 使用 koa2 进行开发。数据库使用 Mysql...

    shengguo 评论0 收藏0

发表评论

0条评论

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