There is a sense that using the React-router-dom when writing projects is very convenient to use, but difficult to maintain because the roads are scattered across components. The config mode provided in react-router-dom is used to write routes. The advantage of this mode is that we can centralize the logic in one place and configure routes easily

The project address

Gitee.com/d718781500/…

1. Centralized routing

Let’s start by defining the following data in/SRC /router/index.js

The React routing official documentation provides an example of configuring centralized routing, which is similar to a VUE route and generates a configuration file, as expected

// The configuration of a route is required, which is an array import Discover from ".. /pages/Discover" import Djradio from ".. /pages/Discover/Djradio" import Playlist from ".. /pages/Discover/Playlist" import Toplist from ".. /pages/Discover/Toplist" import Friends from ".. /pages/Friends" import Mine from ".. /pages/Mine" import Page404 from ".. /pages/Page404" const routes = [ { path: "/friends", component: Friends }, { path: "/mine", component: Mine }, { path: "/discover", component: Discover, children: [ { path: "/discover/djradio", component: Djradio }, { path: "/discover/playlist", component: Playlist }, { path: "/discover/toplist", component: Toplist}]}, {//Page404 Path: "*", Component: Page404}] export default routesCopy the code

We can use the above configuration to generate a route. The configuration of the redirect exact is not written. Let’s start with a simple configuration

2. File directories

The above configuration uses a centralized routing configuration pattern similar to VUE, so let’s show the structure directory of my current demo

Project directory structure

\

\

SRC /pages directory structure

├ ─ Discover │ │ ABC. Js │ │ index. The js │ │ │ ├ ─ Djradio │ │ │ index. The js │ │ │ lf. Js │ │ │ │ │ └ ─ gv │ │ index. The js │ │ │ ├ ─ Playlist │ │ index. Js │ │ │ └ ─ Toplist │ index. The js │ ├ ─ Entertaiment │ index. The js │ ├ ─ Friends │ index. The js │ xb. Js │ ├ ─ mime │ Index. Js │ └ ─ Page404 index. JsCopy the code

With these structures in place, the import files mentioned in 1 can be combined to look uncomplicated. We can then encapsulate a component called CompileRouter which is used to compile routes

3. Create CompileRouter

This component is created in SRC /utils, which calculates the component through the incoming route configuration, so the question is, why create this component?

Let’s review how the React route is written. The React route requires a base component, the HashRouter or BrowserRouter, which act as a cornerstone component

Then you need a routing recipe that accepts a path map to a Component

So let’s write some pseudocode to illustrate that

