BUI 单页路由与模块化开发

修订者 描述 修改日期
王伟深 单页路由与模块化开发 2017-12-21
王伟深 重排阅读顺序 2017-12-29
王伟深 新增预加载,及解决重复加载页面等问题 2018-3-27

目录

简介

欢迎加入我们的QQ技术交流群:

BUI 移动开发交流群1: 691560280
BUI 移动开发-内部交流: 214895324 (非同事不加)

单页示例预览
点击这里预览

单页与模块化的好处:

  • 页面无刷新体验;
  • 页面预加载,让webapp跟本地应用一样快;
  • 页面切换效果可控;
  • 页面按需加载;
  • 前端组件化复用;
  • 方便维护;
  • 跨平台;
  • 完美融入第三方平台;

小提示: 你还可以了解 BUI 5分钟入门, 对BUI的整个制作流程有一个简单的理解.

路由有什么功能?

  • 页面跳转,支持html跳转或者模块跳转;
  • 支持选择不同动画,融入不同平台的切换效果, (常见有[默认]微信的 “push”效果,bingotouch及link的”slide”, dcloud的 “cover”);
  • 支持指定后退多层,并且可以执行回调;
  • 支持webapp预加载;
  • 支持页面刷新;
  • 支持后退刷新;
  • 支持当前页面替换;
  • 支持页面的局部加载;
  • 支持页面传参,获取参数;
  • 支持指定跳入某个页面;
  • 支持缓存,默认已经配置;
  • 支持展示进度条,需要配置;
  • 支持手机端物理按键后退动画,不支持前进操作;
  • 支持找不到页面自动跳到404页面,需要配置;
  • 支持不给手机物理后退,需要配置;

路由适用场景

  • 纯webapp开发,在浏览器运行;
  • 在其它平台运行 微信,QQ,淘宝,支付宝, 获得更好的融入体验;
  • 结合第三方平台开发轻应用, 如: Link,Bingotouch,Dcloud,APICloud 等;
  • 混合使用, 在多页的原生开发中内嵌BUI单页开发;
  • 主题类推广开发, 有很多的交互动画供你选择;
  • 需要快速在单页跟多页以及原生平台间切换,可以用最快的速度切换过来(需要按多页的方式开发).

开始学习

一、学习命令行构建

我们从NODE命令行开始构建,这个也是我们推荐的一种构建方式, 每次会从github上面获取最新的模板构建.

进入桌面目录,创建一个demo工程, 默认就是单页模板工程了.

注意: buijs 工具更新了,需要重新安装, 新的工具默认创建的工程目录把内容放在, src 目录里面, 通过安装依赖以后,可以解决, 工程化构建,接口跨域,服务器等问题,减少对开发工具的依赖. 请仔细查看使用说明.
如何使用buijs命令行工具?

buijs 创建工程预览

你也可以点击这里 下载单页开发包? (单页的工程包需要部署起来才能预览)

打开 demo 目录 工程生成以后的路径: /Users/用户名/Desktop/demo

二、目录规范

BUI 单页示例目录说明

以上图片是路由示例的目录,创建的开发工程目录是纯净的, 在 demo/pages/下,只有入口 main 模块.
点击这里下载单页开发示例工程?

目录说明:

目录名 描述 是否必须
index.html 应用首页入口文件
index.js 路由的初始化脚本及全局事件
css/ 应用样式及bui.css样式
font/ bui.css用到的字体图标
images/ 应用图片目录
js/ 应用脚本
js/zepto.js bui.js默认依赖于zepto.js 或 jquery
js/bui.js BUI交互控件库
js/plugins/ 应用插件目录
js/plugins/fastclick.js 移动端快速点击插件
pages/ 应用的模块
pages/main/ 默认路由初始化以后会先载入这个main模块
pages/main/main.html main模块的模板
pages/main/main.js main模块的业务脚本

小提示: pages 目录下, 建议把模块扁平化, 便于查找及复用. 类似 loadPart_part 是loadPart的一部分.

三、路由初始化

3.1 打开编辑 index.html , body 下只有一个div,这个便是路由最外层结构.

<div id="bui-router"></div>

