chrome js 获取文件目录_仿nuxt.js,自动构建路由,释放你的双手?!

本文介绍如何仿照nuxt.js,通过Chrome JS获取文件目录来自动生成Vue项目的路由。详细解析了require.context的用法,nuxt.js的路由规则,并逐步展示了从初始化文件目录到动态、嵌套路由的构建过程,包括各个关键方法的实现,如getRouteItemName、getRouteItemPath等。文章末尾提供了完整项目代码的GitHub链接。
摘要由CSDN通过智能技术生成

前言

写过nuxt.js的同学,肯定也对nuxt的路由规则有一定的了解,在pages目录下创建文件,即可以自动构建路由,本文来带领大家实现一下在vue里怎么去自动构建路由。我这里使用的是Vue-cli 3.X版本进行初始化。cli 2.X版本也是一样只不过初始化出来的文件目录不一。

做前准备

了解require.context:

它是webpack里一个重要的API,它可以通过一个目录进行搜索,useSubdirectories指定是否搜索子目录,以及与文件匹配的正则表达式。很好理解,它可以对指定目录进行搜索,所以我们可以用它来自动构建路由,也可以用它来构建组建。
  • directory:说明需要检索的目录
  • useSubdirectories:是否检索子目录
  • regExp: 匹配文件的正则表达式

了解nuxt.js路由规则

这里就简单讲一下,不做详诉。想要深入了解:nuxt.js官网地址:nuxtjs.org
  • 基础路由:文件为index.vue,则path在其对应的路由为空字符。即:有pages/user/index.vue文件,则路由path为:'/user'。 而路由的name由文件目录组成,多级以-拼接,如:pages/user/index.vue文件,name为:user-index
  • 嵌套路由:如果想要使用嵌套路由,则在根目录下需要创建同名的文件夹和.vue文件。如跟层级下有pages/user/_id.vue与pages/user.vue。就构成嵌套路由。
  • 动态路由:文件以下划线_开头即为动态路由,如有文件pages/user/_id.vue,那么路由path为'user/:id'

开始

一:初始文件目录

利用vue create XXX 创建项目,选配置的时候需要勾选router。初始化之后,我们把默认的views文件夹更名为pages(对应nuxt.js的路由目录),在router文件夹下创建routes.js,在routes里初始化routes导出给router/index.js里引入

# router/index/js

import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes' // 引入routes
Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
# router/routes/js

const routes = []
export default routes

此时的文件目录如下:(about.vue、home.vue为cli创建后默认带有的文件)

c35fe0b50079a99812ef72199f47d9ff.png

二:在router/routes.js开始写自动构建的逻辑。

1:使用require.context

根据之前我们说过的三个参数,以及我们的目录结构写出以下代码
# router/routes.js
let files = require.context('../pages', true, /.vue$/) // 根据目录结构去搜索文件
let filesKey = files.keys() // 获取整个目录结构
console.log(files)
console.log(filesKey)

const routes = []
export default routes
我们来看看打印出了什么,files是一个方法,具体什么做用这里先不说,而fileskey我们可以看到打印出来的就是一个文件地址的数组。有了地址便可以根据规则来构建routes

1420f36f41d2a5b6ed50408714fae620.png

2:但上面仅仅只是基础路由,我们创建动态路由、嵌套路由与动态嵌套路由,为了逻辑的区分,我们删除原有的pages里的文件,再新增文件,目录结构如下:

2972cdb9f76f16559acae3a8086644b9.png
# pages/index.vue

<template>
  <div class="index">
    这个是默认的index,路由为:/。基础路由
  </div>
</template>
<script>
export default {
  name: 'index'
}
</script>
# pages/group.vue

<template>
  <div class="group">
    group页面 嵌套路由带有route-view,有children属性
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: 'group'
}
</script>
# pages/group/user.vue

<template>
  <div class="user">
    group/user页面 嵌套路由带有route-view,有children属性
    <router-view></router-view>
  </div>
</template>
<script>
export default {
  name: 'user'
}
</script>
# pages/group/user/_id.vue

<template>
  <div class="id">
    group/user/:id页面,动态路由
    <div>userid为:{{$route.params.id}}</div>
  </div>
</template>
<script>
export default {
  name: 'id'
}
</script>
现在我们再看一下打印出来的fileskey是什么?

fc1078d56d21e5c9db3b37aa0f932f76.png
看到现在的fileskey,我们可能会发现,基础路由与动态路由都很简单,只要按照规则replace就好了。但是嵌套路由就很麻烦。根据我们之前打印出的fileskey,还是会有很多疑问:
  • 由打印出来的数据,怎么区分最后到底有几个路由对象?(路由会有嵌套关系)
  • 我们怎么知道某个路由有没有children?
  • 嵌套的深度从何得知?最大的深度呢?
  • 我们怎么知道某个路由是被另一个路由嵌套的?

3:分析问题,给予解答。(解答只是思路,具体的代码逻辑在后面)

  • 由打印出来的数据,怎么区分最后到底有几个路由对象?(路由会有嵌套关系)
答:我们可以根据一级目录的文件名开始分类,建一个map对象,对象里的key就是一级目录文件的名称,而value就是一个数组,把所有是此目录下的文件全部收集到数组中。(一级.vue文件对象的value数组中就只有自己)
  • 我们怎么知道某个路由有没有children?
答:当某个名称叫name的文件满足能在它所对应的数组中查找出来有:name.vue(有.vue文件), name/XXX(有name文件夹),那么就必定有children属性
  • 嵌套的深度从何得知?最大的深度呢?