Import {HashRouter as router,Route} from "react-router-dom" class Demo Extends React.Component {render(){// Cornerstone Route <Router> // Route recipe component matches component <Route path="/" Component ={Home}/> <Route  path="/mine" component={Mine}/> </Router> } }Copy the code

This is the basic usage, so our job for CompileRouter is to generate a Route just like the one in the code above and then display it on the component

Now that we understand the basics of Compile, let’s start coding

The design for CompileRouter is to receive data in an array that matches the route configuration, as shown in the 1 code, with the attribute routes

Import React from 'React' import {Switch, Route} from "react-router-dom"; export default class CompileRouter extends React.Component { constructor() { super() this.state = { c: [] } } renderRoute() { let { routes } = this.props; // console.log(routes) //render IsArray (Routes) &&routes.length > 0) {// Make sure routes passed in is an Array // Routes let finalRoutes = routes.map(route => {// Each route looks like this {path:" XXX ", Component :" XXX "} // If route has child nodes {path:"xxx",component:"xxx",children:[{path:"xxx"}]} return <Route path={route.path} key={route.path} render={ // If the route has nested routes, we can pass the children configuration data to the component so that the component can compile nested routes when it calls CompileRouter () => < route.component.component.compilerouter routes={route.children} /> } /> }) this.setState({ c: FinalRoutes})} else {throw new Error('routes must be an array and length greater than 0')}} componentDidMount() { RenderRoute ()} render() {let {c} = this.state; render() {let {c} = this.state; return ( <Switch> {c} </Switch> ) } }Copy the code

The above code is used to handle routes data and declare such a component. I’ve commented out what each step does

4. Use CompileRouter

In fact, we can think of the encapsulated component as a view component in vue-Router
for the moment, and then we need to render level 1 routing on the page

In the SRC/app. Js

import React from 'react' import { HashRouter as Router, Link} from 'react-router-dom' import CompileRouter from "./utils/ CompileRouter" Import routes from "./router" console.log(routes) class App extends React.Component {render() {return (< the Router > < Link to = "/ friends" > friends < / Link > | < Link to = "/ discover" > find < / Link > | < Link to = "/ mime" > I < / Link > {/* As a view component of the UE -router we need to pass the route configuration data to */} <CompileRouter routes={routes} /> </ router >)}} export default AppCopy the code

When this is done, the page should actually display level 1 routing perfectly

5. Nested routines are handled by

We have rendered the level 1 route above and can jump, but what about the level 2 route? This is easy, just find the parent of the secondary route and continue to CompileRouter

We can see from the configuration that the Discover route has nested routines, so we take the Discover route as an example. First, let’s look at the structure diagram

The index.js is the Discover view component, which is the parent of the nested route, so we just need to continue to use CompileRouter in this index.js

import React from 'react' import { Link } from "react-router-dom" import CompileRouter from ".. /.. /utils/compileRouter" function Discover(props) {let {routes} = props // console.log(routes) let links = routes.map(route => { return ( <li key={route.path}> <Link To ={route.path}>{route.path}</Link> </li>)}) return (<fieldset> </legend> </h1> Route*/} <CompileRouter routes={routes} /> </fieldset>)} Discover. Meta ={Route*/} <CompileRouter routes={routes} /> </fieldset>)} Discover. Title: "Discover ", icon: ""} export default DiscoverCopy the code

So we’re going to remember, whenever there’s a nested pattern there are two things we’re going to do

  1. Configure routes
  2. Used again in parent routes of nested routinesCompileRouterAnd passed inroutesCan be

6.  require.context

Above we implemented a centralized routing configuration, but we found a problem

A lot of components are introduced, in fact, more are introduced in the project, and it would be disastrous for us to introduce them one by one, so we can use a nice API that Webpack provides,require.context let’s talk about how it works first

Import require.context automatically, using this method can reduce tedious component import, and can deeply recursive directory, do things that import cannot do

use

You can create your own context by using require.context().

We can pass four arguments to this function:

  1. A directory to search,
  2. A tag indicates whether to search for subdirectories as well,
  3. A regular expression that matches a file.
  4. Mode Indicates the loading mode of the module. The common values are sync, lazy, lazy-once, and eager
  • syncPackage directly to the current file, load and execute synchronously

    lazyLazy loading separates separate chunk files

    lazy-onceLazy loading splits the chunks into separate files that are loaded and loaded next time to read the code directly from memory.

    eagerNo separate chunk files will be separated, but the promise will be returned, and the code will be executed only after the promise is invoked. It can be understood that the code is loaded first, but we can control the delay in executing this part of the code.

Webpack parses require.context() in the code at build time.

The syntax is as follows:

require.context(
  directory,
  (useSubdirectories = true),
  (regExp = /^./.*$/),
  (mode = 'sync')
);
Copy the code

Example:

require.context('./test', false, /.test.js$/); // create a context where the file is from the test directory and request ends with '.test.js'. require.context('.. /', true, /.stories.js$/); // create a context in which all files are from the parent folder and all of its children. Request ends with '.stories.js'.Copy the code

api

The function has three attributes: resolve, keys, and ID.

  • Resolve is a function that returns the module ID of the resolved request.

  • let p = require.context(“…” ,true,” XXX “) p.resolve(” a path “)\

  • Keys is also a function that returns an array of all the requests that could be handled by this Context Module.

The return value of require.context is a function where we can pass in the path of the file and get the modular component

let components = require.context('.. /pages', true, /.js$/, 'sync') let paths = components.keys()// console.log(paths) let routes = paths.map(path => {let component  = components(path).default path = path.substr(1).replace(//\w+.js$/,"") return { path, component } }) console.log(routes)Copy the code

conclusion

Although there are many apis and returned values, let’s just take two as examples

  1. Let context = require.context(“.. let context = require.context(“.. /pages”, true, /.js$/); Let paths = context.keys() // Get all file paths \

  2. Let context = require.context(“.. /pages”, true, /.js$/); Paths = paths.map(path => {// let paths = context.keys() // Let routes = paths.map(path => {// context(path).default; console.log(component) })\

So that’s all you need to know. Let’s move on

7. Conversion of flat data into tree structure (convertTree algorithm)

I named this algorithm myself, but first we need to understand why we need to convert the data to tree

Our expected routes data should look like this

// What is the purpose? Const routes = [{path:" ", Component: XXX children:[{path:" XXX "component: XXX}]}] const routes = [{path:" XXX" component: XXX}]Copy the code

But the actual data after we use require.context looks like this

As you can see, this data is completely flat, without any nesting, so our first step is to convert this flat data into the tree structure that we expect. Let’s do it step by step

7.1 Flattening data using require.context

The first step is to deal with a structure like the one above, where the code is annotated and not too difficult

//require.context() // 1. A directory to search, // 2. A flag indicates whether subdirectories should also be searched, and // 3. A regular expression that matches files. let context = require.context(".. /pages", true, /.js$/); Paths = paths.map(path => {// let paths = context.keys()// Let routes = paths.map(path => {// context(path).default; / / component extended attributes let meta = convenient rendering menu component (" meta ") | | {} / / console log (path) / / the purpose of the regular / / because the address is. / Discover/Djradio/index, js does not directly use of this type, so must carry on the processing / / 1. After get rid of the former ". "the result is/Discover/Djradio/index. The js / / 2. We can't use it directly because we expect /Discover/Djradio, so we kill //3 with the regex. Js /Discover/abc.js is not used in the path property of the route configuration, so the.js suffix is replaced by path = with the re path.substr(1).replace(/(/index.js|.js)$/, "") // console.log(path) return { path, component, meta } })Copy the code

7.2 ConvertTree algorithm is implemented

So once we’ve processed the data, we’re going to encapsulate a method that’s going to process the flat data into a tree, and the algorithm is O(n^2) in time.

function convertTree(routes) { let treeArr = []; Route. ForEach (route => {let comparePaths = route.path.substr(1).split("/") // Console. log(comparePaths) if (comparepaths.length === 1) {parent_id route.id = comparepaths.join ("")} Id = comparepaths.join (""); Pop () route.parent_id = comparepaths.join ("")}}) //2 Parent_id if (route.parent_id) {// There is a parent node // let target = routes.find(v => vid === route.parent_id); // Check whether the parent has children if (! target.children) { target.children = [] } target.children.push(route) } else { treeArr.push(route) } }) return treeArr }Copy the code

After the above processing, you have a tree structure

Then we just need to export the data and pass it to CompileRouter on our app

7.3 Be careful in the future

Now you just need to create a file in Pages to automatically process and compile routes. For nested routes, add CompileRouter to your component

  1. Creating a Routing page
  2. Nested routines are added to the parent routing component

8. Extend static properties

The effect we are currently creating is there, but there is a problem if we use it to render the menu. There is no content to render the menu, so we can extend the static attribute meta(or something else) to the component and make a few minor changes to our automated compilation code

component

Automatic processing logic complete code

//require.context() // 1. A directory to search, // 2. A flag indicates whether subdirectories should also be searched, and // 3. A regular expression that matches files. let context = require.context(".. /pages", true, /.js$/); Paths = paths.map(path => {// let paths = context.keys()// Let routes = paths.map(path => {// context(path).default; / / component extended attributes let meta = convenient rendering menu component (" meta ") | | {} / / console log (path) / / the purpose of the regular / / because the address is. / Discover/Djradio/index, js does not directly use of this type, so must carry on the processing / / 1. After get rid of the former ". "the result is/Discover/Djradio/index. The js / / 2. We can't use it directly because we expect /Discover/Djradio, so we kill //3 with the regex. Js /Discover/abc.js is not used in the path property of the route configuration, so the.js suffix is replaced by path = with the re path.substr(1).replace(/(/index.js|.js)$/, "") // console.log(path) return { path, component, // console.log(routes) // console.log(routes) // console.log(routes) // console.log(routes) // console.log(routes) // console.log //id //parent_id function convertTree(routes) { let treeArr = []; Route. ForEach (route => {let comparePaths = route.path.substr(1).split("/") // Console. log(comparePaths) if (comparepaths.length === 1) {parent_id route.id = comparepaths.join ("")} Id = comparepaths.join (""); Pop () route.parent_id = comparepaths.join ("")}}) //2 Parent_id if (route.parent_id) {// There is a parent node // let target = routes.find(v => vid === route.parent_id); // Check whether the parent has children if (! target.children) { target.children = [] } target.children.push(route) } else { treeArr.push(route) } }) return treeArr } Export default convertTree(routes) // Get a module // console.log(p("./Discover/index.js").default) // Generate a routing configuration // const routes = [ // { // path: "", // component, // children:[ // {path component} // ] // } // ]Copy the code

Write in the last

In the next installment, I’ll write about how to handle CompileRouter for authentication and other applications in a project

 End