background
The react-router version used in the current business project is 3.x, while the current mainstream use is 5.x or above,
This article will explore the upgrade plan of react-router
current situation
Currently using the react-router3.x version plus the collocation library with redux react-router-redux
4.x 5.x API changes
Because the difference between 4 and 5 is not very big, so let's talk about it together
- Routing cannot be centralized in one file
-
<Router>
representation as a certain category, for example:<BrowserRouter>
,<HashRouter>
etc. -
<Switch>
Components to match routes, exclusive routes -
<Link>
component,<NavLink>
component - Replaced <IndexRoute>` with exact attribute
-
react-router-dom
appears, only need to rely on this component - Support React 16, compatible with React >= 15
- Route component path can be an array
- If there is no matching route, you can also pass <Redirect>
6.x API changes
-
<Switch>
renamed to<Routes>
, this exact is no longer needed. -
<Route>
. - Route nesting is supported again
-
<Navigate>
replaces<Redirect>
- Replace ---b5514609ea8210569f680b257f2a01c4
useNavigate
withuseHistory
. - Remove
<Prompt>
component - New hook
useRoutes
instead ofreact-router-config
. - Size reduction: from
20kb
to8kb
9. Enhanced path pattern matching algorithm.
summary
There are many break changes from 3 to 4, 5, likewise, from 4,5 to 6
So if the current project is 3, we are ready to upgrade to 6 in one go to avoid multiple changes in the middle
Pain points of upgrade
API modification:
Generally speaking, the only difficulty lies in the syntax of the old API, the calls have changed, so once the upgrade, all the places have to be rewritten
Removal of API:
- There is a replacement with a new API that is the same as a modification
- Simple deletion, here also needs to be modified in all places, but this situation is relatively rare, and the deleted API is used in very few places
API added:
Simple additions do not affect existing upgrades
At the same time, we need to distinguish the API
- Configuration API, which is generally only used once, such as
<Router>
, only used in the routing configuration page, then we can directly modify it when we upgrade - Use API, this type of API covers a wide range, for example
router.push
changed tohistory.push
upgrade
Now start our upgrade
redux upgrade
Need to upgrade redux related libraries:
- react-redux^6.0.0
- redux-first-history
Can delete library: react-router-redux
connected-react-router
Only v4 and v5 are supported, here we useredux-first-history
, a smaller, faster alternative
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);
About the redux-first-history
warehouse, if there are dependencies redux-devtools
, redux-devtools-log-monitor
and other libraries, you can not use it
Use like this:
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
Add new library:
- react-router-dom^6.3.0
The dependency of react-router can be removed directly
After the replacement of redux above, we already have several important attributes such as store
, history
, Router
Next, you only need to control the routes:
<Routes>
<Route path={url} element={<App/>}>
<Route path={url2} element={<Foo/>} />
</Route>
</Routes>
One thing to note, no matter in the<App>
component or<Foo>
component, the routing object cannot be obtained through props
To display the ---987806446998c767d0d5eb40fa09e353 <APP>
component in the <Foo>
component, another action is required:
import { Outlet } from "react-router-dom";
function App(props) {
// 其中 Outlet 就是类似于 children 的占位符
return <>
// ...
<Outlet />
</>
}
after that
Usage in hooks:
import { useNavigate } from "react-router-dom";
// hooks
const navigate = useNavigate();
//这会将新路线推送到导航堆栈的顶部
navigate("/new-route");
//这会将当前路线替换为导航堆栈中的新路由
navigate("/new-route", { replace: true });
api changes
After upgrading from v3, the commonly used Link
will be removed from react-router
, and put into react-router-dom
, so how to modify it is more convenient?
About withRouter
In v6, the official package will not come with this component, because we can freely combine it through his 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;
}
Option One
Replace all directly, but this will also encounter our problem: when these APIs, in some sub-packages, or third-party components,
It becomes extremely difficult to update the API, which is also the problem with direct modification
Option II
One of the current ideas is to use alias
plus file compatibility to solve this problem. For example, I create a new file in the project:
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;
With webpack configuration:
alias: {
'react-router': path.resolve(__dirname, './source/react-router-proxy.js'),
}
When running in this way, the reference react-router
will go to this file, and this file is imported from node_modules, and added compatibility, and finally complete the transition of the upgrade
third solution
Use babel's transformation to solve:
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));
}
}
},
},
};
};
By detecting import {Link} from 'react-router'
and other statements, replace it with react-router-dom
warehouse
Program summary
Solution 1 can be solved perfectly, but it takes a lot of energy. Relatively speaking, we need a smooth upgrade solution. Although solutions 2 and 3 can solve the problem, they are still short-term and unsustainable. In the end, we still need to replace them comprehensively.
Summarize
For the v3 upgrade problem, the changes in v6 are too large, and it is acceptable to upgrade to v5, but you still need to pay attention to the latest version.
The news of the upgrade is best viewed on the official website to avoid missing some details about the API modification. There needs to be a solution to solve it. For example, when antd is upgraded to a large version, there will be a compatibility package for migration. Of course, some of the articles in this article can also be used. method, but will be gradually replaced later. When replacing, use the global search function to avoid omissions. Pay attention to the compatibility of the tripartite library, and look for a new version of the replacement. If you can't find it, you need to implement it yourself.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。