除了这个div,一些全局的脚本文件及依赖也在这里加载.
<script src="js/zepto.js"></script>
<script src="js/plugins/fastclick.js"></script>
<script src="js/bui.js"></script>
<!-- 初始化单页 -->
<script src="index.js"></script>

3.2 打开编辑 index.js ( 路由初始化的时候,必须注册给 window.router )

// 把路由实例化给 window.router 
window.router = bui.router();

bui.on("pageinit",function(){

    // 加载页面到div容器里面, 更多参数请查阅API
    router.init({
        id: "#bui-router"
    })
})

疑问: 这里为什么用的是pageinit事件,而不是 bui.ready 呢?

简单的说,就是 pageinit 事件会更快, 常用于 UI控件的初始化,不受 bui.isWebapp 切换影响;
bui.ready 事件在不同平台的初始化速度不一样, 所以放这里的UI控件,速度会比放在 pageinit 要慢.
bui.ready 在多页开发里面, 主要用于原生跟web平台间的切换, 你可以了解有哪些原生方法
Native模块
bui.router 属于UI控件,与原生无关,所以无需放在 bui.ready , 使用单页以后,也不用过多关注native模块了,单页已与平台切换关系不大.

四、路由功能示例

这里我们来讲讲路由的基本功能

注意:
1. 路由初始化的时候,必须注册给 window.router
2. 一个应用的层级建议控制在 5级左右. 例如:

// 推荐: 这也是微信小程序里面推荐的层级数
A->B->C->D->E 

