• 售前

  • 售后

热门帖子
入门百科

react主动化构建路由的实现

[复制链接]
老顽童760 显示全部楼层 发表于 2021-10-26 13:41:51 |阅读模式 打印 上一主题 下一主题
目次



  • 1.路由会合式
  • 2.文件目次
  • 3.创建CompileRouter
  • 4.使用CompileRouter
  • 5.嵌套路由处理
  • 6. require.context

    • 使用
    • api
    • 总结

  • 7.扁平数据转换为树形布局的(convertTree算法)

    • 7.1使用require.context将数据处理成扁平化
    • 7.2 实现convertTree算法
    • 7.3 以后要留意的

  • 8.扩展静态属性

    • 组件
    • 自动化处理逻辑完备代码

  • 写在末了




在使用react-router-dom在编写项目的时候有种感觉就是,使用起来非常的方便,但是如果维护起来,那便是比较贫困了,由于各大路由分散在各个组件中. 以是我们就会想到,使用react-router-dom中提供的config模式来编写我们的路由,如许写的好处就是我们可以将逻辑会合在一处,配置路由比较方便

项目地址
https://gitee.com/d718781500/autoRouter


1.路由会合式


我们先将下列数据定义在/src/router/index.js中
在react的路由官方文档中就提供了配置会合式路由的案例,大抵是如许的仿照vue的路由,生成一个配置文件,预期是如许的
  1. //需要一个路由的配置,它是一个数组
  2. import Discover from "../pages/Discover"
  3. import Djradio from "../pages/Discover/Djradio"
  4. import Playlist from "../pages/Discover/Playlist"
  5. import Toplist from "../pages/Discover/Toplist"
  6. import Friends from "../pages/Friends"
  7. import Mine from "../pages/Mine"
  8. import Page404 from "../pages/Page404"
  9. const routes = [
  10.     {
  11.         path: "/friends",
  12.         component: Friends
  13.     },
  14.     {
  15.         path: "/mine",
  16.         component: Mine
  17.     },
  18.    
  19.     {
  20.         path: "/discover",
  21.         component: Discover,
  22.         children: [
  23.             {
  24.                 path: "/discover/djradio",
  25.                 component: Djradio
  26.             },
  27.             {
  28.                 path: "/discover/playlist",
  29.                 component: Playlist
  30.             },
  31.             {
  32.                 path: "/discover/toplist",
  33.                 component: Toplist
  34.             }
  35.         ]
  36.     },
  37.     {//Page404这个配置一定要在所有路由配置之后
  38.         path: "*",
  39.         component: Page404
  40.     }
  41. ]
  42. export default routes
复制代码
我们可以通过上述配置,来生成一个路由.固然上述的配置也只是做了简朴的处理,另有redirect exact等属性没有写,我们还是从一个简朴的开始吧


2.文件目次


上述的配置中使用了雷同于vue的会合式路由配置模式,那么下面就展示下我当前这个demo的布局目次吧

项目目次布局

src/pages目次布局
  1. ├─Discover
  2. │  │  abc.js
  3. │  │  index.js
  4. │  │
  5. │  ├─Djradio
  6. │  │  │  index.js
  7. │  │  │  lf.js
  8. │  │  │
  9. │  │  └─gv
  10. │  │          index.js
  11. │  │
  12. │  ├─Playlist
  13. │  │      index.js
  14. │  │
  15. │  └─Toplist
  16. │          index.js
  17. ├─Entertaiment
  18. │      index.js
  19. ├─Friends
  20. │      index.js
  21. │      xb.js
  22. ├─Mine
  23. │      index.js
  24. └─Page404
  25.         index.js
复制代码
有了这些布局之后,那么在1中提到的引入文件团结起来看就不懵逼啦,接下来我们可以封装一个组件,给他取个名字叫做CompileRouter这个组件专门用于编译路由


3.创建CompileRouter


这个组件我们把它创建在src/utils中,作用就是通过传入的路由配置,然后盘算出这个组件,那么问题来了,为什么要创建这个组件呢?

让我们回顾一下react路由的编写方式吧,react路由必要一个底子组件HashRouter或者BrowserRouter这两个相当于一个基石组件
然后还必要一个路由配方这个组件可以担当一个path映射一个component

