react15升16

React 16 已经发布很长时间了 目前最新版本是 16.12.0 让我们来看看从 15 升级到 16 都要做哪些迁移吧~

基础依赖升级

  1. react 16.12.0
  2. react-dom 16.12.0
  3. prop-types 15.7.2

如果引用 redux 的话

  1. redux 4.0.5
  2. react-redux 7.1.3
  3. redux-thunk 2.3.0

如果引用 transition 的话

  1. react-transition-group 4.3.0

基础依赖安装

1.react-router-dom 5.1.2
2.react-router-config 5.1.1

三方依赖升级

一些组件库需要升级版本,如果不升级,依赖的不是 react16 的话,就会有一些生命周期函数的 warning
这些生命周期函数将在 17 彻底废除掉

migration 之 路由

升级之后发现跑不起来了,是因为 react-router 的写法完全变了 react 和 react-router 相互版本依赖 所以要一起升级,react-router 已经到 5 了可怕而秉承万物即组件的理念 react-router 相较之前的写法有了很大的变化,迁移改动巨大。
那么使用路由组件的写法,一是改动成本大,二是不集中管理路由的话,又很难维护代,路由分散,对开发和 debug 查 bug 什么的对于新同学来说非常繁琐隐晦。
莫慌,我们还装了一个叫 react-router-config 的库,它可以帮助我们仍然可以集中管理路由,并且有点像 vue 一样的 json 写法,让你的路由配置变的神清气爽。
像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import React, { lazy, Suspense } from "react";
import { BrowserRouter } from "react-router-dom";
import { renderRoutes } from "react-router-config";

const App = lazy(() => import("./containers/App"));
const Organization = lazy(() => import("./containers/Organization"));
const ApplicationList = lazy(() => import("./containers/ApplicationList"));
const ExcelApplicationList = lazy(() =>
import("./containers/ExcelApplicationList")
);
const JobList = lazy(() => import("./containers/JobList"));
const Job = lazy(() => import("./containers/Job"));
const CandidateInfo = lazy(() => import("./components/CandidateInfo"));
const Result = lazy(() => import("./containers/Result"));
const ExcelResult = lazy(() => import("./containers/ExcelResult"));

const routes = [
{
path: ["/headhunters/:orgId", "/headhunters"],
// 是按顺序匹配
// 如果exact为true 虽然会精确匹配 但是 触发子https://reacttraining.com/blog/react-router-v5-1/路由 父路由被精确掉了所以App就没了 子组件也就没有render了
// 所以统一带id的放在前边 优先自动匹配
// 有子路由的exact必须为false
component: App,
routes: [
{
path: "/headhunters/:orgId/org",
component: Organization,
routes: [
{
path: "/headhunters/:orgId/org/jobs",
exact: true,
component: JobList,
},
{
path: "/headhunters/:orgId/org/applications",
exact: true,
component: ApplicationList,
},
{
path: "/headhunters/:orgId/org/excel_applications",
exact: true,
component: ExcelApplicationList,
},
],
},
{
path: "/headhunters/:orgId/job/:jobId",
exact: true,
component: Job,
},
{
path: "/headhunters/:orgId/recommend/:jobId",
component: CandidateInfo,
routes: [
{
path: "/headhunters/:orgId/recommend/:jobId/result",
exact: true,
component: Result,
},
{
path: "/headhunters/:orgId/recommend/:jobId/excel_result",
exact: true,
component: ExcelResult,
},
],
},
{
path: "/headhunters/:orgId/update/:applicationId",
component: CandidateInfo,
routes: [
{
path: "/headhunters/:orgId/update/:applicationId/result",
exact: true,
component: Result,
},
],
},
],
},
];
export default (
<BrowserRouter>
<Suspense fallback={null}>{renderRoutes(routes)}</Suspense>
</BrowserRouter>
);
// 其实这里还用该有个 <Switch></Switch>组件,代表同级的路由只匹配一个,但是renderRoutes内部已经包含了该组件,这里就没有写

这样的路由配置结构清晰,嵌套清晰。

