背景
在以后业务我的项目中应用的 react-router 版本为 3.x, 而以后支流应用的是 5.x 以上,
本文就来探索 react-router 降级的计划
当前情况
目前应用的是 react-router3.x 版本 再加上和 redux 的搭配库 react-router-redux
一起应用的
4.x 5.x API 的变动
因为 4 和 5 之间差异不是很大, 所以就放一起讲了
- 路由不能集中在一个文件中
<Router>
具象为某一类, 比方:<BrowserRouter>
,<HashRouter>
等等<Switch>
组件来匹配路由, 排他性路由<Link>
组件 ,<NavLink>
组件- 用 exact 属性代替了 <IndexRoute>`
react-router-dom
的呈现, 只须要依赖此组件即可- 反对 React 16 , 兼容 React >= 15
- Route 组件 path 能够为数组
- 如果没有匹配的路由,也可通过 <Redirect>
6.x API 的变动
<Switch>
重命名为<Routes>
, 不再须要该 exact。<Route>
的新个性变更。- 再度反对路由嵌套
<Navigate>
代替<Redirect>
- 用
useNavigate
代替useHistory
。 - 删除
<Prompt>
组件 - 新钩子
useRoutes
代替react-router-config
。 - 大小缩小:从
20kb
到8kb
9. 加强的门路模式匹配算法。
小结
从 3 到 4, 5 之间有许多 break change, 同样地, 4,5 到 6 之间也是这样
所以以后我的项目如果是 3 的话, 咱们就筹备一口气降级到 6, 防止两头的多重更改
降级的痛点
API 批改:
一般来说, 惟一的难点在于旧 API 的语法, 调用产生了变动, 导致一旦降级, 所有的中央都要从新写一遍
API 的删除:
- 有呈现新 API 的替换 这种状况是和批改一样的
- 单纯的删除, 这里的话也是须要所有的中央批改的, 然而这种状况比拟少, 而且被删除的 API 用到的中央也很少
API 新增:
单纯的新增并不影响现有的降级
同时 API 咱们须要有所辨别
- 配置型 API, 这种个别只会应用一次, 比方
<Router>
, 只在路由配置页面应用, 那咱们降级的时候间接批改便能够了 - 应用型 API, 这类 api 笼罩比拟宽泛, 比如说
router.push
改成了history.push
降级
当初开始咱们的降级
redux 降级
须要降级 redux 相干库:
- react-redux^6.0.0
- redux-first-history
能够删除库: react-router-redux
connected-react-router
只反对 v4 和 v5, 这里咱们应用redux-first-history
, 更小, 更快的代替计划
store.js:
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';
// 省略 createReduxHistoryContext
const 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 = () => (<Provider store={store}>
<Router history={history}>
//.....
</Router>
</Provider>
);
router
增加新的库:
- react-router-dom^6.3.0
react-router 的依赖能够间接去掉
通过下面 redux 的替换, 咱们曾经领有了 store
, history
, Router
等几个重要属性了
接下来只须要对 routes 进行管制即可:
<Routes>
<Route path={url} element={<App/>}>
<Route path={url2} element={<Foo/>} />
</Route>
</Routes>
有一点须要留神, 不论在
<App>
组件还是<Foo>
组件中都无奈通过 props 来获取路由对象了
想要在 <APP>
组件中显示 <Foo>
组件, 则须要另一个操作:
import {Outlet} from "react-router-dom";
function App(props) {
// 其中 Outlet 就是相似于 children 的占位符
return <>
// ...
<Outlet />
</>
}
之后就是
在 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 (
<Component
{...props}
router={{location, navigate, params}}
/>
);
}
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 === 'react-router') {if (canReplace(path.node)) {
// 替换
path.replaceWithMultiple(replace(path.node.specifiers));
}
}
},
},
};
};
通过检测 import {Link} from 'react-router'
等语句, 将其替换成 react-router-dom
仓库
计划小结
计划一, 能完满解决, 然而所破费的精力较多, 相对来说咱们须要一个平滑的降级
计划二, 三 尽管能解决问题, 然而仍旧是短暂的, 不可继续的, 最终还是须要咱们全面的替换
总结
针对 v3 降级的问题, v6 的变动过大, 降级到 v5 也是能够承受, 然而还是要关注最新版的状况
降级的音讯, 最好在官方网站上查看, 防止脱漏一些细节
对于 API 的批改, 须要有计划来解决他, 比方 antd 大版本升级, 就会有一个兼容包来迁徙
当然也能够应用本文中的一些办法, 然而前面都要逐渐替换
在替换时, 应用全局的查找性能, 防止脱漏的呈现
对于三方库的兼容 也要进行关注, 寻找新版本的替代品, 如果找不到, 就须要本人来实现了
参考
- https://juejin.cn/post/696624…
- https://reactrouter.com/docs/…
- https://github.com/salvoravid…