我们来写段伪代码来说明一下
  1. //引入路由基本组件(要在项目中安装 npm i react-router-dom)
  2. import {HashRouter as Router,Route} from "react-router-dom"
  3. class Demo extends React.Component {
  4.     render(){
  5.         //基石路由
  6.         <Router>
  7.             //路由配方组件 通过path匹配component
  8.             <Route path="/" component={Home}/>
  9.              <Route path="/mine" component={Mine}/>
  10.         </Router>
  11.     }
  12. }
复制代码
这是根本用法,以是我们CompileRouter这个组件的工作就是,生成如上代码中的Route一样,生成Route然后展示在组件上
在了解到Compile的根本作用之后,下面我们就开始编码吧
我个CompileRouter设计是担当一个数据,这个数据必须是符合路由配置的一个数组,就像1里代码中所示的数组一样,担当的属性为routes
  1. //这个文件通过routes配置来编译出路由
  2. import React from 'react'
  3. import { Switch, Route } from "react-router-dom";
  4. export default class CompileRouter extends React.Component {
  5.     constructor() {
  6.         super()
  7.         this.state = {
  8.             c: []
  9.         }
  10.     }
  11.     renderRoute() {
  12.         let { routes } = this.props;//获取routes路由配置
  13.         //1.通过routes生成Route组件
  14.         //确保routes是一个数组
  15.         // console.log(routes)
  16.         //render 不会重复让组件的componentDidMount和componentWillUnmount重复调用
  17.         if (Array.isArray(routes) && routes.length > 0) {
  18.             //确保传入的routes是个数组
  19.            // 循环迭代传入的routes
  20.             let finalRoutes = routes.map(route => {
  21.                 //每个route是这个样子的 {path:"xxx",component:"xxx"}
  22.                 //如果route有子节点 {path:"xxx",component:"xxx",children:[{path:"xxx"}]}
  23.                 return <Route path={route.path} key={route.path} render={
  24.                        // 这么写的作用就是,如果路由还有嵌套路由,那么我们可以把route中的children中的配置数据传递给这个组件,让组件再次调用CompileRouter的时候就能编译出嵌套路由了
  25.                     () => <route.component routes={route.children} />
  26.                 } />
  27.             })
  28.             this.setState({
  29.                 c: finalRoutes
  30.             })
  31.         } else {
  32.             throw new Error('routes必须是一个数组,并且长度要大于0')
  33.         }
  34.     }
  35.     componentDidMount() {
  36.         //确保首次调用renderRoute计算出Route组件
  37.         this.renderRoute()
  38.     }
  39.     render() {
  40.         let { c } = this.state;
  41.         return (
  42.             <Switch>
  43.                 {c}
  44.             </Switch>
  45.         )
  46.     }
  47. }
复制代码
上述代码就是用于行止理routes数据并且声称如许的组件,每一步的作用我都已经在上面用解释标明确


4.使用CompileRouter


着实我们可以把封装的这个组件当成是vue-router中的视图组件<router-view/>就临时先这么以为吧,接下来我们必要在页面上渲染1级路由了

在src/app.js
  1. import React from 'react'
  2. import { HashRouter as Router, Link } from 'react-router-dom'
  3. //引入我们封装的CompileRouter罪案
  4. import CompileRouter from "./utils/compileRouter"
  5. //引入在1中定义的路由配置数据
  6. import routes from "./router"
  7. console.log(routes)
  8. class App extends React.Component {
  9.     render() {
  10.         return (
  11.             <Router>
  12.                 <Link to="/friends">朋友</Link>
  13.                 |
  14.                 <Link to="/discover">发现</Link>
  15.                 |
  16.                 <Link to="/mine">我的</Link>
  17.                 {/*当成是vue-router的视图组件 我们需要将路由配置数据传入*/}
  18.                 <CompileRouter routes={routes} />
  19.             </Router>
  20.         )
  21.     }
  22. }
  23. export default App
复制代码
写完后,那么页面上着实就可以完美的展示1级路由了


5.嵌套路由处理


上面我们已经对1级路由进行了渲染,可以跳转,但是二级路由怎么处理呢?着实也很简朴,我们只必要找到二级路由的父路由,继续使用CompileRouter就可以了
我们从配置中可以看到,Discover这个路由是具有嵌套路由的,以是我们就以Discover路由为例子,起首我们看下布局图