map对象里的数组存的是文件的路径,而其中我们可以用正则提出不必要的相对路径与.vue,如获得:group/user/_id。按字符‘/’切割出来数组的长度就是深度了。对数组中的每个对象切割出来的length进行比较,最大的即为最大深度。
  • 我们怎么知道某个路由是被另一个路由嵌套的?
答:循环从1开始到最大深度结束,一层一层的搭建route,而某层的route(如3层)想要知道自己被谁嵌套,就要把自己的的所有父层级获取到(比如当前层级为:./group/user/_id.vue,那么所有的父层级为group、user),去route中一层一层的找出来。

4:创建getRouteItemName方法

/**
 * 获取路由name
 * @param {*} file type:string (文件完整的目录)
 */
const getRouteItemName = (file) => {
  let match = file.match(//(.+?).vue$/)[1] // 去除相对路径与.vue
  let res = match.replace(/_/ig, '').replace(///ig, '-') // 把下划线去除, 改变/为-拼接
  return res
}

5:创建getRouteItemPath方法,这个替换/index.vue又替换index.vue是因为一级下是有‘/’,而嵌套路由传过来的参数就没有‘/’了

/**
 * 获取路由path
 * @param {*} file String (目录,一级路由则为完整目录,多级为自身目录名称)
 */
const getRouteItemPath = (file) => {
  return file.replace('/index.vue', '').replace('index.vue', '').replace('vue', '').replace(/_/g, ':').replace(/./g, '')
}

6:创建hasfile方法。

/**
 * 校验目录下是否有其他文件,注意((?!${name}).)是因为要查询的目录有可能为name/name.vue,而这样可能会导致误判有无children,所以要匹配非name。
 * @param {*} file type:string (当前目录路径)
 * @param {*} name type:string (目录的文件名 默认等于file参数)
 */
const hasfile = (file, name = file) => new RegExp(file + `/((?!${name}).)`)

7:创建hasVue方法。

/**
 * 校验.vue文件
 * @param {*} file type:string (当前目录路径)
 */
const hasVue = (file) => new RegExp(file + '.vue')

8:创建createClassify方法。这个方法以一级.vue文件或者文件夹进行分级,这里分级的意义是把同一文件夹下的所有文件收集到一个数组,方便后续操作。代码如下:

const createClassify = () => {
  let map = filesKey.reduce((map, cur) => {
    let dislodge = cur.match(//(.+?).vue$/)[1] // 只匹配纯文件名的字符串
    let key = dislodge.split('/')[0]; // 拿到一级文件的名称
    (map[key] || (map[key] = [])).push(cur)
    console.log(map) // 打印出 { about: ['./about.vue'], home: ['./home.vue'] }
    return map
  }, {})
}

9:创建getRoutes方法。

/**
 * 构建路由
 * @param {*} map type:Object
 */
const getRoutes = (map) => {
  let res = []
  for (let key in map) { // 遍历对象
    let level = map[key] // 取出对应value
    let text = level.join('@') // 用@把分级数组拼接,这样只是为了方便查找
    let expr1 = hasfile(key) // 校验规则,有无子文件
    let expr2 = hasVue(key) // 校验规则,有无vue文件
    let route = {} // 初始化route
    if (text.match(expr1) && text.match(expr2)) { // 有children的route
      let max = Math.max(...level.map(v => v.match(//(.+?).vue$/)[1].split('/').length)) // 找目录里最深的层级数
      let i = 0 // 标记层级
      while (i++ < max) { // 按层级来搭建route
        level.forEach((item) => {
          let wipeOfVue = item.match(//(.+?).vue$/)[1] // 匹配纯路径,去除相对路径与.vue
          let classArray = wipeOfVue.split('/') // 切割为了方便操作
          let len = classArray.length // 深度
          if (len === i) {
            if (i === 1) { // 如果为第一层,则必带有children
              route = {
                component: files(item).default,
                path: getRouteItemPath(item),
                children: []
              }
            } else {
              let file = item.match(/(.+?).vue$/)[1] // 只匹配目录下.vue之前的路径
              let name = classArray[len - 1] // 获取每个路径下具体的文件名
              let iteration = classArray.slice(0, len - 1) // 截取文件路径
              let childRoute = {
                component: files(item).default,
                path: getRouteItemPath(name)
              }
              // 从文件的目录下搜索有无子文件,有子文件代表有children属性。 否则无,则直接给route增加name属性
              text.match(hasfile(file, name)) && text.match(hasVue(file)) ? childRoute.children = [] : childRoute.name = getRouteItemName(item)

              // 通过截取的目录找到对应的parent
              let parent = iteration.reduce((map, current, index) => {
                let path = index === 0 ? getRouteItemPath(`/${current}.vue`) : getRouteItemPath(`${current}.vue`)
                return map.filter(v => v.path === path)[0].children
              }, [route])
              parent && parent.push(childRoute)
            }
          }
        })
      }
      res.push(route) // 添加route对象
    } else { // 没有children,直接遍历插入
      level.forEach(item => {
        route = {
          component: files(item).default,
          name: getRouteItemName(item),
          path: getRouteItemPath(item)
        }
        res.push(route) // 添加route对象
      })
    }
  }
  return res // 返回整个route对象
}

10:现在来看一下效果。(找个录屏软件真难,不是打广告)

ad07e0ca24ad2e51a2473c4eaa4ccd83.png
https://www.zhihu.com/video/1172200859886161920

三:结尾。以上就是文章的全部了,感觉自己实现的还不够优雅,算是抛砖引用,在此,谢谢你能看完到最后。完整项目代码:https://github.com/18692959234/free-router

18692959234 - Overview​github.com
99c585aa975a49c3972ab602fe7226b6.png
blog​websitejob.xyz
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值