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
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
- Configure routes
- Used again in parent routes of nested routines
CompileRouter
And passed inroutes
Can 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:
- A directory to search,
- A tag indicates whether to search for subdirectories as well,
- A regular expression that matches a file.
- Mode Indicates the loading mode of the module. The common values are sync, lazy, lazy-once, and eager
sync
Package directly to the current file, load and execute synchronously
lazy
Lazy loading separates separate chunk files
lazy-once
Lazy loading splits the chunks into separate files that are loaded and loaded next time to read the code directly from memory.
eager
No 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
-
Let context = require.context(“.. let context = require.context(“.. /pages”, true, /.js$/); Let paths = context.keys() // Get all file paths \
-
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
- Creating a Routing page
- 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 –