久久精品国产亚洲高清|精品日韩中文乱码在线|亚洲va中文字幕无码久|伊人久久综合狼伊人久久|亚洲不卡av不卡一区二区|精品久久久久久久蜜臀AV|国产精品19久久久久久不卡|国产男女猛烈视频在线观看麻豆

    1. <style id="76ofp"></style>

      <style id="76ofp"></style>
      <rt id="76ofp"></rt>
      <form id="76ofp"><optgroup id="76ofp"></optgroup></form>
      1. 千鋒教育-做有情懷、有良心、有品質的職業(yè)教育機構

        手機站
        千鋒教育

        千鋒學習站 | 隨時隨地免費學

        千鋒教育

        掃一掃進入千鋒手機站

        領取全套視頻
        千鋒教育

        關注千鋒學習站小程序
        隨時隨地免費學習課程

        當前位置:首頁  >  技術干貨  > React自動化路由

        React自動化路由

        來源:千鋒教育
        發(fā)布人:wjy
        時間: 2022-06-01 17:18:00 1654075080

        在使用`react-router-dom`在編寫項目的時候有種感覺就是,使用起來非常的方便,但是若是維護起來,那便是比較麻煩了,因為各大路由分散在各個組件中. 所以我們就會想到,使用`react-router-dom`中提供的`config`模式來編寫我們的路由,這樣寫的好處就是我們可以將邏輯集中在一處,配置路由比較方便

        React自動化路由

        ## **項目地址**

        [https://gitee.com/d718781500/autoRouter](https://link.zhihu.com/?target=https%3A//gitee.com/d718781500/autoRouter)

        ## **1.路由集中式**

        我們先將下列數(shù)據(jù)定義在`/src/router/index.js`中

        在react的路由官方文檔中就提供了配置集中式路由的案例,大致是這樣的仿照`vue`的路由,生成一個配置文件,預期是這樣的

        ```js
        //需要一個路由的配置,它是一個數(shù)組
        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 routes
        ```

        我們可以通過上述配置,來生成一個路由.當然上述的配置也只是做了簡單的處理,還有`redirect` `exact`等屬性沒有寫,我們還是從一個簡單的開始吧

        ## **2.文件目錄**

        上述的配置中使用了類似于vue的集中式路由配置模式,那么下面就展示下我當前這個demo的結構目錄吧

        ### **項目目錄結構**

        ### **src/pages目錄結構**

        ```js
        ├─Discover
        │  │  abc.js
        │  │  index.js
        │  │
        │  ├─Djradio
        │  │  │  index.js
        │  │  │  lf.js
        │  │  │
        │  │  └─gv
        │  │          index.js
        │  │
        │  ├─Playlist
        │  │      index.js
        │  │
        │  └─Toplist
        │          index.js

        ├─Entertaiment
        │      index.js

        ├─Friends
        │      index.js
        │      xb.js

        ├─Mine
        │      index.js

        └─Page404
                index.js
        ```

        有了這些結構之后,那么在`1`中提到的引入文件結合起來看就不懵逼啦,接下來我們可以封裝一個組件,給他取個名字叫做`CompileRouter`這個組件專門用于編譯路由

        ## **3.創(chuàng)建CompileRouter**

        這個組件我們把它創(chuàng)建在`src/utils`中,作用就是通過傳入的路由配置,然后計算出這個組件,那么問題來了,為什么要創(chuàng)建這個組件呢?

        讓我們回顧一下react路由的編寫方式吧,react路由需要一個基礎組件`HashRouter`或者`BrowserRouter`這兩個相當于一個基石組件

        然后還需要一個路由配方這個組件可以接受一個`path`映射一個`component`

        我們來寫段偽代碼來說明一下

        ```js
        //引入路由基本組件(要在項目中安裝 npm i react-router-dom)
        import {HashRouter as Router,Route} from "react-router-dom"
        class Demo extends React.Component {
            render(){
                //基石路由
                <Router>
                    //路由配方組件 通過path匹配component
                    <Route path="/" component={Home}/>
                     <Route path="/mine" component={Mine}/>
                </Router>
            }
        }
        ```

        這是基本用法,所以我們CompileRouter這個組件的工作就是,生成如上代碼中的Route一樣,生成Route然后展示在組件上

        在了解到Compile的基本作用之后,下面我們就開始編碼吧

        我個`CompileRouter`設計是接受一個數(shù)據(jù),這個數(shù)據(jù)必須是符合路由配置的一個數(shù)組,就像`1`里代碼中所示的數(shù)組一樣,接受的屬性為`routes`

        ```js
        //這個文件通過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;//獲取routes路由配置
                //1.通過routes生成Route組件
                //確保routes是一個數(shù)組
                // console.log(routes)
                //render 不會重復讓組件的componentDidMount和componentWillUnmount重復調用
                if (Array.isArray(routes) && routes.length > 0) {
                    //確保傳入的routes是個數(shù)組
                   // 循環(huán)迭代傳入的routes
                    let finalRoutes = routes.map(route => {
                        //每個route是這個樣子的 {path:"xxx",component:"xxx"}
                        //如果route有子節(jié)點 {path:"xxx",component:"xxx",children:[{path:"xxx"}]}
                        return <Route path={route.path} key={route.path} render={
                               // 這么寫的作用就是,如果路由還有嵌套路由,那么我們可以把route中的children中的配置數(shù)據(jù)傳遞給這個組件,讓組件再次調用CompileRouter的時候就能編譯出嵌套路由了
                            () => <route.component routes={route.children} />
                        } />
                    })

                    this.setState({
                        c: finalRoutes
                    })
                } else {
                    throw new Error('routes必須是一個數(shù)組,并且長度要大于0')
                }
            }
            componentDidMount() {
                //確保首次調用renderRoute計算出Route組件
                this.renderRoute()
            }
            render() {
                let { c } = this.state;
                return (
                    <Switch>
                        {c}
                    </Switch>
                )
            }
        }
        ```

        上述代碼就是用于去處理`routes`數(shù)據(jù)并且聲稱這樣的組件,每一步的作用我都已經(jīng)在上面用注釋標明了

         

        ## **4.使用CompileRouter**

        其實我們可以把封裝的這個組件當成是`vue-router`中的視圖組件`<router-view/>`就暫且先這么認為吧,接下來我們需要在頁面上渲染`1級路由了`

        在`src/app.js`

        ```js
        import React from 'react'
        import { HashRouter as Router, Link } from 'react-router-dom'
        //引入我們封裝的CompileRouter罪案
        import CompileRouter from "./utils/compileRouter"
        //引入在1中定義的路由配置數(shù)據(jù)
        import routes from "./router"
        console.log(routes)
        class App extends React.Component {
            render() {
                return (
                    <Router>
                        <Link to="/friends">朋友</Link>
                        |
                        <Link to="/discover">發(fā)現(xiàn)</Link>
                        |
                        <Link to="/mine">我的</Link>
                        {/*當成是vue-router的視圖組件 我們需要將路由配置數(shù)據(jù)傳入*/}
                        <CompileRouter routes={routes} />
                    </Router>
                )
            }
        }

        export default App
        ```

        寫完后,那么頁面上其實就可以完美的展示1級路由了

        ## **5.嵌套路由處理**

        上面我們已經(jīng)對1級路由進行了渲染,可以跳轉,但是二級路由怎么處理呢?其實也很簡單,我們只需要找到二級路由的父路由,繼續(xù)使用`CompileRouter`就可以了

        我們從配置中可以看到,`Discover`這個路由是具有嵌套路由的,所以我們就以`Discover`路由為例子,首先我們看下結構圖

        ![img](https://pic4.zhimg.com/80/v2-99aebf28aca62fa66c48587f6897068b_720w.jpg)

        圖上的`index.js`就是`Discover`這個視圖組件了,也是嵌套路由的`父級路由`,所以我們只需要在這個`index.js`中繼續(xù)使用`CompileRouter`就可以了

        ```js
        import React from 'react'
        import { Link } from "react-router-dom"

        import CompileRouter from "../../utils/compileRouter"
        function Discover(props) {

            let { routes } = props //這個數(shù)據(jù)是從ComileRouter組件編譯的時候傳遞過來的children
            // console.log(routes)
            let links = routes.map(route => {
                return (
                    <li key={route.path}>
                        <Link to={route.path}>{route.path}</Link>
                    </li>
                )
            })
            return (
                <fieldset>
                    <legend>發(fā)現(xiàn)</legend>
                    <h1>我發(fā)現(xiàn),不能說多喝熱水</h1>
                    <ul>
                        {links}
                    </ul>
                    {/*核心代碼,再次使用即可 這里將通過children數(shù)據(jù)可以渲染出Route*/}
                    <CompileRouter routes={routes} />
                </fieldset>
            )
        }
        Discover.meta = {
            title: "發(fā)現(xiàn)",
            icon: ""
        }
        export default Discover
        ```

        所以我們以后記住,只要是有嵌套路由我們要做兩件事

        1. 配置routes
        2. 在嵌套路由的父級路由中再次使用`CompileRouter`,并且傳入`routes`即可

        ## **6.** **require.context**

        上面我們實現(xiàn)了一個路由集中式的配置,但是我們會發(fā)現(xiàn)一個問題

        ![img](https://pic3.zhimg.com/80/v2-93403806cb935bd4dbeb50a6e770f66e_720w.jpg)

        引入了很多的組件,實際上,在項目中引入的更多,如果一個一個引入,對我們來說是災難性的,所以我們可以使用`webpack`提供的一個很好用的api,`require.context`我們先說說它是怎么使用的吧

        自動化導入`require.context`方法,使用這個方法可以減少繁瑣的組件引入,而且可以深度的遞歸目錄,做到import做不到的事情 下面我們來看一下這個方法是如何使用的

        ### **使用**

        你可以通過 `require.context()` 函數(shù)來創(chuàng)建自己的 context。

        可以給這個函數(shù)傳入4個參數(shù):

        1. 一個要搜索的目錄,
        2. 一個標記表示是否還要搜索其子目錄,
        3. 一個匹配文件的正則表達式。
        4. mode 模塊加載模式,常用值為 sync、lazy、lazy-once、eager

        - `sync` 直接打包到當前文件,同步加載并執(zhí)行
          `lazy` 延遲加載會分離出單獨的 chunk 文件
          `lazy-once` 延遲加載會分離出單獨的 chunk 文件,加載過下次再加載直接讀取內(nèi)存里的代碼。
          `eager` 不會分離出單獨的 chunk 文件,但是會返回 promise,只有調用了 promise 才會執(zhí)行代碼,可以理解為先加載了代碼,但是我們可以控制延遲執(zhí)行這部分代碼。

        webpack 會在構建中解析代碼中的 `require.context()` 。

        語法如下:

        ```js
        require.context(
          directory,
          (useSubdirectories = true),
          (regExp = /^\.\/.*$/),
          (mode = 'sync')
        );
        ```

        示例:

        ```js
        require.context('./test', false, /\.test\.js$/);
        //(創(chuàng)建出)一個 context,其中文件來自 test 目錄,request 以 `.test.js` 結尾。
        require.context('../', true, /\.stories\.js$/);
        // (創(chuàng)建出)一個 context,其中所有文件都來自父文件夾及其所有子級文件夾,request 以 `.stories.js` 結尾。
        ```

        ### **api**

        函數(shù)有三個屬性:`resolve`, `keys`, `id`。

        - `resolve` 是一個函數(shù),它返回 request 被解析后得到的模塊 id。
        - let p = require.context("...",true,"xxx")
          p.resolve("一個路徑")
        - `keys` 也是一個函數(shù),它返回一個數(shù)組,由所有可能被此 context module 處理的請求(譯者注:參考下面第二段代碼中的 key)組成。

        `require.context`的返回值是一個函數(shù),我們可以在函數(shù)中傳入文件的路徑,就可以得到模塊化的組件了

        ```js
        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)
        ```

        ### **總結**

        雖然上面有很多api和返回的值,我們只拿兩個來做說明

        1. keys方法,這個可以獲取所有模塊的路徑,返回的是一個數(shù)組
           let context = require.context("../pages", true, /\.js$/);

           let paths = context.keys()*//獲取了所有文件的路徑*

        2. 獲取路徑下所有的模塊
           let context = require.context("../pages", true, /\.js$/);

           let paths = context.keys()*//獲取了所有文件的路徑*

           let routes = paths.map(path => {
           *//批量獲取引入的組件*
           let component = context(path).default;
           console.log(component)
           })

        掌握這兩個就可以了,下面我們來繼續(xù)處理

        ## **7.扁平數(shù)據(jù)轉換為樹形結構的(convertTree算法)**

        這個算法的名字是我自己起的,首先我們要明白為甚么需要將數(shù)據(jù)轉換成tree

        我們的預期的`routes`數(shù)據(jù)應該是下面這樣的

        ```js
        //目的是什么?
        //生成一個路由配置
         const routes = [
             {
                 path: "",
                 component:xxx
                  children:[
                         {
                             path:"xxx"
                             component:xxx
                         }
                    ]
             }
         ]
        ```

        但其實我們使用`require.context`處理之后的數(shù)據(jù)是這樣的

        ![img](https://pic1.zhimg.com/80/v2-7562f0762bc6f1e97c9c7b7e128ef618_720w.jpg)

        可以看到這個數(shù)據(jù)是完全`扁平化`的,沒有任何的嵌套,所以我們第一步就是要實現(xiàn)將這種扁平化的數(shù)據(jù)轉換為符合我們預期的`樹形`結構,下面我們一步一步來

        ### **7.1使用require.context將數(shù)據(jù)處理成扁平化**

        首先要處理成上圖那樣的結構,代碼都有注釋,難度也不高

        ```js
        //require.context()

        // 1. 一個要搜索的目錄,
        // 2. 一個標記表示是否還要搜索其子目錄,
        // 3. 一個匹配文件的正則表達式。
        let context = require.context("../pages", true, /\.js$/);

        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** **實現(xiàn)convertTree算法**

        上面處理好了數(shù)據(jù)后,我們封裝一個方法,專門用于處理扁平化數(shù)據(jù)變成樹形數(shù)據(jù),算法`時間復雜度為O(n^2)`

        ```js
        function convertTree(routes) {
            let treeArr = [];
            //1.處理數(shù)據(jù) 將每條數(shù)據(jù)的id和parent處理好 (俗稱 爸爸去哪兒了)
            routes.forEach(route => {
                let comparePaths = route.path.substr(1).split("/")
                // console.log(comparePaths)
                if (comparePaths.length === 1) {
                    //說明是根節(jié)點,根節(jié)點不需要添加parent_id
                    route.id = comparePaths.join("")
                } else {
                    //說明具有父節(jié)點
                    //先處理自己的id
                    route.id = comparePaths.join("");
                    //comparePaths除去最后一項就是parent_id
                    comparePaths.pop()
                    route.parent_id = comparePaths.join("")
                }
            })
            //2.所有的數(shù)據(jù)都已經(jīng)找到了父節(jié)點的id,下面才是真正的找父節(jié)點了
            routes.forEach(route => {
                //判斷當前的route有沒有parent_id
                if (route.parent_id) {
                    //有父節(jié)點
                    //id===parent_id的那個route就是當前route的父節(jié)點
                    let target = routes.find(v => v.id === route.parent_id);
                    //判斷父節(jié)點有沒有children這個屬性
                    if (!target.children) {
                        target.children = []
                    }
                    target.children.push(route)
                } else {
                    treeArr.push(route)
                }
            })

            return treeArr
        }
        ```

        通過上述處理之后就可以得到樹形結構啦

        ![img](https://pic3.zhimg.com/80/v2-74337231c1b570e8532025dd75a56aaa_720w.jpg)

        接下來我們只需要把數(shù)據(jù)導出去,在app上引入傳遞給`CompileRouter`組件就可以了

        ### **7.3** **以后要注意的**

        以后只需要在pages中創(chuàng)建文件即可自動實現(xiàn)路由的處理以及編譯了,不過對于嵌套級別的路由咱們別忘了要在路由組件加上CompileRouter組件,總結為亮點

        1. 創(chuàng)建路由頁面
        2. 嵌套路由的父級路由組件中加入

        ## **8.擴展靜態(tài)屬性**

        我們當前創(chuàng)建出來的效果是有了,但是如果我們用于渲染`菜單`的時候就會有問題,沒有內(nèi)容可以用于渲染菜單,所以我們可以給組件上擴展`靜態(tài)屬性meta(也可以是別的)`,然后對我們的自動化編譯代碼做一些小小的改動就行了

        ### **組件**

        ### **自動化處理邏輯完整代碼**

        ```js
        //require.context()

        // 1. 一個要搜索的目錄,
        // 2. 一個標記表示是否還要搜索其子目錄,
        // 3. 一個匹配文件的正則表達式。
        let context = require.context("../pages", true, /\.js$/);

        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
            }
        })
        //這種數(shù)據(jù)是扁平化的數(shù)據(jù),并不符合我們的路由規(guī)則
        //需要做算法 盡可能將時間復雜度降低o(n)最好
        //封裝一個convertTree算法 時間復雜度o(n^2)
        // console.log(routes)

        //id
        //parent_id

        function convertTree(routes) {
            let treeArr = [];
            //1.處理數(shù)據(jù) 將每條數(shù)據(jù)的id和parent處理好 (俗稱 爸爸去哪兒了)
            routes.forEach(route => {
                let comparePaths = route.path.substr(1).split("/")
                // console.log(comparePaths)
                if (comparePaths.length === 1) {
                    //說明是根節(jié)點,根節(jié)點不需要添加parent_id
                    route.id = comparePaths.join("")
                } else {
                    //說明具有父節(jié)點
                    //先處理自己的id
                    route.id = comparePaths.join("");
                    //comparePaths除去最后一項就是parent_id
                    comparePaths.pop()
                    route.parent_id = comparePaths.join("")
                }
            })
            //2.所有的數(shù)據(jù)都已經(jīng)找到了父節(jié)點的id,下面才是真正的找父節(jié)點了
            routes.forEach(route => {
                //判斷當前的route有沒有parent_id
                if (route.parent_id) {
                    //有父節(jié)點
                    //id===parent_id的那個route就是當前route的父節(jié)點
                    let target = routes.find(v => v.id === route.parent_id);
                    //判斷父節(jié)點有沒有children這個屬性
                    if (!target.children) {
                        target.children = []
                    }
                    target.children.push(route)
                } else {
                    treeArr.push(route)
                }
            })

            return treeArr
        }

        export default convertTree(routes)


        //獲取一個模塊
        // console.log(p("./Discover/index.js").default)

        //目的是什么?
        //生成一個路由配置
        // const routes = [
        //     {
        //         path: "",
        //         component,
        //          children:[
        //                 {path component}
        //             ]
        //     }
        // ]
        ```

        ## **寫在最后**

        其實上述的處理并不能作為`應用級別`用于項目中,主要在于`CompileRouter`處理的不夠細致,下一期我將專門寫一篇如何處理`CompileRouter`用于`鑒權`等應用在項目中

        **-** **End** **-**

        更多關于“html5培訓”的問題,歡迎咨詢千鋒教育在線名師。千鋒已有十余年的培訓經(jīng)驗,課程大綱更科學更專業(yè),有針對零基礎的就業(yè)班,有針對想提升技術的提升班,高品質課程助理你實現(xiàn)夢想。

        tags:
        聲明:本站稿件版權均屬千鋒教育所有,未經(jīng)許可不得擅自轉載。
        10年以上業(yè)內(nèi)強師集結,手把手帶你蛻變精英
        請您保持通訊暢通,專屬學習老師24小時內(nèi)將與您1V1溝通
        免費領取
        今日已有369人領取成功
        劉同學 138****2860 剛剛成功領取
        王同學 131****2015 剛剛成功領取
        張同學 133****4652 剛剛成功領取
        李同學 135****8607 剛剛成功領取
        楊同學 132****5667 剛剛成功領取
        岳同學 134****6652 剛剛成功領取
        梁同學 157****2950 剛剛成功領取
        劉同學 189****1015 剛剛成功領取
        張同學 155****4678 剛剛成功領取
        鄒同學 139****2907 剛剛成功領取
        董同學 138****2867 剛剛成功領取
        周同學 136****3602 剛剛成功領取
        相關推薦HOT
        桓仁| 绿春县| 阜新| 来凤县| 彩票| 肃南| 克山县| 启东市| 龙江县| 东乌珠穆沁旗| 凤山县| 莱西市| 玛纳斯县| 兰州市| 阿拉善右旗| 临泽县| 建阳市| 浦北县| 榆中县| 石林| 临猗县| 仁寿县| 柯坪县| 徐州市| 原平市| 井陉县| 赞皇县| 沧源| 保靖县| 九台市| 会泽县| 宁明县| 祁门县| 巫山县| 泊头市| 巨鹿县| 营口市| 会同县| 东明县| 平潭县| 瓮安县|