react-router|react-router v3 升级至 v6 探索小结
背景
在当前业务项目中使用的 react-router 版本为 3.x, 而当前主流使用的是 5.x 以上,
本文就来探究 react-router 升级的方案
当前情况
目前使用的是 react-router3.x 版本 再加上和 redux 的搭配库 react-router-redux
一起使用的
4.x 5.x API 的变动
因为 4 和 5 之间差别不是很大, 所以就放一起讲了
- 路由不能集中在一个文件中
具象为某一类, 比如:
,等等
组件来匹配路由, 排他性路由
组件 ,
组件- 用exact属性代替了
` react-router-dom
的出现, 只需要依赖此组件即可- 支持 React 16 , 兼容 React >= 15
- Route 组件 path 可以为数组
- 如果没有匹配的路由,也可通过
重命名为
, 不再需要该exact。
的新特性变更。- 再度支持路由嵌套
替代- 用
useNavigate
代替useHistory
。 - 删除
组件
- 新钩子
useRoutes
代替react-router-config
。 - 大小减少:从
20kb
到8kb
9.增强的路径模式匹配算法。
所以当前项目如果是 3 的话, 我们就准备一口气升级到 6, 避免中间的多重更改
升级的痛点 API 修改:
一般来说, 唯一的难点在于旧 API 的语法, 调用发生了变化, 导致一旦升级, 所有的地方都要重新写一遍
API 的删除:
- 有出现新 API 的替换 这种情况是和修改一样的
- 单纯的删除, 这里的话也是需要所有的地方修改的, 但是这种情况比较少, 而且被删除的 API 用到的地方也很少
单纯的新增并不影响现有的升级
同时 API 我们需要有所区分
- 配置型 API, 这种一般只会使用一次, 比如
, 只在路由配置页面使用, 那我们升级的时候直接修改便可以了 - 使用型 API, 这类 api 覆盖比较广泛, 比如说
router.push
改成了history.push
redux 升级
需要升级 redux 相关库:
- react-redux^6.0.0
- redux-first-history
react-router-redux
store.js:connected-react-router
只支持 v4 和 v5, 这里我们使用redux-first-history
, 更小, 更快的替代方案
import { createStore, combineReducers, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { createReduxHistoryContext } from "redux-first-history";
import { createBrowserHistory } from 'history';
// 原有 routerMiddleware 来自于 react-router-redux
const { createReduxHistory, routerMiddleware, routerReducer } = createReduxHistoryContext({
history: createBrowserHistory(),
//other options if needed
});
export const store = createStore(
combineReducers({
router: routerReducer
//... reducers //your reducers!
}),
composeWithDevTools(
applyMiddleware(routerMiddleware)
)
);
export const history = createReduxHistory(store);
关于
redux-first-history
仓库, 如果有依赖 redux-devtools
, redux-devtools-log-monitor
等库, 可以不使用它这样使用:
import { compose, createStore, combineReducers, applyMiddleware } from 'redux';
import DevTools from '../utils/DevTools';
// 省略 createReduxHistoryContextconst enhancer = compose(
applyMiddleware(
// ...省略
logger,
routerMiddleware
),
DevTools.instrument({maxAge: 10})
);
export const store = createStore(
combineReducers({
router: routerReducer
// ...省略
}),
enhancer
);
app.js:
import { Provider } from "react-redux";
import { HistoryRouter as Router } from "redux-first-history/rr6";
import { store, history } from "./store";
const App = () => (
//.....
);
router
添加新的库:
- react-router-dom^6.3.0
react-router的依赖可以直接去掉
store
, history
, Router
等几个重要属性了接下来只需要对 routes 进行控制即可:
} />
有一点需要注意, 不管在想要在组件还是
组件中都无法通过 props 来获取路由对象了
组件中显示
组件, 则需要另一个操作:import { Outlet } from "react-router-dom";
function App(props) {
// 其中 Outlet 就是类似于 children 的占位符
return <>
// ...
>
}
之后就是
在 hooks 中的用法:
import { useNavigate } from "react-router-dom";
// hooks
const navigate = useNavigate();
//这会将新路线推送到导航堆栈的顶部
navigate("/new-route");
//这会将当前路线替换为导航堆栈中的新路由
navigate("/new-route", { replace: true });
api 的改动 从 v3 升级之后, 常用的
Link
会从 react-router
移除, 放进 react-router-dom
中, 那么怎么修改会比较方便呢关于 withRouter
在 v6 中, 官方包不会自带这个组件了, 因为我们可以通过他的 api 自由组合:
import {
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
);
}return ComponentWithRouterProp;
}
方案一
直接全部替换, 但是这也会碰到我们的问题所在: 当这些 API, 在某一些子文件包, 或者第三方组件中的时候,
API 的更新就变得异常艰难了, 这也是直接修改的问题点所在
方案二
当前的一个思路就是, 使用
alias
加上文件的兼容来解决这个问题, 比如我在项目中新建文件:routerProxy.js
import * as ReactRouter from '../node_modules/react-router';
import {Link} from 'react-router-dom';
function withRouter(Component) {
//省略
}export * from '../node_modules/react-router';
export {Link,withRouter}
export default ReactRouter;
搭配 webpack 配置:
alias: {
'react-router': path.resolve(__dirname, './source/react-router-proxy.js'),
}
这样运行的时候, 引用
react-router
的东西都会走到此文件中, 而此文件中从 node_modules 中引入, 并且加上兼容, 最终完成升级的过度方案三
使用 babel 的转换来解决:
module.exports = function ({ types: t }) {
const namespace = __dirname + '/../node_modules/react-router/es/';
const canReplace = ({ specifiers }) => {
return (
specifiers.length > 0 &&
specifiers.every((specifier) => {
return (
t.isImportSpecifier(specifier) &&
(specifier.imported.name === 'Link' ||
specifier.imported.name === 'withRouter')
);
})
);
};
const replace = (specifiers) => {
return specifiers.map(({ local, imported }) => {
if (imported.name === 'Link') {
return t.importDeclaration(
[t.importDefaultSpecifier(local)],
t.stringLiteral(`react-router-dom/${imported.name}`),
);
}return t.importDeclaration(
[t.importDefaultSpecifier(local)],
t.stringLiteral(`${namespace}${imported.name}`),
);
});
};
return {
visitor: {
ImportDeclaration(path) {
if (path.node.source.value =https://www.it610.com/article/=='react-router') {
if (canReplace(path.node)) {
// 替换
path.replaceWithMultiple(replace(path.node.specifiers));
}
}
},
},
};
};
通过检测
import {Link} from 'react-router'
等语句, 将其替换成 react-router-dom
仓库方案小结
方案一, 能完美解决, 但是所花费的精力较多, 相对来说我们需要一个平滑的升级
方案二,三 虽然能解决问题, 但是依旧是短暂的, 不可持续的, 最终还是需要我们全面的替换
总结 针对 v3 升级的问题, v6 的变化过大, 升级到 v5 也是可以接受, 但是还是要关注最新版的情况
【react-router|react-router v3 升级至 v6 探索小结】升级的消息, 最好在官方网站上查看, 避免遗漏一些细节
关于 API 的修改, 需要有方案来解决他, 比如 antd 大版本升级, 就会有一个兼容包来迁移
当然也可以使用本文中的一些方法, 但是后面都要逐步替换
在替换时, 使用全局的查找功能, 避免遗漏的出现
对于三方库的兼容 也要进行关注, 寻找新版本的替代品, 如果找不到, 就需要自己来实现了
参考
- https://juejin.cn/post/696624...
- https://reactrouter.com/docs/...
- https://github.com/salvoravid...
推荐阅读
- Android需求之点击跳转至市场评价
- BCD至七段解码器详细解读
- Android 增量更新和升级
- 怎样处理Win8.1升级Win10时的8007045D出错
- Win8.1升级Win10出错代码0x80070003的如何处理?
- Win8应用商店中升级到Win8.1按钮点不开的处理办法
- React-Native开发之原生模块封装(Android)升级版
- windows7升级windows10系统图文详细教程制作详细说明
- 微软官方Windows10升级工具易升制作详细说明
- 本图文详细教程教你win10升级助手官方