class 组件的 render 方法,withRouter基本不变,但是可以看到 export 出去的路由多了很多东西。BrowserRouter 组件代表使用 history 模式的路由,对,还有一个HashRouter,react-router 已经内置了 history 模式,不需要再去引别的库了。

this.props.router. 等都改为 this.props.history.

且 react16 引入了 lazy 和 Suspense,分别代表懒加载模块,以及将来时的声明组件,两者配合使用,还可以 fallback,在加载时降级展示的组件,react 自己引入了代码分割功能。

具体用法参考 https://react.docschina.org/docs/code-splitting.html

renderRoutes 就是 react-router-config 库的 render 方法了,源码很简单,就是递归你的配置路由,自己去看。使用这个方法将配置的路由 render 出来。

在子路由的 render 还是要改动,以前子路由的 render 可能是这样的

1
2
3
4
5
6
7
{
this.props.children && React.cloneElement(
this.props.children,
{
...
})
}

现在改成这样

1
2
3
4
5
import { renderRoutes } from "react-router-config";

// 凡是路由组件,经过renderRoutes之后都会有一个route字段,即当前匹配的路由配置
renderRoutes(route.routes, { ...props });
// 只有匹配了子路由才会,子组件才会被render
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { renderRoutes, matchRoutes } from "react-router-config";

// 但是对于这种children是否存在,即是否匹配到子路由的情况的判断应该怎么判断呢
// 上边的例子是匹配哪个子路由就render哪个子组件,没有匹配就不render所以不会有这样的情况
// 但是这里需要在不匹配时render别的组件
const renderDom = children || Component;

// 可以这样写
// matchRoutes是路由匹配方法,判断当前地址pathname是否匹配到了路由,匹配的路径是啥,返回的是一个数组
// 我们可以直接以子路由为匹配输入参数 进行匹配,如果branch的length>0则说明当前地址匹配到了 当前层 路由的子路由,即有children
const branch = matchRoutes(
this.props.route.routes,
this.props.location.pathname
);
const renderDom = branch.length ? children : Component;
  • 需要注意的是,exact这个字段,默认为 false,顾名思义精确匹配,且是按层级的精准匹配
1
2
3
4
5
6
7
8
9
10
11
{
"path": "/headhunters/:orgId/update/:applicationId",
"component": CandidateInfo,
"exact": true,
"routes": [
{
"path": "/headhunters/:orgId/update/:applicationId/result",
"component": Result
}
]
}

比如这个路由/headhunters/:orgId/update/:applicationId,exact 为 true 时,它的子路由/headhunters/:orgId/update/:applicationId/result,只会显示Result这个组件,而不会显示CandidateInfo这个组件,因为没有精准匹配到/headhunters/:orgId/update/:applicationId这个路由,这对于嵌套路由父子组件都显示(例如列表组件上浮着的详情浮层)的期望是不匹配的,所以 exact 为 true,一定不要给到有子组件的路由上,要给到无子组件的路由上(特殊情况除外)。

  • 另外还有一个strict字段,默认为 false,如果为 true 时,路由后面有斜杠而 url 中没有斜杠,是不匹配的
  • 另外 react-router5 好像不支持那种带括号啊之类的路由了,比如/headhunters(/:orgId)(有没有 orgId 都会匹配),但是 path 可以接受一个数组,也就是这个数组里的路由会按顺序挨个去匹配,那么我们可以改写上边的路由,例如path: ['/headhunters/:orgId', '/headhunters'],以更精确的路由来配置。
  • 对于 function 组件,在 react-router-dom 中有let history = useHistory()钩子 🐶,可以使用,想咋使咋使。

具体用法参考 https://reacttraining.com/blog/react-router-v5-1/

migration 之 params 和 query

params 不再存在于 this.props.location 里了

location 中只剩下这几个字段了,当前路由和 search 等

1
2
3
4
5
6
location: {
pathname: "/headhunters/gaobo/org/jobs"
search: "?aa=123"
hash: ""
state: undefined
}

那 params 和 query 跑哪了嘞?

可以看到 props 里多了一个 match 字段,对,params 跑这里去了,顾名思义,这个字段是路由匹配的结果,里边有

