单页路由

前言

单页路由可以实现对路由的复杂操作, 弥补多页开发的不足, 在体验上, 效果也会更佳. 每一个单页都是一个组件.

单页的原理是通过ajax请求,把其它页面以局部加载的方式,渲染到路由里面, 那页面重复加载就势必会导致ID重复, 必须遵守以下几个原则.

  1. 使用 router.$ 替换 $ 选择器; 例如:
    • 获取或者设置 $("#id").text() 改成 router.$("#id").text();
    • 事件绑定 $("#id").click(function(){}) 改成 router.$("#id").click(function(){}) 才能确保你操作的是当前页面选择器.
    • $.each $.extend … 这类非选择器的操作无需改变;
  2. 加载过的页面, 使用后退刷新处理;
    • 例如: 从首页 main 跳转到登录页, 在登录的时候,就应该使用后退页面刷新或者局部刷新处理, 而不是使用 bui.load 继续跳转; 可以了解下登录权限.
  3. 样式没有局部作用域, 需要手动加上父级选择器;
  4. 路径问题;
    • 例如: /pages/ui/list.htmlpages/ui/list.html是不一样的, 在打包以后, /开头的文件将会从内存的根目录开始查找, 会导致404, 应该统一使用相对 index.html路径的写法 pages/ui/list.html.
  5. 页面预览;
    • 单页里面只有模板跟模块, 直接打开里面什么都没有, 可以通过index.html#+模块名的方式进行预览. index.html#pages/ui_controls/bui.list

效果预览

单页示例预览

点击这里体验

路由功能

  • 页面跳转,支持html跳转或者模块跳转;
  • 支持选择不同动画,融入不同平台的切换效果;
  • 支持预加载;
  • 支持页面刷新;
  • 支持当前页面替换;
  • 支持页面的局部加载;
  • 支持页面传参,获取参数;
  • 支持指定跳入某个页面;
  • 支持缓存,默认已经配置;
  • 支持展示进度条,需要配置;
  • 支持后退刷新;
  • 支持后退多层;
  • 支持后退到指定模块;
  • 支持物理后退按键;

创建单页工程

使用buijs命令行构建. 建议版本在1.6.0以上, 如何使用buijs命令行工具?

buijs 创建工程预览

创建demo工程

  1. # 创建目录
  2. $ mkdir demo
  3. # 进入demo目录
  4. $ cd demo
  5. # 创建单页工程
  6. $ buijs create

安装依赖及预览

  1. # 安装工程依赖
  2. $ npm install
  3. # 运行服务并预览
  4. $ npm run dev

目录规范

BUI 单页示例目录说明

目录说明:

目录名描述是否必须
/app.json插件及跨域的配置
/gulpfile.jsgulp编译配置
/package.json工程依赖配置
/src/index.html应用首页入口文件
/src/index.js路由的初始化脚本及全局事件
/src/css/应用样式及bui.css样式
/src/font/bui.css用到的字体图标
/src/images/应用图片目录
/src/js/应用脚本
/src/js/zepto.jsbui.js默认依赖于zepto.js 或 jquery
/src/js/bui.jsBUI交互控件库
/src/pages/应用的模块
/src/pages/main/默认路由初始化以后会先载入这个main模块
/src/pages/main/main.htmlmain模块的模板
/src/pages/main/main.jsmain模块的业务脚本

路由初始化

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

src/index.html

  1. <!DOCTYPE HTML>
  2. <html lang="en-US">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  5. <title>BUI单页工程</title>
  6. <meta name="format-detection" content="telephone=no" />
  7. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
  8. <link rel="stylesheet" href="css/bui.css" />
  9. </head>
  10. <body>
  11. <!-- 第1步: 开启单页路由 -->
  12. <div id="bui-router"></div>
  13. <script src="js/zepto.js"></script>
  14. <script src="js/bui.js"></script>
  15. <!-- 初始化单页 -->
  16. <script src="index.js"></script>
  17. </body>
  18. </html>

*src/index.js *

  1. // 把路由实例化给 window.router
  2. window.router = bui.router();
  3. bui.ready(function(){
  4. // 加载页面到div容器里面, 更多参数请查阅API
  5. router.init({
  6. id: "#bui-router"
  7. })
  8. })

当路由初始化以后,会自动查找首页main模块, 这个模块是内部定义好的, 默认指向路径 pages/main/main.html自动加载相同名字的js文件. 除了main, 正常我们都是创建匿名模块,这样只要通过路径跳转,就会自动加载相对路径同名的模块.

模块名: main

src/pages/main/main.html

  1. <div class="bui-page">
  2. <header class="bui-bar">
  3. <div class="bui-bar-left">
  4. </div>
  5. <div class="bui-bar-main">main</div>
  6. <div class="bui-bar-right">
  7. </div>
  8. </header>
  9. <main>
  10. <div id="btn" class="bui-btn">跳转page2.html</div>
  11. </main>
  12. <footer></footer>
  13. </div>

