You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
layui/docs/modules.md

265 lines
8.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
title: 模块系统
toc: true
---
<h1 id="idea" lay-toc="{title: '概要'}">模块系统</h1>
> Layui 制定了一套适合自身应用场景的轻量级模块规范,以便在不同规模的项目中,也能对前端代码进行很好的管理或维护。 Layui 的轻量级模块系统,并非有意违背 CommonJS 和 ES Module ,而是试图以更简单的方式去诠释高效,这种对*返璞归真*的执念源于在主流标准尚未完全普及的前 ES5 时代,后来也成为 Layui 独特的表达方式,而沿用至今。
如下是一个关于模块的简单示例:
```
// 定义模块(通常单独作为一个 JS 文件)
layui.define([mods], function(exports){
// …
exports('mod1', api); // 输出模块
});
// 使用模块
layui.use(['mod1'], function(args){
var mod1 = layui.mod1;
// …
});
```
我们可以将其视为「像使用普通 API 一样来管理模块」,在此前提下,组件的承载也变得轻松自如,我们完全可以游刃在以浏览器为宿主的原生态的 HTML/CSS/JavaScript 的开发模式中,而不必卷入层出不穷的主流框架的浪潮之中,给心灵一个栖息之所。
当然Layui 自然也不是一个模块加载器,而是一套相对完整的 UI 解决方案,但与 Bootstrap 又并不相同,除了 HTML+CSS 本身的静态化处理Layui 的组件更倾向于 JavaScript 的动态化渲染,并为之提供了相对丰富和统一的 API使用时只需稍加熟悉便可在各种交互中应付自如。
<h2 id="define" lay-toc="">定义模块</h2>
`layui.define([mods], callback);`
- 参数 `mods` 可选,用于声明该模块所依赖的模块;
- 参数 `callback` 即为模块加载完毕的回调函数,它返回一个 `exports` 参数,用于输出该模块的接口。
```
/** demo.js **/
layui.define(function(exports){
// do something
// 输出 demo 模块
exports('demo', {
msg: 'Hello Demo'
});
});
// 若该模块需要依赖别的模块,则在 `mods` 参数中声明即可:
// layui.define(['layer', 'form'], callback);
```
如上所示,`callback` 返回的 `exports` 参数是一个函数,它接受两个参数:参数一为*模块名*,参数二为*模块接口*。
另外, `callback` 将会在初次加载该模块时被自动执行。而有时,在某些特殊场景中可能需要再次执行该 `callback`,那么可以通过 `layui.factory(mod)` 方法获得。如:
```
var demoCallback = layui.factory('demo'); // 得到定义 demo 模块时的 `callback`
```
- **模块命名空间**
Layui 定义的模块将会被绑定在 `layui` 对象下,如:`var demo = layui.demo;` 每个模块都有一个特定命名,且无法被占用,所以你无需担心模块的命名空间被污染,除非通过 `layui.disuse([mods])` 方法弃用已定义的模块。
以下是定义一个「依赖 Layui 内置模块」的模块示例:
```
layui.define(['layer', 'laydate'], function(exports){
var layer = layui.layer // 获得 layer 模块
var laydate = layui.laydate; // 获得 laydate 模块
// 输出模块
exports('demo', {}); // 模块名 demo 未被占用,此时模块定义成功
// exports('layer', {}); // 模块名 layer 已经存在,此时模块定义失败
});
```
同样的,在「扩展模块」时,也同样不能命名已经存在的模块名。
<h2 id="use" lay-toc="">使用模块</h2>
`layui.use([mods], callback);`
- 参数 `mods` 若填写,必须是已被成功定义的模块名;
<br>注<sup>2.6+</sup>:若 mods 不填,即只传一个 callback 参数时,则表示引用所有内置模块。
- 参数 `callback` 即为使用模块成功后回调函数。
<br>注<sup>2.6+</sup>:该回调会在 html 文档加载完毕后再执行,确保你的代码在任何地方都能对元素进行操作。
```
// 使用指定模块
layui.use(['layer', 'table'], function(){
var layer = layui.layer;
var table = layui.table;
// do something
});
// 使用所有内置模块layui v2.6 开始支持)
layui.use(function(){
var layer = layui.layer;
var table = layui.table;
var laydate = layui.laydate;
// …
// do something
});
```
你还可以通过 `callback` 返回的参数得到模块对象,如:
```
layui.use(['layer', 'table'], function(layer, table){
// 使用 layer
layer.msg('test');
// 使用 table
table.render({});
});
```
- **执行「定义模块」时的回调函数**
在上文的定义模块中,我们提到一个特殊场景,即重新获取定义模块时的 `callback` 函数, 譬如在*单页面应用*开发中,我们在视图碎片中使用某个模块,由于定义模块时的 `callback` 只会在模块初次加载中被调用,而当视图碎片在每次被渲染时,又往往需要该 `callback` 被再次执行,那么则可以通过以下方式实现:
```
// 在单页面视图碎片渲染时,再次调用「定义模块」时的 `callback`
layui.use('demo', layui.factory('demo'));
```
<h2 id="extend" lay-toc="">扩展模块</h2>
`layui.extend(obj);`
- 参数 `obj` 是一个对象,必选,用于声明模块别名。
除了 Layui 的内置模块,在实际项目开发时,必不可少也需要扩展模块(可以简单理解为符合 layui 模块规范的 JS 文件)。 现在,让我们尝试着扩展一个 Layui 第三方模块:
1. **创建模块**
我们在前文的「模块命名空间」提到,模块名具有唯一性,即不可被占用,因此我们扩展的模块必须是一个未被定义过的模块名。假设为:`firstMod`,然后新建一个 `firstMod.js` 文件并放入项目的任意目录中(最好不要放入到 Layui 原始目录)
2. **编写模块**
接下来我们开始定义 `firstMod` 模块,并编写该模块主体代码。
```
/**
* 编写一个 firstMod 模块
**/
layui.define(function(exports){ // 也可以依赖其他模块
var obj = {
hello: function(str){
alert('Hello '+ (str || 'firstMod'));
}
};
// 输出 firstMod 接口
exports('firstMod', obj);
});
```
3. **声明模块**
现在,我们只需声明模块名及模块文件路径,即完成模块扩展。
```
// 假设 firstMod 模块文件所在路径在: /js/layui_exts/firstMod.js
layui.config({
base: '/js/layui_exts/' // 配置 Layui 第三方扩展模块存放的基础目录
}).extend({
firstMod: 'firstMod', // 定义模块名和模块文件路径,继承 layui.config 的 base 路径
// mod2: 'mod2' // 可同时声明其他更多模块
});
// 也可以不继承 layui.config 的 base 路径,即单独指定路径
layui.extend({
firstMod: '{/}/js/layui_exts/firstMod' // 开头特定符 {/} 即代表采用单独路径
});
// 然后我们就可以像使用内置模块一样使用扩展模块
layui.use(['firstMod'], function(){
var firstMod = layui.firstMod;
firstMod.hello('World');
});
```
> 扩展模块是项目开发的重要环节,它既可以是工具性组件,也可以是纯业务组件,是 Layui 的延伸,也是项目的支撑。
<h2 id="index" lay-toc="">建立模块入口</h2>
在不同的页面中,可能需要用到不同的业务模块。以首页为例:
```
<script src="/js/layui/layui.js"></script>
<script>
layui.config({
base: '/js/modules/' // 业务模块所在目录
}).use('index'); // 加载当前页面需要的业务模块
</script>
```
上述的 `index` 模块即对应的模块文件 `/js/modules/index.js`,它同样也必须符合 Layui 模块规范。如:
```
/**
* index.js 首页业务模块
*/
layui.define(['layer', 'form'], function(exports){
var layer = layui.layer;
var form = layui.form;
layer.msg('Hello Index');
exports('index', {}); // 输出模块名需和 use 和 extend 时的模块名一致
});
```
**合并模块入口**
当项目存在许多不同的业务模块(且存在一定的依赖关系),我们又希望在页面中建立统一的入口模块。如:
```
// mod1.js
layui.define('layer', function(exports){
// …
exports('mod1', {});
});
// mod2.js假设依赖 mod1 和 form
layui.define(['mod1', 'form'], function(exports){
// …
exports('mod2', {});
});
// mod3.js
// …
// index.js 主入口模块
layui.define('mod2', function(exports){
// …
exports('main', {});
});
```
我们可以将上述模块合并为一个文件来加载,即借助构建工具(如 Gulp将上述的 <code>mod1、mod2、mod3、index</code> 等业务模块合并到一个模块文件:`index.js`,此时只需在页面统一加载该模块即可。这样我们最多只需要加载两个 JS 文件:`layui.js、index.js`,这将大幅度减少静态资源的请求。
<br>
## 小贴士
> 综上: Layui 轻量级模块系统,无非就是:定义模块、使用模块、弃用模块、扩展模块的相互呼应,翻译成 API 即:
> - `layui.define();`
> - `layui.use();`
> - `layui.disuse();`
> - `layui.extend();`
> ---
> 熟练运用,可让您的项目更利于维护。