1. 程式人生 > >微信小程式:元件實踐

微信小程式:元件實踐

前階段有網友問了一個關於小程式元件的問題:

有一個列表,我點選編輯,出現一個彈窗修改元件,輸入修改數值點選確定,對應的列表項的值就被更改過來,點選取消,不做任何操作。

據說能獲取到文字框的值,可是列表值一直更改不了。我就抽空寫了個demo發現並未出現此問題,因此萌生了寫這篇文章。

那麼什麼是元件,為什麼要用元件。相信不用我多說大家都很清楚,以前每個html靜態頁的頭部和底部都得重複寫,自從有了元件概念出現,再也不用做這些重複性工作了,開發效率是大大的提升,而且稍微有改動,只需改動一個地方。

那麼,微信小程式的元件該如何使用呢?
假設我們的專案中有個元件B【包含B.js、B.json、B.wxml、B.wxss】,頁面A【包含A.js、A.json、A.wxml、A.wxss】,結構如下:
這裡寫圖片描述

首先,我們要了解下元件的一些基礎特性以及用法。
1、想要使用元件,需先在B.json配置定義當前資料夾目錄為元件目錄模板:

{
  "component": true
}

然後在A.json做如下配置,引入元件模板B:

{
  "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"//component-tag-name是A.wxml要引入自定義的元件節點名稱,我們需遵循這樣的命名規則
  }
}

2、元件模板B.wxml:
wxml元件頁和一般的wxml頁面語法基本相同,有個比較特殊地方是元件wxml頁可以使用slot插槽,定義了slot節點後,頁面上引用的元件節點內部填寫的內容就會展示在slot插槽的位置上,預設一個元件只能有一個slot插槽,想要使用多個需要額外配置,具體參考

官方文件

<!-- 元件模板 -->
<view class="wrapper">
  <view>這裡是元件的內部節點</view>
  <slot><!--外部文字會顯示在這裡--></slot>
</view>

然後在A.wxml引用元件B

<!-- 引用元件的頁面模版 -->
<view>
  <component-tag-name title="我是A傳遞的引數" id="BB" test-a="{{a}}" data-test-b="{{b}}">
<!-- 這部分內容將被放置在元件 <slot> 的位置上 --> <view>這裡是插入到元件slot中的內容</view> </component-tag-name> </view>

3、B.wxss元件樣式頁:
寫法基本和普通wxss頁的沒什麼差別,只是有些需要注意的地方,例如不能使用id選擇器、子元素選擇器、元素選擇器等,請自行檢視官網

4、B.js元件邏輯頁Component構造器:

屬性 型別 是否必填 描述
properties Object Map 元件的對外屬性,是屬性名到屬性設定的對映表,屬性設定中可包含三個欄位, type 表示屬性型別、 value 表示屬性初始值、 observer 表示屬性值被更改時的響應函式[簡單的說,就是從A頁面傳遞引數給B元件]
data Object 元件的內部資料,和 properties 一同用於元件的模版渲染
methods Object 元件的方法,包括事件響應函式和任意的自定義方法,關於事件響應函式的使用,參見官網 元件事件
behaviors String Array 類似於mixins和traits的元件間程式碼複用機制,參見 behaviors
created Function 元件生命週期函式,在元件例項進入頁面節點樹時執行,注意此時不能呼叫 setData
attached Function 元件生命週期函式,在元件例項進入頁面節點樹時執行
ready Function 元件生命週期函式,在元件佈局完成後執行,此時可以獲取節點資訊(使用 SelectorQuery )
moved Function 元件生命週期函式,在元件例項被移動到節點樹另一個位置時執行
detached Function 元件生命週期函式,在元件例項被從頁面節點樹移除時執行
relations Object 元件間關係定義,參見 元件間關係
options Object Map 一些元件選項,請參見文件其他部分的說明

其他框架我不瞭解,但是最近有使用vue開發過專案,我覺得微信小程式的元件系統和vue的元件系統非常類似,平常的元件架構基本使用properties、data、attached、methods就夠了。

這裡寫圖片描述
4-1、對於created和attached生命週期有點小疑惑,為什麼兩個描述都是一樣的呢?於是做了個小測試。

created生命週期
created生命週期不能使用setData。但是我列印this.data可以得到properties和data中定義的資料。我嘗試呼叫setData並設定引數值,確實會報錯,但是如果我不設定數值函式又正常執行,只不過返回了undefined。然後列印了下this物件,可以看到建構函式都已經存在。由此是否可以猜測此生命週期元件已經例項化,而wxml節點樹還未例項導致的。那麼,在此生命週期中到底能處理什麼事務呢?目前還是想不通。
attached生命週期
我在當前生命週期直接執行setData修改properties屬性,發現wxml上的資料已經得到修改,由此是否可以猜測此生命週期中,例項化已經結束,節點樹也例項了。因此才能時時渲染到頁面上了。也就是說可以在此週期中獲取資料並setData到data物件上,進而渲染到節點中。那麼到底能不能操作節點呢?為了證實這個,我在attached和ready分別執行了下面的方法。
var query = wx.createSelectorQuery()
query.select('#BB').boundingClientRect()
query.selectViewport().scrollOffset()
query.exec(function (res) {
  console.log(res)
})

最終發現ready中第一個陣列有返回自定義元件的一些基礎資訊,比如id、dataset、width等。而attached中第一個陣列是null。那麼是否可以進一步猜測,在attached中是例項了節點樹而還未渲染到頁面上?當然這一切還都是我自己猜測的,如果有得出不同的結論歡迎告知下,感謝感謝~