src/pages/main/main.js

  1. loader.define(function(require,exports,module){
  2. // 绑定按钮跳转
  3. $("#btn").on("click",function(){
  4. bui.load({ url: "pages/page2/page2.html", param: {} });
  5. })
  6. })

pages/page2/page2模块

我们再创建一个 src/pages/page2/page2.html,及 src/pages/page2/page2.js ,那通过上面的方式跳转,则创建了一个 src/pages/page2/page2 的模块.

src/pages/page2/page2.html

  1. <div class="bui-page">
  2. <header class="bui-bar">
  3. <div class="bui-bar-left">
  4. </div>
  5. <div class="bui-bar-main">page2</div>
  6. <div class="bui-bar-right">
  7. </div>
  8. </header>
  9. <main></main>
  10. <footer></footer>
  11. </div>

src/pages/page2/page2.js

  1. loader.define(function(require,exports,module){
  2. // 脚本都需要在这里执行
  3. })

注意:

  • 页面已经加载过,应该使用后退操作, 例如,上面的例子,在pages2.html,不要使用 router.load({url:”main”}) 这样跳回首页, 使用 router.back;

页面跳转

router.load(option)

页面跳转,保持js跟html一致的命名,才能自动加载触发. 同样支持 bui.btn 静态跳转

*参数: option是一个对象 *

option.url

  • Type: string
  • Detail: 相对路径

option.param

  • Type: object
  • Detail: 传给目标页面的参数

例子:

  1. router.load({ url: "pages/page2/page2.html", param: {} });
  2. // 路由初始化以后,下面的跳转也是单页跳转
  3. bui.load({ url: "pages/page2/page2.html", param: {} });

注意: 仔细查看下跟多页路由接口都是保持的一致,甚至你可以直接使用 bui.load 来代替 router.load , 这个也是最早我们推荐的方式, 所以你会看到部分例子还是保持这样的写法.

接收参数

router.getPageParams

接收到的参数为一个对象, 无需在回调里面拿.

例子:

  1. var params = router.getPageParams();

页面后退

router.back(option)

router.back 跟 bui.back 的区别在于, bui.back 后退会把历史记录一起后退,router.back 只是后退页面. 开发中推荐使用 bui.loadbui.back.

*参数: option是一个对象 *

option.index

  • Type: number
  • Detail: 后退几层,默认:-1

option.name

  • Type: string
  • Detail: 后退到指定的模块名, name跟index,只能有一个

option.callback

  • Type: function
  • Detail: 后退以后执行回调

例子:

  1. // 普通后退
  2. router.back();
  3. // 后退局部刷新
  4. router.back({
  5. callback: function(module){
  6. // 后退的页面有抛出一个init方法 或者刷新方法, 做内容修改
  7. module.init();
  8. }
  9. });
  10. // 后退2层刷新
  11. bui.back({
  12. index: -2,
  13. callback: function(){
  14. bui.refresh()
  15. }
  16. });
  17. // 不管在哪层,都可以后退到首页
  18. bui.back({
  19. name: "main"
  20. });

页面替换

router.replace(option)

页面替换不会增加历史记录

*参数: option是一个对象 *

option.url

  • Type: string
  • Detail: 相对路径

option.param

  • Type: object
  • Detail: 传给目标页面的参数

例子:

  1. router.replace({ url: "pages/page3/page3.html" });

局部加载模块

router.loadPart

*参数: option是一个对象 *

option.id

  • Type: string|object
  • Detail: 局部的id或者对象

option.url

  • Type: string
  • Detail: 相对路径

option.param

  • Type: object
  • Detail: 传给目标页面的参数

例子:

  1. router.loadPart({ id:"#part", url: "pages/page2/page2.html", param: {} });

局部加载在1.6.x 有更简单的方式, 使用 component标签, 便可自动加载.

  1. <component name="pages/page2/page2"></component>

局部接收参数

router.getPartParams(moduleName)

*参数: *

moduleName

  • Type: string
  • Detail: 模块的名称

例子:

  1. loader.define(function(require,exports,module) {
  2. // module 为当前的模块信息
  3. var pid = module.moduleName,
  4. params = router.getPartParams(pid);
  5. })

预加载

router.preload(option)

如果你的应用是webapp,那这个功能就比较好用了,先在首页预加载想要触发的页面, 当点击触发相同页面的时候, 你会发现跳转速度快多了, 本地应用则无需使用到预加载.

*参数: option是一个对象 *

option.url

  • Type: string
  • Detail: 缓存模板

