模块化

前言

随着应用的功能逐渐丰富,逻辑的复杂度不断的增加,多人协作等问题, BUI有了自己的模块化方案, 类似requirejs的AMD. 熟悉requirejs,seajs都可以很好的适应过来.

模块化解决什么问题?

  • 命名冲突
  • 文件依赖
  • 多人协作
  • 可维护性
  • 跨页面共享

定义模块

window.loader 默认注册给了 bui.loader. 关于loader的用法,可以查看 bui.loader API.

loader.define

*loader.define 定义一个匿名模块. *

  1. loader.define(function(require,exports,module){
  2. // 以下几个参数非必须,如果前面加载了依赖,则这三个参数后移;
  3. // require : 相当于 loader.require, 获取依赖的模块
  4. // exports : 如果没有return 可以采用这种方式输出模块
  5. // module : 拿到当前模块信息
  6. // 第一次加载会执行一次
  7. // 模块如果需要给其它模块加载,通过 return 的方式抛出来
  8. return {};
  9. })

定义模块需要遵循什么?

  1. 一个js 文件里面只能有一个 loader.define 的匿名模块;
  2. 业务逻辑需要在 loader.define 里面,防止加载其它模块的时候冲突;
  3. 避免循环依赖 A ->依赖 B 模块, 而 B模块 -> A模块, 这就造成循环依赖,一般需要避免这种设计,如果一定要用, 不使用依赖前置的方式;
  4. 避免循环嵌套自身, 在loader.define 里面 又 require 加载当前模块, 这个时候还没实例化,就会造成死循环;
  5. 作为依赖的模块, 里面不要执行, 应该返回对象给外层调用的方式;

加载模块

loader.require

假设我们定义了一个匿名模块, 是在pages/page2/目录下, 目录下有 page2.html ,page2.js 两个文件. 则默认匿名模块的 模块名是 pages/page2/page2 会根据.html 文件提取前面路径作为模块名.

page2.js

  1. loader.define(function(require,exports,module){
  2. // 定义初始化
  3. function init(text){
  4. // console.log("init:"+text)
  5. }
  6. // 自执行初始化, 如果要给tab 使用,建议不要自执行.
  7. init("第一次会自执行");
  8. // 抛出方法及变量给外部访问.
  9. return {
  10. init: init,
  11. pageName: "page2"
  12. }
  13. })

现在我们想在刚刚的main.js里面加载这个模块,调用pages/page2/page2 的名称.

main.js

  1. loader.define(function(require,exports,module){
  2. // 1. 加载pages/page2/page2模块 方法1: 这里会自执行一次 init. 输出自执行. 如果该模块已经加载过了,这里则不会执行.
  3. require("pages/page2/page2");
  4. // 2. 有回调的时候,是会每次都执行, 如果define的时候,有一次自执行, 会变成执行2次.
  5. require("pages/page2/page2",function(page2){
  6. // 这里会执行第2次.
  7. page2.init("回调执行")
  8. })
  9. return {
  10. pageName: "main"
  11. }
  12. })

这样打开首页的时候,就会加载main.js, main.js 会去加载pages/page2/page2模块,并调用对应的方法.

造成重复执行一般在tab比较常见, bui.tabto 事件是会每次都执行, 如果 loader.require 的模块有相同init回调, 则每次都会执行两次, 解决的办法是, 外部要操作里面的init方法时, define 的时候,不要自执行init.

模块的定义及加载更多用法,请大家自行查阅 bui.loader API

加载文件资源

loader.import

没有经过define的第三方资源,又不想全局引用,可以使用loader.import动态引入进来. 例如,图表控件.

  1. 例子1: 动态加载单个样式
  2. loader.import("main.css",function(){
  3. // 创建成功以后执行回调
  4. });
  5. 例子2: 动态加载单个脚本
  6. loader.import("main.js",function(){
  7. // 创建成功以后执行回调
  8. });
  9. 例子3: 动态加载多个脚本
  10. loader.import(["js/plugins/baiduTemplate.js","js/plugins/map.js"],function(){
  11. // 创建成功以后执行回调
  12. });
  13. 例子4: 1.5.2新增, 动态加载模板,回调每次都执行, 如果放在 loader.require 里面执行,则默认只初始化一次;
  14. loader.import("pages/ui/list.html",function(res){
  15. // 拿到模板信息
  16. });
  17. 例子5: 1.5.4新增, html,渲染到某个id下,只渲染一次. 有回调也只执行一次
  18. loader.import("pages/ui/list.html","#id",function(res){
  19. // 在渲染模板到#id以后,回调只执行一次
  20. });

