实例

在开始之前,我们先来看看这个待办处理的效果. 代码里面只需做相关的绑定操作, 操作dom的行为交给行为属性处理, 这个案例在综合案例会有详细说明.

1. 创建一个store实例.

  • el 挂载的根元素, 默认是: .bui-page, 如果是在tab控件里面动态加载(tab外层没有header,里面有header,一般就会有2个.bui-page), 修改el参数可以防止多次解析.
  • scope 数据的范围, 必须字段, 比方公共数据可以使用app, 模块里面的数据默认使用 page
  • isPublic 是否是公共数据, 默认: false | true
  • data 数据的定义, 结合行为属性使用
  • watch 侦听器, 侦听data 里面的字段改变, 触发当前回调
  • computed 计算属性, 结合 data 的字段的处理, 比方加减乘除
  • methods 自定义的方法, 通过行为属性b-click访问
  • templates 模板的定义,通过行为属性b-template访问
  • beforeMount 数据解析前执行
  • mounted 数据解析后执行

bui.store初始化, 单页一般在loader.define 里面, 多页在 bui.ready 里面.

  1. loader.define(function(){
  2. var bs = bui.store({
  3. scope: "page",
  4. data: {},
  5. methods: {},
  6. watch: {},
  7. computed: {},
  8. templates: {},
  9. beforeMount: function(){
  10. // 数据解析前执行
  11. },
  12. mounted: function(){
  13. // 数据解析后执行
  14. }
  15. })
  16. })

注意: 每个单独的模块里面都可以使用page, 如果作为局部加载的模块, 则需要区分数据源.

比方模块A, 要把模块B的数据加载到模块A里面.

模块A:

  1. loader.define(function(){
  2. // 当前数据源为 page
  3. var bs = bui.store({
  4. scope: "page",
  5. data: {},
  6. mounted: function(){
  7. // 加载模块B
  8. router.loadPart({
  9. id: "#test",
  10. url: "模块B.html"
  11. })
  12. }
  13. })
  14. })

模块B:

  1. loader.define(function(){
  2. // 这里的数据源不能跟模块A的数据源名字相同
  3. var bs = bui.store({
  4. scope: "pageB",
  5. data: {},
  6. mounted: function(){
  7. }
  8. })
  9. return bs;
  10. })

2. 基本使用

实例的名字由你自己定义,这里我们整篇使用bs (behavior store)作为实例名. b 标签作为这个数据关联的默认标签.

  1. var bs = bui.store({
  2. scope: "page",
  3. data: {
  4. size: 1
  5. }
  6. });
  7. // 设置以后就会触发dom b-text="page.size"的视图更新
  8. bs.size = 2;
  1. <b b-text="page.size"></b>

当这些数据改变时,视图会进行重渲染。绑定到模板里面的写法是 page.xxx 而不是 bs.xxx .

注意: 新增的属性不是响应式的. 例如:

  1. bs.number = 12;

在进行视图的设计的时候, 需要对这些值进行初始值的设定, 自定义键值, 比如:

  1. data : {
  2. str: '',
  3. num: 0,
  4. bool: false,
  5. lists: [],
  6. }

需要注意的是, 如果你希望通过 bs.lists = [1,2] 这种赋值操作来操作数组, bs.lists 的dom是不会进行响应的, 但你可以使用 bs.lists.push() 的方式, 或者使用 bui.array 的一些命令式方法, 来处理这些数组. 这个会在 模板渲染的章节 使用到.

1.5.3 以后可以直接修改 bs.lists.$set(0,222), 这样会触发视图更新, 具体可以查看 bui.array.set 的使用

3. 加载的时机

当store初始化的时候, 会做两件事情

  • 第1件事, 把当前已有的数据进行代理, 也就是vue使用的 Object.define来处理 data,watch,computed 这些数据挂载到 store实例本身;
  • 第2件事, 把模板进行匹配过滤, 找到对应的选择器.

在这两件事前后, 会分别执行beforeMount, mounted 方法. 所以一般业务都应该在 mounted 里面执行.

而页面的生命周期, 其实是在模块里面的, 通过路由的跳转执行模块的生命周期, 很多时候我们都无需关注, 我们也仅仅是提供了最简单的使用方式.

4. 动态加载时机

我们看到上面的数据都是静态数据, 一开始数据是有初始值的, 这样是最好的, 但有时候我们还有动态数据, 需要通过请求以后才能加载进来, 这种又该如何处理呢?

  1. var bs = bui.store({
  2. scope: "page",
  3. data: {
  4. list: [],
  5. },
  6. templates: {
  7. tplList: function (data) {
  8. var html = "";
  9. data.forEach(function (item,i) {
  10. html += `<li class="bui-btn">${item}</li>`;
  11. })
  12. return html;
  13. }
  14. },
  15. mounted: function () {
  16. // 模拟数据动态改变
  17. setTimeout(()=>{
  18. // 方法1:
  19. // this.list.push("广州","深圳","上海","北京");
  20. // 方法2: 合并并触发 this.list 的视图更新
  21. bui.array.merge(this.list,["广州","深圳","上海","北京"])
  22. },1000)
  23. }
  24. })
  1. <ul b-template="page.tplList(page.list)"></ul>

一般请求得到的是一个数组, 所以建议可以使用 bui.array.merge 合并数据. 这样视图就能得到更新.

