BUI store 實(shí)例

2020-08-12 14:08 更新

在開(kāi)始之前,我們先來(lái)看看這個(gè)待辦處理的效果. 代碼里面只需做相關(guān)的綁定操作, 操作dom的行為交給行為屬性處理, 這個(gè)案例在綜合案例會(huì)有詳細(xì)說(shuō)明.

案例

案例圖片

1. 創(chuàng)建一個(gè)store實(shí)例.

  • el 掛載的根元素, 默認(rèn)是: .bui-page, 如果是在tab控件里面動(dòng)態(tài)加載(tab外層沒(méi)有header,里面有header,一般就會(huì)有2個(gè).bui-page), 修改el參數(shù)可以防止多次解析.
  • scope 數(shù)據(jù)的范圍, 必須字段, 比方公共數(shù)據(jù)可以使用app, 模塊里面的數(shù)據(jù)默認(rèn)使用 page
  • isPublic 是否是公共數(shù)據(jù), 默認(rèn): false | true
  • data 數(shù)據(jù)的定義, 結(jié)合行為屬性使用
  • watch 偵聽(tīng)器, 偵聽(tīng)data 里面的字段改變, 觸發(fā)當(dāng)前回調(diào)
  • computed 計(jì)算屬性, 結(jié)合 data 的字段的處理, 比方加減乘除
  • methods 自定義的方法, 通過(guò)行為屬性b-click訪問(wèn)
  • templates 模板的定義,通過(guò)行為屬性b-template訪問(wèn)
  • beforeMount 數(shù)據(jù)解析前執(zhí)行
  • mounted 數(shù)據(jù)解析后執(zhí)行

bui.store初始化, 單頁(yè)一般在loader.define 里面, 多頁(yè)在 bui.ready 里面.

loader.define(function(){


  var bs = bui.store({
      scope: "page",
      data: {},
      methods: {},
      watch: {},
      computed: {},
      templates: {},
      beforeMount: function(){
        // 數(shù)據(jù)解析前執(zhí)行
      },
      mounted: function(){
        // 數(shù)據(jù)解析后執(zhí)行
      }
    })
})

注意: 每個(gè)單獨(dú)的模塊里面都可以使用page, 如果作為局部加載的模塊, 則需要區(qū)分?jǐn)?shù)據(jù)源.

比方模塊A, 要把模塊B的數(shù)據(jù)加載到模塊A里面.

模塊A:

loader.define(function(){
  // 當(dāng)前數(shù)據(jù)源為 page
  var bs = bui.store({
      scope: "page",
      data: {},
      mounted: function(){
        // 加載模塊B
        router.loadPart({
          id: "#test",
          url: "模塊B.html"
        })
      }
    })
})

模塊B:

loader.define(function(){


  // 這里的數(shù)據(jù)源不能跟模塊A的數(shù)據(jù)源名字相同
  var bs = bui.store({
      scope: "pageB",
      data: {},
      mounted: function(){


      }
    })


    return bs;
})

2. 基本使用

實(shí)例的名字由你自己定義,這里我們整篇使用bs (behavior store)作為實(shí)例名. b 標(biāo)簽作為這個(gè)數(shù)據(jù)關(guān)聯(lián)的默認(rèn)標(biāo)簽.

var bs = bui.store({
  scope: "page",
  data: {
    size: 1
  }
});


// 設(shè)置以后就會(huì)觸發(fā)dom b-text="page.size"的視圖更新
bs.size = 2;

<b b-text="page.size"></b>

當(dāng)這些數(shù)據(jù)改變時(shí),視圖會(huì)進(jìn)行重渲染。綁定到模板里面的寫法是 page.xxx 而不是 bs.xxx .

注意: 新增的屬性不是響應(yīng)式的. 例如:

bs.number = 12;

在進(jìn)行視圖的設(shè)計(jì)的時(shí)候, 需要對(duì)這些值進(jìn)行初始值的設(shè)定, 自定義鍵值, 比如:

data : {
  str: '',
  num: 0,
  bool: false,
  lists: [],
}

需要注意的是, 如果你希望通過(guò) bs.lists = [1,2] 這種賦值操作來(lái)操作數(shù)組, bs.lists 的dom是不會(huì)進(jìn)行響應(yīng)的, 但你可以使用 bs.lists.push() 的方式, 或者使用 bui.array 的一些命令式方法, 來(lái)處理這些數(shù)組. 這個(gè)會(huì)在 模板渲染的章節(jié) 使用到.

1.5.3 以后可以直接修改 bs.lists.$set(0,222), 這樣會(huì)觸發(fā)視圖更新, 具體可以查看 bui.array.set 的使用

3. 加載的時(shí)機(jī)