4-2、properties和dataset傳參觀察
通過官網可以得知從A頁面傳遞引數給B元件有兩種方式,一種是properties,另外一種是dataset方式。那麼這兩個有什麼區別呢?
於是我在頁面上分別傳入test-a和data-test-b兩個引數,然後在B.js的created分別列印this,觀察到properties屬性都會被重新賦值到data物件上,而data物件上的引數在properties也能找到,而dataset並未出現在這兩個屬性中,也就是說這兩個是共用一個引用地址。而dataset是單獨一個引用地址。
這裡寫圖片描述

有趣的是,我在該階段測試的時候,嘗試在created生命週期列印dataset的值,竟然報錯了,於是我又列印this.dataset,觀察到該屬性在此生命週期其實還未建立呢。而列印this.data並未出現該情況,但是我又嘗試列印this.data.testA的值,發現也是null。而在attached並未有次狀況,因此可以大膽猜測created其實只是初始化了元件例項,而接受父容器引數什麼的都還未開始進行。那麼created生命週期到底能處理些什麼呢,名存實亡的感覺??

因此,我建議:如果不是要渲染到元件節點樹上的單純只是在邏輯層進行邏輯運算的,可以通過dataset方式傳入。而需要渲染到節點樹的則可以直接通過properties傳遞,這樣就避免再一次在attached中setData。儘量初始化事務都在attached生命週期處理比較好。

4-3、元件事件
頁面和元件之間的互動主要就是通過事件系統。自定義元件觸發事件時,需要使用 triggerEvent 方法,指定事件名、detail物件和事件選項! 那麼具體怎麼使用事件系統。

我們在頁面A.wxml引入自定義元件B節點名

<view bindtap="mappao">
<!-- 當自定義元件觸發名為“myevent”的事件時,呼叫“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以寫成 -->
<component-tag-name bind:myevent="onMyEvent" />
</view>

然後在頁面A.js寫好自定義事件onMyEvent

maopao(){
console.log('mao pao le.')
},
onMyEvent: function(e){
  e.detail // 自定義元件觸發事件時提供的detail物件
}

然後元件B.wxml寫入:

<!-- 在自定義元件中 -->
<button bindtap="onTap">點選這個按鈕將觸發“myevent”事件</button>

在元件B.js寫入:

Component({
  properties: {}
  methods: {
    onTap: function(){
      var myEventDetail = {} // detail物件,提供給事件監聽函式
      var myEventOption = {} // 觸發事件的選項
      this.triggerEvent('myevent', myEventDetail, myEventOption);//通過triggerEvent指定A頁面中的myevent是見面,從而觸發頁面A中的onMyEvent自定義事件
    }
  }
})

myEventOption屬性控制這裡就不做討論了,為了防止冒泡之類的不可預期的事件產生,我都是直接用catch來繫結事件的。不知道為什麼需要這麼多引數控制,經測試後,發現只要是使用bind繫結的事件,不管有沒有設定引數控制都會冒泡到引用元件的父節點樹的事件上的,而且多層元件巢狀的話,最裡層冒泡到引用元件的父容器上就會報錯。不知道是否是個bug?經官方證實了,這不是bug。(tap事件是全域性冒泡的(bubbles: true, composed: true),想要阻止冒泡,請使用catchtap。—-來自官方 @LastLeaf )的回答。【測試榮耀9安卓版微信版本號6.6.1、開發者工具v1.02.1801081、基礎框架1.9.1】

我做了如下測試:
頁面A:

<!-- 引用元件的頁面模版 -->
<view bindtap="maopao">
  // 頁面 page.wxml
<another-component bindcustomevent="pageEventListener1">
  <view>點C我試試啊</view>
  <my-component bindcustomevent="pageEventListener2"><view>點D我試試</view></my-component>
</another-component>
</view>
Page({
  data: {
  },
  pageEventListener2(e){
    console.log('pageEventListener2');
  },
  pageEventListener1(e){
    console.log('pageEventListener1')
  },
  maopao(e){
    console.log('mao pao le.')
  }
})

元件C

// 元件 another-component.wxml
<view bindtap="anotherEventListener">
  <slot />
</view>
Component({
methods: {
  anotherEventListener: function () {
    console.log('click c');
  }
}
})

元件D

// 元件 my-component.wxml
<view bindtap="myEventListener">
  <slot />
</view>
Component({
methods: {
  myEventListener: function () {
    console.log('click D');
    this.triggerEvent('customevent', {}) // 只會觸發 pageEventListener2
    this.triggerEvent('customevent', {}, { bubbles: true }) // 會依次觸發 pageEventListener2 、 pageEventListener1
    //this.triggerEvent('customevent', {}, { bubbles: false, composed: false, capturePhase: false }) // 會依次觸發 pageEventListener2 、 anotherEventListener 、 pageEventListener1
  }
}
})

演示如下:
這裡寫圖片描述

我如果把頁面A引用元件的父容器maopao事件去掉就不會有這些情況;
或者是元件模板上改為catch繫結事件也不會有這類情況。因此是否可以大膽猜測官方當時設計這個的初衷並未考慮會在頁面中用view等父容器包含元件的情況下表現的呢?不管怎麼說元件內部用catch繫結事件總不會錯的。這是個好習慣O(∩_∩)O~

behaviors和元件關係並未做深入研究,大致看了下官方文件,能做的事情太多了,這個具體還得結合專案來研究除錯比較好。(其實是這貨自己也不懂!!哭唧唧T^T)

以上是我自己對小程式元件的一些見解,如有不同的見解歡迎指出。祝食用愉快,感謝閱讀~