Write a simple React router

Furman is good at learning Dharma
Record the previously written React configurable route
The main implementation is to generate routes through config files,
The function should be similar to that of react router config,
But you may gain something by writing down the pit yourself

CRA creates app s (with a learning attitude, I install ts, react router- dom@6.0
Start at 0 and see what pits you will encounter

npm install -D react-router-dom
npm install -D -S @types/react-router-dom

newly build
src/router/routerConfig.ts / / routing configuration
src/type/type.ts / / it is mainly used to export interface s uniformly
src/type/RouterType.ts / / route type

// RouterType.ts
export interface RouterMenuType {
    title: string
}

export interface RouterType {
    path: string, //route
    component: string, //The location of the component, starting from src/pages
    menu?: RouterMenuType, //If it is not empty, it will be displayed on the menu
    icon?: string,
    //The sub route (this part can be directly created in the outer layer, but considering that you want to implement bread crumbs later, it is written here
    routes?: Array<RouterType>, //Next layer routing
    childrens?: Array<RouterType> //Nested Route 
}

Our purpose is to complete the configuration of routing and nested routing by configuring this json file of RouterType type.
Now let's write a simple configuration json.
Before that, I configured alias. Please refer to this link
https://www.jianshu.com/p/6f8a98a9f2e2

import { RouterType } from "@/types/types";

export const authRouterConfig: Array<RouterType> =
  [
    {
      path: '/home',
      component: 'Home',
      menu: { title: 'Home'},
      icon: 'Home',
    },
    {
      path: '/user',
      component: 'User',
      menu: { title: 'User'},
      icon: 'User',
      routes:[
        {
          path: '/edit/:id', 
          component: 'Edit', 
          routes: [
            {
              path: '/test/:id',
              component: 'Test',
            },
          ]
        }
      ],
      childrens: [
        {
          path: '/detail/:id',
          component: 'Detail',
        },
      ],
      
    }

  ];

new file
src/layout/index.tsx
src/layout/MainPane.tsx / / content main page
Src / layout / menubane. TSX / / sidebar
src/router/routerTool.ts / / tool method

First, we need to format our configuration file
Create method in routerTool

//Obtain qualified Router (possible authentication in the future)
export const getAuthRoutes = (routers: Array<RouterType>, fatherRouter: RouterType = { path: "", component: "" }) => {

    return routers.reduce((res: Array<RouterType>, route: RouterType) => {
        const newRoute: RouterType = {
            ...route,
            path: fatherRouter.path + route.path,
            component: (isEmpty(fatherRouter.component) ? '' : fatherRouter.component + '/') + route.component,
        }
        if (!isUndefined(route.childrens)) {
            newRoute.childrens = getAuthRoutes(route.childrens, newRoute)
        }
        if (!isUndefined(route.routes)) res.push(...getAuthRoutes(route.routes, newRoute));
        res.push(newRoute);
        return res;
    }, [])
}

This method is passed into routes and fatherRouter,
Recursively splice the paths of children and component,
The routes in the router are spliced to the path and split to the outside,
The formatted data looks like this

[{
	"path": "/home",
	"component": "Home",
	"menu": {
		"title": "Home"
	},
	"icon": "Home"
}, {
	"path": "/user/edit/:id/test/:id",
	"component": "User/Edit/Test"
}, {
	"path": "/user/edit/:id",
	"component": "User/Edit",
	"routes": [{
		"path": "/test/:id",
		"component": "Test"
	}]
}, {
	"path": "/user",
	"component": "User",
	"menu": {
		"title": "User"
	},
	"icon": "User",
	"routes": [{
		"path": "/edit/:id",
		"component": "Edit",
		"routes": [{
			"path": "/test/:id",
			"component": "Test"
		}]
	}],
	"childrens": [{
		"path": "/user/detail/:id",
		"component": "User/Detail"
	}]
}]

The next step is to render menu and main through this formatted array

MenuPane.tsx  
import { FC } from 'react';
import { NavLink } from 'react-router-dom';
import { RouterType } from '@/types/RouterType';

interface Iprops {
  routes: RouterType[]
}

const LeftPane: FC<Iprops> = (props: Iprops) => {
  const { routes } = props;
  return (
    <div>
      <ul className="list">
        {
          routes.filter((item) => typeof item.menu != "undefined").map((item) => (
            <li key={item.path}>
              <NavLink
                to={item.path}
              >
                <div><b>{item.menu?.title}</b></div>
              </NavLink>
            </li>

          ))
        }
      </ul>
    </div>

  );
}

export default LeftPane;

Menu determines whether to display menu items by judging whether the menu exists,
For Main, we need to create two more component s
src/components/router/RouterComponent.tsx
src/components/router/SwitchRouter.tsx

import React, { FC, lazy, useEffect } from "react";
import { RouterType } from "@/types/RouterType";

interface Iprops {
    route: RouterType
}

const RouterComponent: FC<Iprops> = (props) => {
    console.log(props);
    
    const { route } = props;
    const DynamicComponent = lazy(() => import(`@/pages/${route.component}`));

    return <>
        <React.Suspense fallback={'loading...'}>
            <DynamicComponent {...props} childrenRoutes={route.childrens}/>
        </React.Suspense>
    </>
}

export default RouterComponent;

The RouterComponent accepts the route and uses React.lazy to load the incoming components,
Only strings are stored in our component variable. After splicing, it becomes a folder path, so the configuration file and file directory need to be mapped

import React, { FC } from 'react';
import { Route, Navigate, Routes } from 'react-router-dom';
import { isUndefined, isArray, isEmpty } from 'lodash';
import { RouterType } from '@/types/types';
import RouterComponent from './RouterComponent';

interface Iprops {
    routes: Array<RouterType>
}

const renderRouter = (routes: Array<RouterType>) => {
    return <>
        {isArray(routes) && routes.map((route: RouterType) => (
            <Route
                key={route.path}
                path={route.path}
                element={<RouterComponent route={route}/>}
            >
                { route.childrens && renderRouter(route.childrens)}
            </Route>
        ))}
    </>
}


const SwitchRouter = ({routes}: Iprops) => {
    return (<Routes>
        {renderRouter(routes)}
        {!isEmpty(routes) && <Route
            path="*"
            element={<Navigate to={routes[0].path}></Navigate>}
        />}
    </Routes>)
}

export default SwitchRouter;

In the SwitchRouter, traverse the rendering route and nest render children when judging that there are children
(reaction router here)- dom@6.0 In the older version, we should call renderRouter(...) in nested routing parent component to render nested routing.

The SwitchRouter component renders the route and finds the first route jump from the current route when the match path cannot be found
(used to replace the old version)

(in the SwitchRouter part, whether Routes can be used here remains to be discussed, because there is no 404 interface configured here, and not all Routes will be in the MainPane, such as the login interface or other interfaces that do not need login)

Finally, we simply write a layer of MainPane, and then use it in Layout

import React from 'react';

interface Iprops {
  children: React.ReactNode | React.ReactChild[],
}

const MainPane = (props: Iprops) => {
  const {
    children,
  } = props;
  return (
    <div className="main-pane">
      {children}
    </div>
  );
}

export default MainPane;
import React, { FC } from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { authRouterConfig } from "@/router/routerConfig";

import SwitchRouter from "@/components/Router/SwitchRouter";
import MainPane from "./MainPane";
import MenuPane from "./MenuPane";
import { getAuthRoutes } from "@/router/routerTool";



const Index: FC = () => {
    const authRoutes = getAuthRoutes(authRouterConfig);
    
    return <Router>

        <div className="layout">
            <div>
                <div>
                    <MenuPane routes={authRoutes} />
                </div>
                <div>
                    <MainPane>
                        <SwitchRouter routes={authRoutes}></SwitchRouter>
                    </MainPane>
                </div>
            </div>
        </div>
    </Router>

}

export default Index;

After the Layout call, we can write simple components,
Home and User

Here we use user as an example
src/pages/User/index.tsx
src/pages/User/Detail.tsx
src/pages/User/Edit
src/pages/User/Edit/index.tsx
src/pages/User/Edit/Test.tsx

index.tsx
    const { childrenRoutes } = props;
    console.log('childrensRoutes', childrenRoutes);
    
    let navigate = useNavigate();
    return <div>
        <button onClick={() => navigate({pathname: '/user/detail/1'})}>Detaillll</button>
        <br/>
        <br/>
        <button onClick={() => navigate({pathname: '/user/edit/1'})}>Edit</button>
        <br/>
        <br/>
        <button onClick={() => navigate({pathname: '/user/edit/1/test/1'})}>Edit/Test</button>
        <br/>
        <br/>
        <Outlet />
    </div>
};

Usenavigator is used to replace the old version of History
For the new API of 6.0, we just put / user/detail into children when rendering,
So here, the Outlet is the exit of detail, and the detail component will be displayed here

The remaining components also need to be created according to the path of router-config.json

Such a simple configurable route that supports lazy loading can be run.

In the next step, redux and this configurable route may be combined to complete the breadcrumb navigation.

Tags: Javascript Front-end React

Posted on Sun, 21 Nov 2021 21:24:30 -0500 by php-n00b