浅谈前端工程化、模块化、组件化
什么是前端工程化、模块化、组件化?
前端工程化
工程化是一种思想而不是某种技术(当然为了实现工程化我们会用一些技术)
再用一句通俗的话来概括前端工程化:前端工程化就是用做工程的思维看待和开发自己的项目,而不再是直接撸起袖子一个页面一个页面开写
前端模块化
前端工程化是一个高层次的思想,而模块化和组件化是为工程化思想下相对较具体的开发方式,因此可以简单的认为模块化和组件化是工程化的表现形式。
模块化开发,一个模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块。
模块化开发的4点好处:
1 避免变量污染,命名冲突
2 提高代码复用率
3 提高维护性
4 依赖关系的管理
那具体什么是模块化呢?还是举简单的例子,我们要写一个实现A功能的JS代码,这个功能在项目其他位置也需要用到,那么我们就可以把这个功能看成一个模块采用一定的方式进行模块化编写,既能实现复用还可以分而治之,同理在写样式的时候,如果我们需要某种特殊的样式,会在很多地方应用,那么我们也可以采用一定的方式进行CSS的模块化,具体说来,JS模块化方案很多有AMD/CommonJS/UMD/ES6 Module等,CSS模块化开发大多是在less、sass、stylus等预处理器的import/mixin特性支持下实现的
总体而言,模块化不难理解,重点是要学习相关的技术并且灵活运用。
前端组件化
组件化也是工程化的表现形式。
①页面上的每个独立的、可视/可交互区域视为一个组件;
②每个组件对应一个工程目录,组件所需的各种资源都在这个目录下就近维护;
③由于组件具有独立性,因此组件与组件之间可以 自由组合;
④页面只不过是组件的容器,负责组合组件形成功能完整的界面;
⑤当不需要某个组件,或者想要替换组件时,可以整个目录删除/替换。
组件化将页面视为一个容器,页面上各个独立部分例如:头部、导航、焦点图、侧边栏、底部等视为独立组件,不同的页面根据内容的需要,去盛放相关组件即可组成完整的页面。
PS:模块化和组件化一个最直接的好处就是复用,同时我们也应该有一个理念,模块化和组件化除了复用之外还有就是分治,我们能够在不影响其他代码的情况下按需修改某一独立的模块或是组件,因此很多地方我们及时没有很强烈的复用需要也可以根据分治需求进行模块化或组件化开发。
模块化规范
commonJS:commonJS由nodeJS发扬光大,这标志着js模块化正式登场。
一 定义模块
根据commonJS规范,一个单独的文件是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量、函数、类,都是私有的,对其他文件不可见,无法被其他模块读取,除非为global对象的属性。
二 模块输出
模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象。
三 加载模块
加载模块用require方法,该方法读取一个文件并且执行,返回文件内部的module.exports对象。
var name = 'Byron';
function printName(){
console.log(name);
}
function printFullName(firstName){
console.log(firstName + name);
}
module.exports = {
printName: printName,
printFullName: printFullName
然后加载模块
var nameModule = require('./myModel.js');
nameModule.printName();
AMD:Asynchronous Module Definition 中文名是一步模块
它是一个在浏览器端模块化开发的规范,由于不是js原生支持,使用AMD规范进行页面开发需要用到对应的函数库,也就是大名鼎鼎的RequireJS,实际上AMD是RequireJS在推广过程中对模块定义的规范化的产出。
requireJS主要解决两个问题:
1 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器。
2 js加载的时候浏览器会停止页面渲染,加载文件愈多,页面失去响应的时间愈长。
//定义模块
define(['dependency'],function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName:printName
}
})
//加载模块
require(['myModule'],function(my){
my.printName();
})
语法:
requireJS定义了一个函数define,它是全局变量,用来定义模块。
define(id,dependencies,factory)
——id 可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
——dependencies 是一个当前模块用来的模块名称数组
——factory 工厂方法,模块初始化要执行的函数或对象,如果为函数,它应该只被执行一次,如果是对象,此对象应该为模块的输出值。
在页面上使用require函数加载模块;
require([dependencies], function(){});
require()函数接受两个参数:
——第一个参数是一个数组,表示所依赖的模块;
——第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
AMD推崇的是依赖前置,被提前罗列出来并会背提前下载并执行,后来做了改进,可以不用罗列依赖模块,允许在回调函数中就近使用require引入并下载执行模块。
CMD:即common module definition
就像AMD有个requireJS,CMD有个浏览器实现的sea.js,sj要解决的问题和rj一样,只不过在模块定义方式和模块加载时机上有所不同。
cmd是sea.js在推广过程中的规范化产出,sea.js是另一种前端模块化工具,它的出现缓解了requireJS的几个痛点。
define(id, deps, factory)
因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id;
CMD推崇依赖就近,所以一般不在define的参数中写依赖,而是在factory中写。
factory有三个参数: function(require, exports, module){}
一,require require 是 factory 函数的第一个参数,require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口;
二,exports exports 是一个对象,用来向外提供模块接口;
三,module module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。 demo // 定义模块 myModule.js define(function(require, exports, module) { var $ = require('jquery.js') $('div').addClass('active'); }); // 加载模块 seajs.use(['myModule.js'], function(my){ });
AMD与CMD区别
总结如下:
最明显的区别就是在模块定义时对依赖的处理不同。
AMD推崇依赖前置 在定义模块的时候就有声明其依赖的模块
CMD推崇就近依赖 只有在用到某模块的时候再去require
AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同。
为什么我们说两个的区别是依赖模块执行时机不同,为什么很多人认为ADM是异步的,CMD是同步的(除了名字的原因。。。)
同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。
CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。
这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因。
- 目录结构的制定
-- src # 源码目录
|__ index.js # 项目入口,注入store, 调用render方法
|__ App.js # 应用入口,页面整体布局,处理页面路由
|__ App.less # 全局共用样式文件
|__ theme.less # 项目主题文件
|__ Components
|__ ActiveChart # [组件ActiveChart ]
|__ index.js # 组件实现源码文件
|__ index.less
|__ AxisTitle # [组件AxisTitle]
|__ index.js # 组件实现源码文件
|__ index.less
|__ routes 定义应用的页面容器组件
|__ Cockpit # [页面1]
|__ index.js # 页面具体业务代码
|__ indes.less
|__components 页面的私有组件
|__ WorkSpace # [页面2]
|__ index.js # 页面具体业务代码
|__ indes.less
|__models
|__ index.js # 导出封装后的所有store,以及初始化saga
|__ request.js # 封装网络请求,比如所所有的方法进行拦截(inspector)
|__ model1 # [模块1]store基于具体业务模块来编写,方便多页面调用
|__ actionTypes.js
|__ actions.js
|__ reducer.js 封装业务逻辑
|__ model2 # [模块2]
|__services
|__model1.js 封装接口请求
|__model2.js 封装接口请求
|__common 公用的工具库
|__chartUtil
|__arrayUtil
|__constants