V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
wuchangming89
V2EX  ›  JavaScript

分享:使用 webpack 的 require.context 实现路由“去中心化”管理

  •  
  •   wuchangming89 · 2017-05-05 16:29:10 +08:00 · 11424 次点击
    这是一个创建于 2793 天前的主题,其中的信息可能已经有所发展或是发生改变。

    阅读须知:示例代码以 react-router V3 为例。其他路由同样适用,如:vue-router。同样其他的功能模块也可以用该思路进行相应的去中心化管理改造。
    本文示例代码

    一个项目中路由的变化过程

    当你在开发一个大型单页面应用的时候,项目之初一般做法是所有的路由规则都维护在一个route.js的文件里。

    // rootRoute.js
    const rootRoute = {
        childRoutes: [
            {
                path: '/',
                component: AppLayout,
                childRoutes: [
                    {
                        path: 'shop', // 购买详情页
                        component: Shop
                    },
                    {
                        path: 'order', // 订单页
                        component: Order
                    }
                    // ...
                    // 少量其他路由
                    // ...
                ]
            }
        ]
    };
    

    随着业务代码的增长路由很快会变成:

    // rootRoute.js
    const rootRoute = {
        childRoutes: [
            {
                path: '/',
                component: AppLayout,
                childRoutes: [
                    {
                        path: 'shop', // 购买详情页
                        component: ShopLayout,
                        childRoutes: [
                            {
                                path: 'foodDetail',
                                component: FoodDetail
                            },
                            {
                                path: 'shoesDetail',
                                component: ShoesDetail
                            }
                            // 其他
                        ]
                    },
                    {
                        path: 'order', // 订单页
                        component: Order,
                        childRoutes: [
                            {
                                path: 'remark', //订单备注
                                component: Remark
                            },
                            {
                                path: 'invoice', //发票抬头
                                component: Invoice
                            },
                            {
                                path: 'payment', //付款页面
                                component: Payment
                            },
                            {
                                path: 'userValidation', //用户验证
                                component: UserValidation
                            },
                            {
                                path: 'chooseAddress', //选择地址
                                component: ChooseAddress,
                                childRoutes: [
                                    {
                                        path: 'addAddress', //添加地址
                                        component: AddAddress,
                                        childRoutes: [
                                            {
                                                path: 'searchAddress', //搜索地址
                                                component: SearchAddress
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                    // ...
                    // 大量新增路由
                    // ...
                ]
            }
        ]
    };
    

    当路由变的越来越大,大到已经难以维护时。我们按照react-router 提供的思路,对路由按业务模块进行拆分。

    // rootRoute.js
    const rootRoute = {
        childRoutes: [
            {
                path: '/',
                component: AppLayout,
                childRoutes: [
                    require('./modules/shop/route'), //购买详情页
                    require('./modules/order/route'), // 订单页
                    require('./modules/login/route'), // 登录注册页
                    require('./modules/service/route'), // 服务中心
                    // ...
                    // 其他大量新增路由
                    // ...
                ]
            }
        ]
    };
    

    按该方式拆分后,每个业务模块维护自身的路由配置。新增业务模块路由,只需要在总的 rootRoute 中引入该业务模块的路由即可(也就是加一行代码)。这个方案看来是已经接近完美了。但如果想达到连一行代码都不用加?实现彻彻底底的去中心化管理

    require.context 是什么?

    想要彻彻底底的实现去中心化管理我们需要使用到 require.context

    webpack 官方文档的介绍require.context

    简单说就是:有了 require.context,我们可以通过正则匹配引入相应的文件模块。

    require.context(directory, useSubdirectories, regExp)
    

    require.context 有三个参数:

    • directory:说明需要检索的目录
    • useSubdirectories:是否检索子目录
    • regExp: 匹配文件的正则表达式

    使用 require.context 改造后的 rootRoute.js 文件

    const rootRoute = {
        childRoutes: [
            {
                path: '/',
                component: AppLayout,
                childRoutes: (r => {
                    return r.keys().map(key => r(key));
                })(require.context('./', true, /^\.\/modules\/((?!\/)[\s\S])+\/route\.js$/))
            }
        ]
    };
    

    优化后,新增一个业务模块,只要业务模块 route 文件遵循统一的目录结构。业务模块 route 就能被自动关联到 rootRoute 里。
    查看示例代码

    其他应用场景

    这个思路可应用于其他想要实现"去中心化"管理的功能模块。

    • 模块化管理 reducer

    欢迎转载,转载请注明出处: https://github.com/wuchangming/blog/blob/master/docs/webpack/require-context-usage.md


    6 条回复    2018-02-01 22:18:09 +08:00
    AdamChrist
        1
    AdamChrist  
       2017-05-05 17:15:57 +08:00
    不错..感谢分享.
    wuchangming89
        2
    wuchangming89  
    OP
       2017-05-05 17:43:29 +08:00
    @AdamChrist,THX
    kimown
        3
    kimown  
       2017-05-06 08:56:58 +08:00 via Android
    这个好用 感谢分享
    otarim
        4
    otarim  
       2017-05-30 18:17:39 +08:00
    不错的思路,之前使用的是 loader 的方式
    zhjie
        5
    zhjie  
       2017-11-20 10:30:42 +08:00
    这路由就很灵性了
    bonashen
        6
    bonashen  
       2018-02-01 22:18:09 +08:00
    确实不错,通常我是在 redux 中使用。
    ```js
    const context = require.context('./', false, /\.js$/);
    const keys = context.keys().filter(item => item !== './index.js' && !item.endsWith('.test.js'));
    const regexp = /\.\/(\S+)\.js$/;

    const reducers = keys.reduce((reducers, key) => {
    const match = regexp.exec(key);
    const result = reducers;
    let reducer = context(key).default;
    reducer = isFunction(reducer) ? reducer : combineReducers({ ...reducer });
    result[match[1]] = reducer;
    return result;
    }, {});

    export default reducers;

    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3210 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 10:48 · PVG 18:48 · LAX 02:48 · JFK 05:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.