?> 當(dāng)store初始化的時(shí)候, 會(huì)做兩件事情

  • 第1件事, 把當(dāng)前已有的數(shù)據(jù)進(jìn)行代理, 也就是vue使用的 Object.define來(lái)處理 data,watch,computed 這些數(shù)據(jù)掛載到 store實(shí)例本身;
  • 第2件事, 把模板進(jìn)行匹配過(guò)濾, 找到對(duì)應(yīng)的選擇器.

在這兩件事前后, 會(huì)分別執(zhí)行beforeMount, mounted 方法. 所以一般業(yè)務(wù)都應(yīng)該在 mounted 里面執(zhí)行.

而頁(yè)面的生命周期, 其實(shí)是在模塊里面的, 通過(guò)路由的跳轉(zhuǎn)執(zhí)行模塊的生命周期, 很多時(shí)候我們都無(wú)需關(guān)注, 我們也僅僅是提供了最簡(jiǎn)單的使用方式.

4. 動(dòng)態(tài)加載時(shí)機(jī)

我們看到上面的數(shù)據(jù)都是靜態(tài)數(shù)據(jù), 一開(kāi)始數(shù)據(jù)是有初始值的, 這樣是最好的, 但有時(shí)候我們還有動(dòng)態(tài)數(shù)據(jù), 需要通過(guò)請(qǐng)求以后才能加載進(jìn)來(lái), 這種又該如何處理呢?

var bs = bui.store({
    scope: "page",
    data: {
        list: [],
    },
    templates: {
        tplList: function (data) {
            var html = "";
            data.forEach(function (item,i) {
                html += `<li class="bui-btn">${item}</li>`;
            })
            return html;
        }
    },
    mounted: function () {
        // 模擬數(shù)據(jù)動(dòng)態(tài)改變
        setTimeout(()=>{


            // 方法1:
            // this.list.push("廣州","深圳","上海","北京");


            // 方法2: 合并并觸發(fā) this.list 的視圖更新
            bui.array.merge(this.list,["廣州","深圳","上海","北京"])


        },1000)
    }
})

<ul b-template="page.tplList(page.list)"></ul>

一般請(qǐng)求得到的是一個(gè)數(shù)組, 所以建議可以使用 bui.array.merge 合并數(shù)據(jù). 這樣視圖就能得到更新.

再?gòu)?fù)雜一點(diǎn), 我們需要對(duì)數(shù)據(jù)進(jìn)行解析, 比方模板里面,包含 b-model, 需要雙向綁定, 那么我們需要調(diào)用 this.compile 方法, 而這個(gè)方法,是需要在 dom 渲染完成以后才能處理的, 這就要用到 this.oneTick 或者 this.nextTick 來(lái)處理, 并且需要在數(shù)據(jù)更新之前調(diào)用.

...
mounted: function () {
    // 模擬數(shù)據(jù)動(dòng)態(tài)改變
    setTimeout(()=>{
        this.oneTick("citys",function () {
            this.compile("#test")
        })


        bui.array.merge(this.list,["廣州","深圳","上海","北京"])


    },1000)
}
...

如果首頁(yè)的tab,要異步加載公共模板在tab1里,則需要在tab1里執(zhí)行一次 this.compile("#id"), id 為當(dāng)前tab的樣式或者id名 .

5. oneTick 與 nextTick 的區(qū)別.

這兩個(gè)方法, 都是在 dom 渲染以后執(zhí)行, 不同的是:

  • oneTick 只在某個(gè)字段更新,并且視圖渲染以后的才會(huì)觸發(fā), 并且同個(gè)字段只監(jiān)聽(tīng)一次.
  • nextTick 是多字段, 不管哪個(gè)字段更新,只要觸發(fā)了視圖更新, 都會(huì)執(zhí)行一次, 造成重復(fù)渲染, 重復(fù)調(diào)用還會(huì)造成重復(fù)的監(jiān)聽(tīng).

!> 特別是在watch監(jiān)聽(tīng)的時(shí)候, 千萬(wàn)不要使用 nextTick. 一般是在 mounted 使用.

6. this.xxx 跟 this.$data.xxx 有什么區(qū)別?

this.xxx === this.$data.xxx; 對(duì)于data里面的字段來(lái)說(shuō), 這2個(gè)值是完全相等的. 那他們之間的區(qū)別在哪里? bui.store通過(guò)Object.defineProperty劫持對(duì)象的讀取或者設(shè)置獲得字段, 通過(guò)訂閱來(lái)響應(yīng)頁(yè)面上的DOM行為, 他們之間會(huì)有很多種組合, 最常見(jiàn)的一種情況是, 容易導(dǎo)致字段更新以后, 頁(yè)面沒(méi)有同時(shí)響應(yīng). 通過(guò)log:true可以看到字段讀取的順序.(如果只有1層數(shù)據(jù),則沒(méi)有這個(gè)問(wèn)題.)