样式的引入没有局部作用域,所以加载样式文件可能会造成影响全局,最好样式还是统一sass模块化管理.

同步加载多个文件

loader.importSync

如果需要同步加载多个文件, 应该使用loader.importSync来替代loader.import;

  1. 例子: 动态加载多个脚本
  2. loader.importSync(["js/plugins/baiduTemplate.js","js/plugins/map.js"],function(){
  3. // 创建成功以后执行回调
  4. });

获取及配置模块

loader.map

可以用于设置或者获取已经加载的模块的相关信息

  1. 例子1: 获取所有模块的配置信息
  2. var map = loader.map();
  3. 例子2: 声明单个模块, router路由默认声明了main模块,页面打开会自动加载该模板下的资源,也可以通过map去修改
  4. 修改首页,必须在 window.router=bui.router(); 之后;
  5. loader.map({
  6. moduleName: "main",
  7. template: "pages/main/main.html",
  8. script: "pages/main/main.js"
  9. })
  10. 例子3: 定义多个模块,并修改路径
  11. loader.map({
  12. baseUrl: "",
  13. modules: {
  14. "main": {
  15. moduleName: "main",
  16. template: "pages/main/main.html",
  17. script: "pages/main/main.js"
  18. }
  19. "home": {
  20. moduleName: "home",
  21. template: "pages/main/home.html",
  22. script: "pages/main/home.js"
  23. }
  24. }
  25. })

注意:

  • 定义了模块名以后,单页跳转则不能使用路径跳转,而需要传模块跳转, 例如, router.load({url:"home"})
  • 如果loader.define的第一个参数有自定义名称, 则还需要通过loader.map配置下模块的路径及模板指向.

获取模块的信息

1.5.3 新增

loader.get

  1. var main = loader.get("main");

设置模块的信息

1.5.3 新增

loader.set

如要设置main模块, 必须在 window.router = bui.router() 之后, router.init 之前.

  1. loader.set("main",{
  2. template:"pages/login/login.html",
  3. script: "pages/login/login.js"
  4. });

页面模块的生命周期

1.5.3 新增. 需要配合路由使用,路由里面会去调用模块定义的生命周期.

  • beforeCreate,create 只在模块第一次创建的时候执行,如果相同模块第2次拿的是缓存, 不会触发;
  • beforeLoad,loaded 每次进入页面都会执行, loaded 就相当于 loader.define(function(){}) 里面的function;
  • show,hide 每次页面前进后退都会分别执行, 可以通过形参拿到 show,hide 的 type 是 load, 还是 back, 默认当前页刷新, 也会触发 show, type 则等于 firstload;
  • beforeDestroy,destroyed 每次后退前跟后退后执行;

注意: beforeLoad 这里return false 并不能阻止页面跳转及执行, 如果要阻止应该在 bui.load({url:"",beforeLoad:function(){ return false; }}).

  1. loader.define({
  2. beforeCreate: function() {
  3. // 只在创建脚本前执行,缓存的时候不执行
  4. console.log(this.moduleName + " before create")
  5. },
  6. created: function() {
  7. // 只在创建后执行,缓存的时候不执行
  8. console.log(this.moduleName + " createed")
  9. },
  10. beforeLoad: function() {
  11. // 页面每次跳转前都会执行
  12. console.log(this.moduleName + " before load")
  13. },
  14. loaded: function() {
  15. // 页面每次跳转后都会执行
  16. console.log(this.moduleName + " loaded")
  17. },
  18. hide: function(e) {
  19. // 页面每次跳转后退都会执行当前模块的触发
  20. console.log(this.moduleName + " hide")
  21. console.log(e.type)
  22. },
  23. show: function(e) {
  24. // 页面每次跳转后退都会执行当前模块的触发
  25. console.log(this.moduleName + " show")
  26. console.log(e.type)
  27. },
  28. beforeDestroy: function() {
  29. // 页面每次后退前执行
  30. console.log(this.moduleName + " before destroy")
  31. },
  32. destroyed: function() {
  33. // 页面每次后退后执行
  34. console.log(this.moduleName + " destroyed")
  35. }
  36. })