再复杂一点, 我们需要对数据进行解析, 比方模板里面,包含 b-model, 需要双向绑定, 那么我们需要调用 this.compile 方法, 而这个方法,是需要在 dom 渲染完成以后才能处理的, 这就要用到 this.oneTick 或者 this.nextTick 来处理, 并且需要在数据更新之前调用.

  1. ...
  2. mounted: function () {
  3. // 模拟数据动态改变
  4. setTimeout(()=>{
  5. this.oneTick("citys",function () {
  6. this.compile("#test")
  7. })
  8. bui.array.merge(this.list,["广州","深圳","上海","北京"])
  9. },1000)
  10. }
  11. ...

如果首页的tab,要异步加载公共模板在tab1里,则需要在tab1里执行一次 this.compile("#id"), id 为当前tab的样式或者id名 .

5. oneTick 与 nextTick 的区别.

这两个方法, 都是在 dom 渲染以后执行, 不同的是:

  • oneTick 只在某个字段更新,并且视图渲染以后的才会触发, 并且同个字段只监听一次.
  • nextTick 是多字段, 不管哪个字段更新,只要触发了视图更新, 都会执行一次, 造成重复渲染, 重复调用还会造成重复的监听.

特别是在watch监听的时候, 千万不要使用 nextTick. 一般是在 mounted 使用.

6. this.xxx 跟 this.$data.xxx 有什么区别?

this.xxx === this.$data.xxx; 对于data里面的字段来说, 这2个值是完全相等的. 那他们之间的区别在哪里? bui.store通过Object.defineProperty劫持对象的读取或者设置获得字段, 通过订阅来响应页面上的DOM行为, 他们之间会有很多种组合, 最常见的一种情况是, 容易导致字段更新以后, 页面没有同时响应. 通过log:true可以看到字段读取的顺序.(如果只有1层数据,则没有这个问题.)

解决这个页面不响应的问题也很简单, 就是规定使用 this.xxx 用于设置; 在设置前, 数据的其它获取,计算,比对等操作, 需要通过 this.$data.xxx 去处理. 特别是多层级的设置. 如果字段层级较深, 可以使用 this.set("xx.xx.xx",123), 确保能够正确触发视图更新.

beforeMount 里面的数据操作, 需要使用 this.$data.xxx

  1. var bs = bui.store({
  2. data: {
  3. a: {
  4. b: 234
  5. },
  6. c: {
  7. d: 345
  8. }
  9. },
  10. beforeMount: function(){
  11. // 获取页面参数
  12. var pageParams = router.getPageParams();
  13. // 在beforeMount 只能通过 this.$data.xx = xxx 这样去操作.
  14. this.$data.a.b = pageParams.id;
  15. },
  16. mounted: function(){
  17. // 判断或者比对,使用这种 this.$data.xxx
  18. if( this.$data.c.d == 345) {
  19. // 设置使用这种 this.xxx
  20. this.a.b = 123;
  21. }
  22. }
  23. })

如果data里面的值是数组的操作, bui.array.index, bui.array.indexs, bui.array.compare, bui.array.filter, bui.array.get, bui.array.getAll 取值,比对,索引等方法, 应该使用 this.$data.xxx 作为参数. 如果是赋值修改操作 bui.array.empty, bui.array.replace, bui.array.merge, bui.array.set, bui.array.delete, bui.array.remove , 应该使用 this.xxx 作为参数.

如果在computed的计算,也是需要直接使用 this.xxx 去读取才会触发 computed 的计算的.

7. 公共数据与私有数据

这是bui.store独特的地方, 我们通过scope来区分数据源, 再加上isPublic:true这个参数, 这样在index.js初始化以后, 所有的单页页面都可以拿到这个公共数据, 当公共数据改变的时候, 多个页面的数据视图都会重新渲染.

  1. window.router = bui.router();
  2. bui.ready(function() {
  3. // 公共数据
  4. window.store = bui.store({
  5. scope: "app",
  6. isPublic: true,
  7. data: {
  8. firstName: "Hello",
  9. lastName: "BUI"
  10. }
  11. })
  12. // 初始化路由
  13. router.init({
  14. id: "#bui-router",
  15. progress: true,
  16. hash: true,
  17. store: store,
  18. })
  19. })
  1. <b b-text="app.firstName"></b>

如上面例子: store.firstName="Bingo" 的时候, 所有单页页面上有<b b-text="app.firstName"></b> 进行渲染的模板,都会一起改变.

store挂载到路由, 还可以解析公共数据的 {{app.firstName}} 之类的数据(只渲染一次), 在模块里面,你也可以使用 store.firstName 读取跟修改公共数据的值, 会更新页面相关数据的视图.

8. Tab子模块加载公共数据

如果是在tab 里面要加载公共数据的模板解析的话, 需要执行多一次 store.compile(".tab-news") ;

例如:

index.js 公共数据的示例数据

  1. window.store = bui.store({
  2. scope: "app",
  3. isPublic: true,
  4. data: {
  5. list: [{
  6. id: "news1",
  7. title: "新闻标题1"
  8. },{
  9. id: "news2",
  10. title: "新闻标题1"
  11. }]
  12. },
  13. templates: {
  14. tplList: function(data){
  15. var html = "";
  16. data.forEach(function(item,index){
  17. html +=`<li class="bui-btn">${item.title}</li>`
  18. })
  19. return html;
  20. }
  21. }
  22. })

tab模块的结构及脚本.

  1. <div class="tab-news">
  2. <ul class="bui-list" b-template="app.tplList(app.list)"></ul>
  3. </div>
  1. loader.define(function(){
  2. // 必须执行一次
  3. store.compile(".tab-news");
  4. })

因为tab异步加载一个模块的时候, html模板还没有渲染完毕, 但store已经处理完, 所以需要告诉store 还有哪个模板需要解析. 如果不是tab 则不用.

9. 常用方法

请查看bui.store API