解決這個(gè)頁(yè)面不響應(yīng)的問(wèn)題也很簡(jiǎn)單, 就是規(guī)定使用 this.xxx 用于設(shè)置; 在設(shè)置前, 數(shù)據(jù)的其它獲取,計(jì)算,比對(duì)等操作, 需要通過(guò) this.$data.xxx 去處理. 特別是多層級(jí)的設(shè)置. 如果字段層級(jí)較深, 可以使用 this.set("xx.xx.xx",123), 確保能夠正確觸發(fā)視圖更新.

beforeMount 里面的數(shù)據(jù)操作, 需要使用 this.$data.xxx

var bs = bui.store({
  data: {
    a: {
      b: 234
    },
    c: {
      d: 345
    }
  },
  beforeMount: function(){
    // 獲取頁(yè)面參數(shù)
    var pageParams = router.getPageParams();
    // 在beforeMount 只能通過(guò) this.$data.xx = xxx 這樣去操作. 
    this.$data.a.b = pageParams.id;
  },
  mounted: function(){
    // 判斷或者比對(duì),使用這種 this.$data.xxx
    if( this.$data.c.d == 345) {
      // 設(shè)置使用這種 this.xxx 
      this.a.b = 123;
    }
  }
})

如果data里面的值是數(shù)組的操作, bui.array.index, bui.array.indexs, bui.array.compare, bui.array.filter, bui.array.get, bui.array.getAll 取值,比對(duì),索引等方法, 應(yīng)該使用 this.$data.xxx 作為參數(shù). 如果是賦值修改操作 bui.array.empty, bui.array.replace, bui.array.merge, bui.array.set, bui.array.delete, bui.array.remove , 應(yīng)該使用 this.xxx 作為參數(shù).

如果在computed的計(jì)算,也是需要直接使用 this.xxx 去讀取才會(huì)觸發(fā) computed 的計(jì)算的.

7. 公共數(shù)據(jù)與私有數(shù)據(jù)

這是bui.store獨(dú)特的地方, 我們通過(guò)scope來(lái)區(qū)分?jǐn)?shù)據(jù)源, 再加上isPublic:true這個(gè)參數(shù), 這樣在index.js初始化以后, 所有的單頁(yè)頁(yè)面都可以拿到這個(gè)公共數(shù)據(jù), 當(dāng)公共數(shù)據(jù)改變的時(shí)候, 多個(gè)頁(yè)面的數(shù)據(jù)視圖都會(huì)重新渲染.

window.router = bui.router();


bui.ready(function() {


    // 公共數(shù)據(jù)
    window.store = bui.store({
        scope: "app",
        isPublic: true,
        data: {
            firstName: "Hello",
            lastName: "BUI"
        }
    })    


    // 初始化路由
    router.init({
        id: "#bui-router",
        progress: true,
        hash: true,
        store: store,
    })
})

<b b-text="app.firstName"></b>

如上面例子: store.firstName="Bingo" 的時(shí)候, 所有單頁(yè)頁(yè)面上有<b b-text="app.firstName"></b> 進(jìn)行渲染的模板,都會(huì)一起改變.

store掛載到路由, 還可以解析公共數(shù)據(jù)的 {{app.firstName}} 之類的數(shù)據(jù)(只渲染一次), 在模塊里面,你也可以使用 store.firstName 讀取跟修改公共數(shù)據(jù)的值, 會(huì)更新頁(yè)面相關(guān)數(shù)據(jù)的視圖.

8. Tab子模塊加載公共數(shù)據(jù)

如果是在tab 里面要加載公共數(shù)據(jù)的模板解析的話, 需要執(zhí)行多一次 store.compile(".tab-news") ;

例如:

index.js 公共數(shù)據(jù)的示例數(shù)據(jù)

window.store = bui.store({
      scope: "app",
      isPublic: true,
      data: {
          list: [{
            id: "news1",
            title: "新聞標(biāo)題1"
          },{
            id: "news2",
            title: "新聞標(biāo)題1"
          }]
      },
      templates: {
        tplList: function(data){
          var html = "";
          data.forEach(function(item,index){
            html +=`<li class="bui-btn">${item.title}</li>`
          })


          return html;
        }
      }
  })

tab模塊的結(jié)構(gòu)及腳本.

<div class="tab-news">
  <ul class="bui-list" b-template="app.tplList(app.list)"></ul>
</div>

loader.define(function(){
  // 必須執(zhí)行一次
  store.compile(".tab-news");
})

因?yàn)閠ab異步加載一個(gè)模塊的時(shí)候, html模板還沒(méi)有渲染完畢, 但store已經(jīng)處理完, 所以需要告訴store 還有哪個(gè)模板需要解析. 如果不是tab 則不用.

9. 常用方法

請(qǐng)查看bui.store API

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)