图上的index.js就是Discover这个视图组件了,也是嵌套路由的父级路由,以是我们只必要在这个index.js中继续使用CompileRouter就可以了
  1. import React from 'react'
  2. import { Link } from "react-router-dom"
  3. import CompileRouter from "../../utils/compileRouter"
  4. function Discover(props) {
  5.     let { routes } = props //这个数据是从ComileRouter组件编译的时候传递过来的children
  6.     // console.log(routes)
  7.     let links = routes.map(route => {
  8.         return (
  9.             <li key={route.path}>
  10.                 <Link to={route.path}>{route.path}</Link>
  11.             </li>
  12.         )
  13.     })
  14.     return (
  15.         <fieldset>
  16.             <legend>发现</legend>
  17.             <h1>我发现,不能说多喝热水</h1>
  18.             <ul>
  19.                 {links}
  20.             </ul>
  21.             {/*核心代码,再次使用即可 这里将通过children数据可以渲染出Route*/}
  22.             <CompileRouter routes={routes} />
  23.         </fieldset>
  24.     )
  25. }
  26. Discover.meta = {
  27.     title: "发现",
  28.     icon: ""
  29. }
  30. export default Discover
复制代码
以是我们以后记住,只要是有嵌套路由我们要做两件事
       
  • 配置routes   
  • 在嵌套路由的父级路由中再次使用CompileRouter,并且传入routes即可

6. require.context


上面我们实现了一个路由会合式的配置,但是我们会发现一个问题

引入了很多的组件,现实上,在项目中引入的更多,如果一个一个引入,对我们来说是劫难性的,以是我们可以使用webpack提供的一个很好用的api,require.context我们先说说它是怎么使用的吧
自动化导入require.context方法,使用这个方法可以镌汰繁琐的组件引入,而且可以深度的递归目次,做到import做不到的事变 下面我们来看一下这个方法是怎样使用的


使用


你可以通过 require.context() 函数来创建自己的 context。
可以给这个函数传入4个参数:
       
  • 一个要搜索的目次,   
  • 一个标志表现是否还要搜索其子目次,   
  • 一个匹配文件的正则表达式。   
  • mode  模块加载模式,常用值为 sync、lazy、lazy-once、eager
              
    • sync 直接打包到当前文件,同步加载并执行        
    • lazy 延长加载会分离出单独的 chunk 文件        
    • lazy-once 延长加载会分离出单独的 chunk 文件,加载过下次再加载直接读取内存里的代码。        
    • eager 不会分离出单独的 chunk 文件,但是会返回 promise,只有调用了 promise 才会执行代码,可以明确为先加载了代码,但是我们可以控制延长执行这部分代码。   
       
webpack 会在构建中剖析代码中的 require.context() 。

语法如下:
  1. require.context(
  2.   directory,
  3.   (useSubdirectories = true),
  4.   (regExp = /^\.\/.*$/),
  5.   (mode = 'sync')
  6. );
复制代码
示例:
  1. require.context('./test', false, /\.test\.js$/);
  2. //(创建出)一个 context,其中文件来自 test 目录,request 以 `.test.js` 结尾。
  3. require.context('../', true, /\.stories\.js$/);
  4. // (创建出)一个 context,其中所有文件都来自父文件夹及其所有子级文件夹,request 以 `.stories.js` 结尾。
复制代码
api


函数有三个属性:resolve, keys, id。
resolve 是一个函数,它返回 request 被剖析后得到的模块 id。
  1. let p = require.context("...",true,"xxx")
  2. p.resolve("一个路径")
复制代码
keys 也是一个函数,它返回一个数组,由所有可能被此 context module 处理的哀求(译者注:参考下面第二段代码中的 key)组成。
require.context的返回值是一个函数,我们可以在函数中传入文件的路径,就可以得到模块化的组件了
  1. let components = require.context('../pages', true, /\.js$/, 'sync')
  2. let paths = components.keys()//获得了所有引入文件的地址
  3. // console.log(paths)
  4. let routes = paths.map(path => {
  5.     let component = components(path).default
  6.     path = path.substr(1).replace(/\/\w+\.js$/,"")
  7.     return {
  8.         path,
  9.         component
  10.     }
  11. })
  12. console.log(routes)