1
2
3
4
5
6
match: {
path: "/headhunters/:orgId"
url: "/headhunters/gaobo"
isExact: false
params: {orgId: "gaobo"}
}

这些字段,path 当前路由组件对应的路由配置,url 是当前地址匹配到当前组件对应路由配置的部分,isExact 略,params 跑这了,所以之前有依赖这个字段的地方,会比较大。以前this.props.params,现在得this.props.match.params,虽然繁琐,但是对 props 的数据结构也更合理(改吧)。

query 去哪了?对,没了。
在 v4 中不再能直接获取到 query 的值了。由于没有处理复杂 query 字符串的标准。所以 v4 决定把 query 字符串的处理权留给开发者,这样其实也挺好的,可以用 qs 等库自己处理,但是依赖 query 改变而触发不同 render 的情况,我还没改到那,但是我估计会比较麻烦,可能需要 forceUpdate 之类的吧。毕竟不算是 props 里的东西了,那它也不会引起组件的重新 render。

  • 对于 function 组件,在 react-router-dom 中有let { xxx } = useParams()let location = useLocation()钩子,随便造,还是方便了很多的。

具体用法参考 https://reacttraining.com/blog/react-router-v5-1/

migration 之 react-transition-group

迁移

1
2
3
4
5
transitionName -> classNames
transitionEnterTimeout and transitionLeaveTimeout -> timeout={{ exit, enter }}
transitionAppear -> appear
transitionEnter -> enter
transitionLeave -> exit

原来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import CSSTransitionGroup from 'react/lib/ReactCSSTransitionGroup';
// 或者
import CSSTransitionGroup from 'react-addons-css-transition-group';
// 这些都是低版本react的用法 里边包含着一些即将被废弃的生命周期函数(会报warning)所以需要一并升级

<CSSTransitionGroup
transitionName="toast-animation"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{
toasts.map((, index) => (
<div key={index}></div>
))
}
</CSSTransitionGroup>

现在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { CSSTransition, TransitionGroup } from 'react-transition-group';
<TransitionGroup>
{
toasts.map((, index) => (
<CSSTransition
classNames="toast-animation"
timeout={{ exit: 500, enter: 500 }}
key={index}
// 还增加了一些生命周期函数
onEnter={handleEnter}
onEntering={handleEntering}
onEntered={handleEntered}
onExit={handleExit}
onExiting={handleExiting}
onExited={handleExited}
>
<div className={styles.item}></div>
</CSSTransition>
))
}
</TransitionGroup>

具体用法参考 https://github.com/reactjs/react-transition-group/blob/master/Migration.md

migration 之 生命周期函数

有一些生命周期函数在 16 里边会报 warning,且到 17 的时候会被废弃掉不能用了。
比如 componentWillReceiveProps 改为 UNSAFE_componentWillReceiveProps
componentWillMount 改为 UNSAFE_componentWillMount等等

由于目前项目较大,完全更换生命周期函数,工作量和风险都比较大,目前先改成 UNSAFE 了。

批量修改工具

具体参考https://github.com/reactjs/react-codemod

可以批量修改生命周期、prop-types、bind to arrow、纯函数

建议这几个命令都用一下,一步到位

npx react-codemod pure-component <path>
npx react-codemod React-PropTypes-to-prop-types <path>
npx react-codemod rename-unsafe-lifecycles <path>
npx react-codemod manual-bind-to-arrow <path>

总结

以上是对 development 环境下的 react15 升 16 的一些改造
对 production 环境,打包升级,还没开始做,后续做了会更新,尽情期待~

引用

https://react.docschina.org/docs/code-splitting.html > https://reacttraining.com/blog/react-router-v5-1/ > https://github.com/reactjs/react-transition-group/blob/master/Migration.md

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 基础依赖升级
  2. 2. 基础依赖安装
  3. 3. 三方依赖升级
  4. 4. migration 之 路由
  5. 5. migration 之 params 和 query
  6. 6. migration 之 react-transition-group
  7. 7. migration 之 生命周期函数
  8. 8. 批量修改工具
  9. 9. 总结
  10. 10. 引用
,