option.script

  • Type: string
  • Detail: 缓存脚本
  1. // 预加载一个页面
  2. router.preload({ url: "pages/page2/page2.html" });
  3. // 预加载多个页面
  4. router.preload([{
  5. url: "pages/page2/page2.html"
  6. },{
  7. url: "pages/page3/page3.html"
  8. }]);

页面重复加载

页面重复加载的表现通常是, 渲染出不来,要刷新才有数据. 事件被重复绑定等.

使用 router.$ 替换 $ 选择器

router.$$的区别在于, router.$ 是相对于当前路由的页面查找, 有一种情况, 页面需要被重复加载, 比方列表页,跳转到详情页,详情页又有推荐的列表,点击又会跳转到详情, 这种时候,$绑定页面的事件会被重复绑定, router.$ 则不会.

*事件绑定示例: *

  1. // 静态的结构绑定事件
  2. router.$(".bui-page .btn").click(function(e){
  3. })

另外,建议一个单页应用的层级建议控制在 5级左右. 已经加载过的页面,使用后退刷新处理. 例如:

  1. // 推荐: 这也是微信小程序里面推荐的层级数
  2. A->B->C->D->E
  3. // 不要这样, 这种做法本身会让用户陷入混乱
  4. A->B->C->B->C

路由全局事件

load 事件

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

  1. router.on("load",function(e){
  2. // 获取当前页的模块
  3. console.log(e.target);
  4. // 获取上一页的模块
  5. console.log(e.prevTarget);
  6. })

complete 事件

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

  1. router.on("complete",function(e){
  2. // 获取当前页的模块
  3. console.log(e.target);
  4. // 获取上一页的模块
  5. console.log(e.prevTarget);
  6. })

back 事件

  1. router.on("back",function(e){
  2. // 获取当前页的模块
  3. console.log(e.target);
  4. // 获取上一页的模块
  5. console.log(e.prevTarget);
  6. })

refresh 事件

  1. router.on("refresh",function(e){
  2. // 获取当前页的模块
  3. console.log(e.target);
  4. // 获取上一页的模块
  5. console.log(e.prevTarget);
  6. })

loadpart 事件

页面局部加载后触发

  1. router.on("loadpart",function(e){
  2. // 获取当前页子模块
  3. console.log(e.target);
  4. // 获取当前页父模块
  5. console.log(e.prevTarget);
  6. })

注意: 建议全局事件都在 index.js 加载. 事件名全部为小写.

单页生命周期

路由原理

这是路由加载的完整过程.

BUI 路由模块加载流程图

路由加载路线说明

  • ① 为首页 main 的自定义模块加载, 已经配置了路径指向, 你可以通过 indexModule 参数修改main首页的指向;
  • ② 为没有自定义模块的时候, 点击后走相对根路径的同名文件模块;
  • ③ 为跳转已经加载过的模块;
  • ④ 为只有模板,没有同名脚本路径走的路线;

路线①: 自定义的main模块加载

BUI main模块的加载

路线②: 点击跳转的页面加载, 默认跳转的路径就是模块名

路线③: 已经加载过的页面,只需走loaded回调.

BUI 点击跳转的模块加载

路由加载注意事项

  1. 打开路由及跳转页面都会先加载模板,再加载脚本;
  2. 单页只需要在首页执行一次 bui.ready;
  3. 路由的初始化也必须在 bui.ready 里面执行;
  4. 模块里面的选择器使用 router.$ 替换 $ ,确保你操作的是当前页面的模块的dom;
  5. 如果需要修改 bui.loader 需要在 window.router 之前;
  6. 路由的全局事件, 必须在 index.js 才能使用, 不能放在单独的模块里.

生命周期

1.5.3 新增, 路由跳转页面会触发模块的加载, 从进入页面,跳转页面,后退页面完成一个页面的生命周期.

页面展示的生命周期

这是打开路由第一次加载 main模块以后触发的加载.

BUI 模块的加载会触发模块的生命周期

页面跳转后退之间的生命周期

BUI 模块的加载会触发模块的生命周期

一. 跳转:

1, main模块

① 未定义生命周期, 只执行 loaded .

2, main->A 页面

① A页面定义了生命周期, 从 beforeCreate -> created -> beforeLoad -> loaded -> show , 这是A页面的显示生命周期. ② 从main->第2次跳转到A页面的时候, 直接走 beforeLoad -> loaded -> show;

3, A页面->B页面, 会执行 A页面自身的 hide 再跳去 B页面的 ① beforeCreate -> created -> beforeLoad -> loaded -> show

二. 后退:

  1. B->A 页面 B跟A都有自己的生命周期, 后退的时候会先执行 当前页的hide ->beforeDestroy -> destroyed , 然后还会触发 A页面的 show 方法.

  2. A->main 页面 由于main没有定义show方法, 所以只执行A页面的 hide ->beforeDestroy -> destroyed

使用

具体查看模块的定义. 页面模块的生命周期

接下来你可以继续学习