复制代码
总结


虽然上面有很多api和返回的值,我们只拿两个来做说明
keys方法,这个可以获取所有模块的路径,返回的是一个数组
  1. let context = require.context("../pages", true, /\.js$/);
  2. let paths = context.keys()//获取了所有文件的路径
复制代码
获取路径下所有的模块
  1. let context = require.context("../pages", true, /\.js$/);
  2. let paths = context.keys()//获取了所有文件的路径let routes = paths.map(path => {    //批量获取引入的组件    let component = context(path).default;    console.log(component)    })
复制代码
把握这两个就可以了,下面我们来继续处理


7.扁平数据转换为树形布局的(convertTree算法)


这个算法的名字是我自己起的,起首我们要明确为甚么必要将数据转换成tree
我们的预期的routes数据应该是下面如许的
  1. //目的是什么?
  2. //生成一个路由配置
  3. const routes = [
  4.      {
  5.          path: "",
  6.          component:xxx
  7.           children:[
  8.                  {
  9.                      path:"xxx"
  10.                      component:xxx
  11.                  }
  12.             ]
  13.      }
  14. ]
复制代码
但着实我们使用require.context处理之后的数据是如许的

可以看到这个数据是完全扁平化的,没有任何的嵌套,以是我们第一步就是要实现将这种扁平化的数据转换为符合我们预期的树形布局,下面我们一步一步来


7.1使用require.context将数据处理成扁平化


起主要处理成上图那样的布局,代码都有解释,难度也不高
  1. //require.context()// 1. 一个要搜索的目次,// 2. 一个标志表现是否还要搜索其子目次, // 3. 一个匹配文件的正则表达式。let context = require.context("../pages", true, /\.js$/);
  2. let paths = context.keys()//获取了所有文件的路径let routes = paths.map(path => {    //批量获取引入的组件    let component = context(path).default;    //组件扩展属性方便渲染菜单    let meta = component['meta'] || {}    //console.log(path)    //这个正则的目的    //由于地址是./Discover/Djradio/index.js这种类型的并不能直接使用,以是要进行处理    //1.接去掉最前的"." 得到的结果是/Discover/Djradio/index.js    //2.处理了还是不能直接用 由于我们的预期/Discover/Djradio,以是通过正则将index.js干掉了    //3.有可能后面的路径不是文件夹 得到的结果是/Discover/abc.js,后缀名并不能用到路由配置的path属性中,以是.js后缀名又用正则替换掉    path = path.substr(1).replace(/(\/index\.js|\.js)$/, "")    // console.log(path)    return {        path,        component,        meta    }})
复制代码
7.2 实现convertTree算法


上面处理好了数据后,我们封装一个方法,专门用于处理扁平化数据变成树形数据,算法时间复杂度为O(n^2)
  1. function convertTree(routes) {
  2.     let treeArr = [];
  3.     //1.处理数据 将每条数据的id和parent处理好 (俗称 爸爸去哪儿了)
  4.     routes.forEach(route => {
  5.         let comparePaths = route.path.substr(1).split("/")
  6.         // console.log(comparePaths)
  7.         if (comparePaths.length === 1) {
  8.             //说明是根节点,根节点不需要添加parent_id
  9.             route.id = comparePaths.join("")
  10.         } else {
  11.             //说明具有父节点
  12.             //先处理自己的id
  13.             route.id = comparePaths.join("");
  14.             //comparePaths除去最后一项就是parent_id
  15.             comparePaths.pop()
  16.             route.parent_id = comparePaths.join("")
  17.         }
  18.     })
  19.     //2.所有的数据都已经找到了父节点的id,下面才是真正的找父节点了
  20.     routes.forEach(route => {
  21.         //判断当前的route有没有parent_id
  22.         if (route.parent_id) {
  23.             //有父节点
  24.             //id===parent_id的那个route就是当前route的父节点
  25.             let target = routes.find(v => v.id === route.parent_id);
  26.             //判断父节点有没有children这个属性
  27.             if (!target.children) {
  28.                 target.children = []
  29.             }
  30.             target.children.push(route)
  31.         } else {
  32.             treeArr.push(route)
  33.         }
  34.     })
  35.     return treeArr
  36. }
