1

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
  1. Routing cannot be centralized in one file
  2. <Router> representation as a certain category, for example: <BrowserRouter> , <HashRouter> etc.
  3. <Switch> Components to match routes, exclusive routes
  4. <Link> component, <NavLink> component
  5. Replaced <IndexRoute>` with exact attribute
  6. react-router-dom appears, only need to rely on this component
  7. Support React 16, compatible with React >= 15
  8. Route component path can be an array
  9. If there is no matching route, you can also pass <Redirect>

6.x API changes

  1. <Switch> renamed to <Routes> , this exact is no longer needed.
  2. <Route> .
  3. Route nesting is supported again
  4. <Navigate> replaces <Redirect>
  5. Replace ---b5514609ea8210569f680b257f2a01c4 useNavigate with useHistory .
  6. Remove <Prompt> component
  7. New hook useRoutes instead of react-router-config .
  8. Size reduction: from 20kb to 8kb
    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

  1. 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
  2. Use API, this type of API covers a wide range, for example router.push changed to history.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 use redux-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.

refer to


Grewer
984 声望28 粉丝

Developer