当然,你依然可以使用默认最简单的模块创建方式, 只是特殊模块你可以给它自己的生命周期, 比方我在列表页面,进去详情页, 后退到列表页, 是不会刷新的, 之前的方式是在后退的时候执行某个方法. 现在只要在 show 的这个生命周期里, 我可以调用这个页面的某个局部刷新的方法, 不管是前进后退, 都可以执行.

例子: 利用生命周期实现后退刷新.

  1. loader.define({
  2. loaded: function() {
  3. this.pageview = {};
  4. // 初始化
  5. this.pageview.init = function(){
  6. }
  7. // 局部刷新
  8. this.pageview.refresh = function(){
  9. }
  10. // 这个是抛出给 loader.require 访问的, 不能return this
  11. return this.pageview;
  12. },
  13. show: function(e) {
  14. // 后退才触发刷新操作
  15. if( e.type == "back" ){
  16. this.pageview.refresh();
  17. }
  18. }
  19. })

比方跳转的页面里面有个定时器, 后退的时候, 需要清理掉这个定时器, 这些是需要自己清除的.

  1. loader.define({
  2. loaded: function() {
  3. // 页面每次跳转后都会执行
  4. console.log(this.moduleName + " loaded")
  5. // 定时刷新
  6. this.timetoRefresh = window.setInterval(function(){
  7. // 4秒后执行刷新
  8. },2000)
  9. },
  10. beforeDestroy: function() {
  11. // 页面每次后退前执行
  12. console.log(this.moduleName + " before destroy")
  13. if( this.timetoRefresh ){
  14. window.clearInterval(this.timetoRefresh);
  15. }
  16. }
  17. })

还有一些比较有用的方法, 会在组件那里介绍.

疑难解答

1. 如何抛出当前模块的方法共享

  1. 推荐 使用return 的方式 ;

2. 微信调试的缓存问题怎么解决?

在 index.js 配置bui.loader的cache参数 初始化必须在 window.router 前面

  1. window.loader = bui.loader({
  2. cache: false
  3. })

3. 为什么不直接采用requirejs或者seajs呢?

这两种方式都有在项目中使用,这样模块的复用及开发方式就无法统一,A项目开发完的部分模块,可能B项目也能用,但两者各自用的模块化方式不同, 这就需要熟悉的人去做一定的修改. 采用我们自己的模块化方式,可以跟bui.router路由更好的配合, 后面模块化的公共插件也会越来越多, 这是我们以后希望看到的.

4. 如何定义模块的依赖呢?

main.js

  1. // 依赖前置, 这种会优先加载完 page2,page3模块以后再执行main的回调. page2,page3 只定义,不执行.
  2. loader.define(["pages/page2/page2","pages/page3/page3"],function(page2,page3,require,exports,module){
  3. // 如果需要用到当前模块信息的话, page3后面依次还有 require,exports,module
  4. })

5. 如何定义一个自定义名字的模块呢?

*第1种: *

  • 第1步: 声明自定义模块, 名称需要跟映射的模块名一致

pages/page2/page2.js

  1. loader.define("page2",function(require,exports,module){
  2. // 这里是page2的业务逻辑
  3. })
  • 第2步: 在首页 index.html 的 bui.js 下面引入该文件.

index.html

  1. <script src="js/bui.js"></script>
  2. <!-- 加入自定义模块 -->
  3. <script src="pages/page2/page2.js"></script>

第2种:

  • 第1步: 映射脚本路径

index.js

  1. // 映射脚本路径
  2. loader.map({
  3. moduleName: "page2",
  4. script: "pages/page2/page2.js"
  5. })
  6. // 把路由实例化给 window.router
  7. window.router = bui.router();
  8. bui.ready(function(){
  9. })
  • 第2步: 声明自定义模块, 名称需要跟映射的模块名一致

pages/page2/page2.js

  1. loader.define("page2",function(require,exports,module){
  2. // 这里是page2的业务逻辑
  3. })

模块的定义及加载更多用法,请大家自行查阅 bui.loader API