复制代码
通过上述处理之后就可以得到树形布局啦

接下来我们只必要把数据导出去,在app上引入通报给CompileRouter组件就可以了


7.3 以后要留意的


以后只必要在pages中创建文件即可自动实现路由的处理以及编译了,不过对于嵌套级别的路由咱们别忘了要在路由组件加上CompileRouter组件,总结为亮点
       
  • 创建路由页面   
  • 嵌套路由的父级路由组件中加入

8.扩展静态属性


我们当前创建出来的结果是有了,但是如果我们用于渲染菜单的时候就会有问题,没有内容可以用于渲染菜单,以是我们可以给组件上扩展静态属性meta(也可以是别的),然后对我们的自动化编译代码做一些小小的改动就行了


组件



自动化处理逻辑完备代码

  1. //require.context()// 1. 一个要搜索的目次,// 2. 一个标志表现是否还要搜索其子目次, // 3. 一个匹配文件的正则表达式。let context = require.context("../pages", true, /\.js$/);
  2. let paths = context.keys()//获取了所有文件的路径let routes = paths.map(path => {    //批量获取引入的组件    let component = context(path).default;    //组件扩展属性方便渲染菜单    let meta = component['meta'] || {}    //console.log(path)    //这个正则的目的    //由于地址是./Discover/Djradio/index.js这种类型的并不能直接使用,以是要进行处理    //1.接去掉最前的"." 得到的结果是/Discover/Djradio/index.js    //2.处理了还是不能直接用 由于我们的预期/Discover/Djradio,以是通过正则将index.js干掉了    //3.有可能后面的路径不是文件夹 得到的结果是/Discover/abc.js,后缀名并不能用到路由配置的path属性中,以是.js后缀名又用正则替换掉    path = path.substr(1).replace(/(\/index\.js|\.js)$/, "")    // console.log(path)    return {        path,        component,        meta    }})//这种数据是扁平化的数据,并不符合我们的路由规则//必要做算法 尽可能将时间复杂度降低o(n)最好//封装一个convertTree算法 时间复杂度o(n^2)// console.log(routes)//id//parent_idfunction convertTree(routes) {
  3.     let treeArr = [];
  4.     //1.处理数据 将每条数据的id和parent处理好 (俗称 爸爸去哪儿了)
  5.     routes.forEach(route => {
  6.         let comparePaths = route.path.substr(1).split("/")
  7.         // console.log(comparePaths)
  8.         if (comparePaths.length === 1) {
  9.             //说明是根节点,根节点不需要添加parent_id
  10.             route.id = comparePaths.join("")
  11.         } else {
  12.             //说明具有父节点
  13.             //先处理自己的id
  14.             route.id = comparePaths.join("");
  15.             //comparePaths除去最后一项就是parent_id
  16.             comparePaths.pop()
  17.             route.parent_id = comparePaths.join("")
  18.         }
  19.     })
  20.     //2.所有的数据都已经找到了父节点的id,下面才是真正的找父节点了
  21.     routes.forEach(route => {
  22.         //判断当前的route有没有parent_id
  23.         if (route.parent_id) {
  24.             //有父节点
  25.             //id===parent_id的那个route就是当前route的父节点
  26.             let target = routes.find(v => v.id === route.parent_id);
  27.             //判断父节点有没有children这个属性
  28.             if (!target.children) {
  29.                 target.children = []
  30.             }
  31.             target.children.push(route)
  32.         } else {
  33.             treeArr.push(route)
  34.         }
  35.     })
  36.     return treeArr
  37. }export default convertTree(routes)//获取一个模块// console.log(p("./Discover/index.js").default)//目的是什么?//生成一个路由配置// const routes = [//     {//         path: "",//         component,//          children:[//                 {path component}//             ]//     }// ]
复制代码
写在末了


着实上述的处理并不能作为应用级别用于项目中,主要在于CompileRouter处理的不敷过细,下一期我将专门写一篇怎样处理CompileRouter用于鉴权等应用在项目中

到此这篇关于react自动化构建路由的实现的文章就先容到这了,更多相关react自动化构建路由内容请搜索草根技术分享以前的文章或继续浏览下面的相关文章希望大家以后多多支持草根技术分享!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作