// 不要这样, 这种做法本身会让用户陷入混乱
A->B->C->B->C->D->E-C

  • 页面跳转并传参:

    方法1: (推荐)
    bui.load({ url: "pages/page2/page2.html", param: {} });
    
    方法2: 
    router.load({ url: "pages/page2/page2.html", param: {} });
    

  • 页面接收上一个页面参数:

    方法1: (推荐)
    var getParams = bui.getPageParams();
        getParams.done(function(res){
          console.log(res)
        })
    
    方法2: 
    var params = router.getPageParams();
    

  • 页面后退: (支持后退以后执行回调,具体查看bui.back API

    方法1: (推荐)
    bui.back();
    
    方法2: 
    router.back();
    

  • 页面刷新: ( 这种刷新方式,简单粗暴,不建议使用 )

    方法1: (推荐)
    bui.refresh();
    
    方法2: 
    router.refresh();
    

  • 当前页面替换:

    方法1: (推荐)
    bui.load({ url: "pages/page2/page2.html", param: {} ,replace: true });
    
    方法2: 
    router.replace({ url: "pages/page3/page3.html" });
    

  • 局部加载:

    router.load({ id:"#part", url: "pages/page2/page2.html", param: {} ,replace: true });
    
    

  • 预加载 1.4.2新增:

如果你的应用是webapp,那这个功能就比较好用了,先在首页预加载想要触发的页面, 然后再次点击跳转就不用请求太多内容了.

// 预加载一个页面
router.preload({ url: "pages/page2/page2.html" });

// 预加载多个页面
router.preload([{ 
    url: "pages/page2/page2.html" 
  },{ 
    url: "pages/page3/page3.html" 
  }]);

五、路由切换效果

单页示例预览

一般在路由初始化的时候,就选择好一种动画最好, 这些效果可以融入主流平台,保持一致的交互体验. 默认是模拟微信的切换效果 push .

例如:

router.init({
  id: "#bui-router",
  effect: "slide"
})

常见效果有:

  • 在微信 推荐使用 push
  • 在link 推荐 slide
  • 在Dcloud, APICloud 推荐 cover
  • 在Bingotouch 推荐还是 push 或者 slide 效果
  • 在移动浏览器 你也可以选择 none, 或fadein ( 移动的浏览器众多,大部分都默认开启了左右手势操作, 这个对一些效果会有一定影响 )

如果需要修改, 则可以使用 router.option 方法.

六、路由模块化开发

6.1、定义模块

我们在浏览器打开 index.html, 这样原本只有一个div内容, 为何会变成这样?

路由工程预览
这是因为路由初始化的时候会默认加载一个main模块, main模块就是 pages/main/ 下面的内容. 一个模块默认包含两个文件, 一个 main.html, main.js , 这是默认main模块的路径.

注意: 如果你打开是一片空白,把工程部署在服务器, 请返回查看
BUI 5分钟入门.

打开 main.html
我们可以看到一个标准的BUI页面结构, bui-page 的子级是 header main footer 代表上中下结构, 其中 bui-page 下面的 main 是必须的. header 跟 footer 可以省略.

<div class="bui-page">
    <header class="bui-bar">
        <div class="bui-bar-left">
            <!-- 左边有图标示例 -->
            <div class="bui-btn"><i class="icon-back"></i></div>
        </div>
        <div class="bui-bar-main">BUI单页开发工程模板</div>
        <div class="bui-bar-right">
            <!-- 右边有图标示例 -->
            <div class="bui-btn"><i class="icon-search"></i></div>
        </div>
    </header>
    <main>
        <!-- 中间内容 -->
    </main>
    <footer>
        <!-- 底部内容 -->
    </footer>
</div>


打开 main.js
BUI 1.4.0 开始把 window.loader 默认注册给了 bui.loader. 所以你可以使用 loader.方法 关于loader的用法,可以查看 bui.loader API 输入 bui.loader 查看.
这里用loader.define定义了一个匿名模块.

loader.define(function(require,exports,module){

    // 以下几个参数非必须,如果前面加载了依赖,则这三个参数后移;
    // require : 相当于 loader.require, 获取依赖的模块
    // exports : 如果没有return 可以采用这种方式输出模块
    // module : 拿到当前模块信息

    // 模块如果需要给其它模块加载,通过 return 的方式抛出来,或者module.exports的方式
    //module.exports = {};
    return {}
})
  • 定义模块需要遵循什么?
    1. 一个js 文件里面只能有一个 loader.define 的匿名模块;
    2. 业务逻辑需要在 loader.define 里面,防止加载其它模块的时候冲突;
    3. 避免循环依赖 A ->依赖 B 模块, 而 B模块 -> A模块, 这就造成循环依赖,一般需要避免这种设计,如果一定要用, 不使用依赖前置的方式;
    4. 避免循环嵌套, 在loader.define 里面 又 require 加载当前模块, 这个时候还没实例化,就会造成死循环;
    5. 每个页面的ID不能相同;

关于模块定义的更多疑问,可以点击左边的疑难解答

6.2、加载定义好的模块

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

page2.js

loader.define(function(require,exports,module){

    return {
      pageName: "page2"
    }
})

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

main.js

loader.define(function(require,exports,module){

    // 加载pages/page2/page2模块
    require("pages/page2/page2",function(page2){

        // 访问page2模块的名称
        console.log( page2.pageName )
    })

    return {
      pageName: "main"
    }
})

这样打开首页的时候,就会加载main.js, main.js 会去加载pages/page2/page2模块,成功以后输出名称.

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

七、路由流程图及事件

7.1 流程图

前面讲到的 打开 index.html –> 通过 loader.require 加载 main 模块 –> 再了解了模块如何定义跟加载, 这就是BUI路由的加载过程了. 详细的过程可以在下方的流程图看到.

BUI 路由模块加载流程图

7.2 事件

  1. 页面加载,在模板加载完成就会触发,有缓存的时候只触发一次

    router.on("load",function(e){
      // 获取当前页的模块
      console.log(e.currentTarget);
      // 获取上一页的模块
      console.log(e.prevTarget);
    })
    

  2. 页面完成,每次加载完模板都会触发

router.on("complete",function(e){
  // 获取当前页的模块
  console.log(e.currentTarget);
  // 获取上一页的模块
  console.log(e.prevTarget);
})
  1. 页面后退

router.on("back",function(e){
  // 获取当前页的模块
  console.log(e.currentTarget);
  // 获取上一页的模块
  console.log(e.prevTarget);
})

4. 页面刷新

router.on("refresh",function(e){
  // 获取当前页的模块
  console.log(e.currentTarget);
  // 获取上一页的模块
  console.log(e.prevTarget);
})

5. 页面局部加载后触发

router.on("loadpart",function(e){
  // 获取当前页子模块
  console.log(e.currentTarget);
  // 获取当前页父模块
  console.log(e.prevTarget);
})

注意: 建议全局事件都在 index.js 加载.
例如: index.js

bui.on("pageinit",function(){

    // 静态绑定 bui-router 容器下所有带有 href 属性的按钮, 点击就会自动跳转, 一个应用只需要初始化一次.
    bui.btn({id:"#bui-router",handle:".bui-btn"}).load();

    // 监听页面所有后退事件
    router.on("back",function(e){
      console.log(e);
    })
})

八、调试

8.1 Chrome调试

8.1.1 输入地址

Chrome 输入 http://localhost:8000

默认端口号:8000, 一个端口对应一个工程, 如需更改同样是在package.json 配置.

8.1.2 模拟手机效果

打开chrome 开发者工具, 开启模拟手机效果, 这样才能模拟手机的滑动拖拽事件.

chrome 预览图

如果开启了跨域的谷歌,可以直接打开 index.html

8.2 手机调试

手机端预览,请更改成您的 http://IP+端口号.

# mac 查看ip 查看 inet 的信息就是ip
$ ifconfig

# windows 查看ip
$ ipconfig

8.3 接口如何跨域

打开根目录下的 package.json ,里面有个 proxy 的对象, key值为接口的目录名, target 为域名的host.

假设请求的接口地址为: http://www.easybui.com/api/getDetail/id/123

需要这样配置 proxy :

{
...
"proxy": {
    "/api": {
      "target": "http://www.easybui.com",  
      "changeOrigin":true  
    }
  }
...
}

js: 脚本请求使用相对路径, 为了后面更改为正式地址, 建议可以把url部分作为配置项.

var apiUrl = "";

bui.ajax({
    url: apiUrl+ "api/getDetail/id/123"
}).then(function(res){

})

疑难解答

如使用过程中有不了解的, 欢迎提出来. wangwsh@bingosoft.net

模块定义的常见问题

  • 如何抛出当前模块的方法共享
    1. 推荐 使用return 的方式 ;
    2. 使用module.exports 的方式;
    3. exports 的方式;
    使用任意一种就可以.

  • 熟悉requirejs或者seajs 模块化开发的开发者是不是有种似曾相识的感觉?
    bui.loader跟requirejs都属于AMD异步模块定义的方式, 按照requirejs的接口设计, 目的就是为了让开发者快速上手. 如果你之前写过一些模块是基于以上两种方式,只要进行一些简单的修改,就能拿过来用的.

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

  • 如何定义模块的依赖呢?
    main.js

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

  • 如何定义一个自定义名字的模块呢?
    有时候,我们觉得通过路径加载名称比较长,想变短一点,那就需要自定义模块名称了,像main模块一样, 自定义名称需要有两步.

  • 第1步: 映射脚本路径
    index.js
    // 映射脚本路径
    loader.map({
      moduleName: "page2",
      script: "pages/page2/page2.js"
    })
    
    // 把路由实例化给 window.router 
    window.router = bui.router();
    
    bui.on("pageinit",function(){
    
        // 加载页面到div容器里面, 更多参数请查阅API
        router.init({
            id: "#bui-router"
        })
    })
    
  • 第2步: 声明自定义模块, 名称需要跟映射的模块名一致
    pages/page2/page2.js
    loader.define("page2",function(require,exports,module){
      // 这里是page2的业务逻辑 
    })
    
    

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

BUI-Fast 插件快速书写

如果你也是用 sublimetext 开发, 那么我们强烈推荐您使用 BUI Fast 插件进行开发,可以大大节省你的时间.

如何安装使用 BUI Fast

ui-router 演示:

ui-router

ui-page 演示:

ui-page

bui-slide-demo 演示:

bui-slide-demo

页面结构新增

缩写代码 描述
ui-router BUI 单页标准结构

页面脚本控件新增

缩写代码 描述
bui-router BUI 单页初始化
bui-router-load 单页跳转
bui-router-refresh 单页刷新
bui-router-replace 单页替换
bui-router-back 单页后退
bui-router-getPageParams 获取页面参数
bui-router-loadPart 局部加载
bui-loader-define 模块定义
bui-loader-require 模块加载
bui-loader-import 脚本及样式资源动态引入
bui-loader-map 单个模块配置
bui-loader-mapall 多个模块配置
bui-loader-preload 预加载页面

查看更多简写代码

了解更多BUI相关

到这里,相信你已经掌握了BUI的入门知识了, 关于开发过程中的技巧及开发规范, 可以阅读 BUI 规范文档