本節書摘來自華章出版社《微信小程式:開發入門及案例詳解》一 書中的第2章,第2.4節,作者李駿 邊思,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
小程式中一個架構頁面包含4個檔案,同一架構頁面的這4個檔案必須具有相同的路徑與檔案名,進入小程式時或頁面跳轉時,小程式會根據app.json配置的路徑找到對應的資源進行渲染。
◇ .js檔案:頁面邏輯檔案,必要項。
◇ .wxml檔案:頁面結構檔案,必要項。
◇ .wxss檔案:頁面樣式檔案。
◇ .json檔案:頁面配置檔案。
與架構主體檔案相比架構頁面檔案多了一種頁面結構檔案,其餘3個檔案和架構主體檔案的功能類同,下面我們一一講解每個檔案作用。
我們首先講解頁面配置檔案,頁面配置檔案和架構配置檔案一樣,是一個json檔案,與架構配置檔案不同的是,頁面配置檔案是非必要存在的,同時頁面配置檔案的配置項隻有window,控制目前頁面的視窗表現,window的屬性和app.json一緻。渲染頁面時,頁面中的window配置項會覆寫app.json中的相同配置項。
由于頁面的.json隻能配置window相關屬性,編寫時隻需直接寫出屬性,不用寫window這個鍵,如下所示:
頁面邏輯檔案主要功能有:設定初始化資料,注冊目前頁面生命周期函數,注冊事件處理函數等。小程式的邏輯層檔案是javascript檔案,所有的邏輯檔案,包括app.js,最終将會打包成一個js檔案,在小程式啟動時運作,直到小程式銷毀,類似于serviceworker,是以邏輯層也稱為app service。
在小程式中,每個邏輯檔案有獨立的作用域,并具備子產品化能力。由于javascript邏輯檔案是運作在純javascript引擎中而并非運作在浏覽器中,一些浏覽器提供的特有對象,如document、window等,在小程式中都無法使用,同理,一些基于document、window的架構如:jquery、zepto都不能在小程式中使用,同時我們不能通過操作dom改變頁面,這時需要我們将面向dom操作的程式設計思路轉化為資料綁定和事件響應。
在頁面邏輯檔案中需要通過page()函數注冊頁面,指定頁面的初始資料、生命周期函數、事件處理函數等,參數為一個object對象,其屬性如下:
◆ data:頁面的初始資料,資料格式必須是可轉成json格式的對象類型。當頁面第一次渲染時,data會以json的形式由邏輯層傳至渲染層,渲染層可以通過wxml對資料進行綁定。
◆ onload:生命周期函數,頁面加載時觸發。一個頁面隻會調用一次,接受頁面參數,可以擷取wx.navigateto、wx.redirectto以及中的query參數。
◆ onshow:生命周期函數,頁面顯示時觸發。每次打開頁面都會調用一次。
◆ onready:生命周期函數,頁面初次渲染完成時觸發。一個頁面生命周期中隻會調用一次,代表目前頁面已經準備妥當,可以和視圖層進行互動。一些對界面的設定,操作需要在頁面準備妥當後調用,如wx.setnavigationbartitle需要在onready之後設定,詳細可以參考後面的“頁面生命周期”小節。
◆ onhide:生命周期函數,頁面隐藏時觸發。
◆ onunload:生命周期函數,頁面解除安裝時觸發。
◆ onpulldownrefresh:頁面相關時間處理函數,使用者下拉時觸發。使用時需要将app.json配置中window的enablepulldownrefresh屬性設定為true。當處理完資料重新整理後,可以調用wx.stoppulldownrefresh方法停止目前頁面的下拉重新整理。
◆ onreachbottom:頁面上拉觸底事件的處理函數。
◆ 其他:開發者可以添加任意的函數或資料到object參數中,可以用this通路這些函數和資料。示例代碼如代碼清單2-2所示:
代碼清單2-2 頁面邏輯檔案
頁面的生命周期函數比小程式的生命周期函數略微複雜一點,弄懂其執行順序能避免在不恰當的生命周期函數中調用還未建立的對象或方法,小程式架構以棧的形式維護了目前的所有頁面,當發生路由切換時,頁面棧和生命周期函數的關系如下:
◆ 小程式初始化:預設頁面入棧,依次觸發預設頁面onload、onshow、onready方法。
◆ 打開新頁面:新頁面入棧,依次觸發新頁面onload、onshow、onready方法。
◆ 頁面重定向:目前頁面出棧并解除安裝,觸發目前頁面onunload方法,新頁面入棧,觸發新頁面onload、onshow、onready方法。
◆ 頁面傳回:頁面不斷出棧并解除安裝,觸發目前彈出頁面onunload方法,直到傳回目标頁面,新頁面入棧,觸發新頁面onshow方法。
◆ tab切換:目前頁面出棧但不解除安裝,僅觸發onhide方法,新頁面入棧,如果目前頁面是新加載的,觸發onload、onshow、onready方法,如果目前頁面已加載過,僅觸發onshow方法。
◆ 程式從前台到背景:觸發目前頁面onhide方法,觸發app onhide方法。
◆ 程式從背景到前台:觸發小程式onshow方法,觸發頁面onshow方法。
整體來說,如果頁面在生命周期中,隻會觸發onshow和onload方法,隻有加載和解除安裝時才會觸發onload、onready和onunload方法,而觸發頁面解除安裝隻有頁面傳回和頁面重定向兩種操作。頁面生命周期函數的執行順序不用死記硬背,開發過程中可以開啟app.json中的debug模式,路由切換時,小程式、頁面的生命周期函數執行順序都會在控制台以info形式輸出,在後面小節中我們将對頁面生命周期作簡單介紹。
有注冊就有擷取,getcurrentpages()函數便是用于擷取目前頁面棧的執行個體,頁面棧以數組形式按棧順序給出,第一個元素為首頁,最後一個元素為目前頁面。不要嘗試修改頁面棧,這會導緻路由以及頁面狀态錯誤。
示例代碼如下:
頁面對象中注冊的函數可以和視圖層中的元件進行綁定,當達到觸發條件時,就會執行page中定義的相應事件,這類自定義函數統稱為事件處理函數。小程式中元件的事件分為通用事件和特殊事件,事件類型請參考後面2.4.3節中“事件”小節。
頁面首次加載時,架構會結合初始化資料渲染頁面,在邏輯層中則需主動調用page.
prototype.setdata()方法,而不能直接修改page的data值,這樣不僅無法觸發視圖層渲染,還會造成資料不一緻。當 page.prototype.setdata()被調用時,會将資料從邏輯層發送到視圖層觸發視圖層重繪,同時會修改page的data值。setdata()接受一個object對象參數,方法會自動将this.data中的key對應的值變成object參數中key對應的值。當object參數key對應的值和this.data中key對應的值一緻時,将不會觸發視圖層渲染。在項目中我們一定要保證視圖層和邏輯層的資料一緻。
object參數的key值非常靈活,可以按資料路徑的形式給出,如array[5].info、obj.key.subkey,并且這樣使用時,不需要在this.data中預先定義。
頁面的生命周期整體關系着頁面視圖層線程和頁面邏輯層線程,注冊頁面時,object參數中很多屬性都是生命周期函數,這些函數的調用和頁面生命息息相關,程式視圖層線程和邏輯層線程關系如圖2-8所示。

如圖2-8,線程啟動後視圖層和邏輯層互相監聽,當邏輯層線程觸發onload、onshow方法後會把初始資料data傳送給視圖層線程,視圖層完成第一次渲染後觸發邏輯層onready方法,代表頁面已經準備妥當,之後我們便可通過setdata方法主動觸發視圖層渲染。當頁面被調往背景時,觸發onhide方法,這時邏輯層線程并沒有銷毀,我們仍然可以通過代碼控制視圖層渲染,隻是可能不會在界面上表現出來。當頁面從背景回到前台時,觸發onshow方法,最後當頁面銷毀時,觸發onunload方法。整體來看onload、onready和onunload方法在生命周期中隻會調用一次,生命周期内顯示、隐藏頁面都是觸發onshow和onhide方法,在路由方式中,隻有頁面重定向和頁面傳回會結束目前頁面生命周期,當進入一個已加載的頁面時隻會觸發onshow方法,不會觸發onload和onready方法。
wxml(weixin markup language)是架構設計的一套标記語言,用于渲染界面,wxml的渲染原理和react native思路一緻,通過一套标記語言,在不同平台被解析為不同端的渲染檔案,如圖2-9所示。
從圖中我們能看出,wxml語言最終會轉譯為宿主端對應的語言,是以wxml中所使用的标簽一定是小程式定義的标簽,不能使用自定義标簽,這樣才能保證頁面能被正确轉譯。使用微信開發者工具開發時,在wxml中編寫一些html标簽或自定義标簽仍然會被正常解析,這會給開發者造成一種小程式能直接支援html标簽的誤解。這是因為微信開發者工具核心是浏覽器核心,同時小程式架構并沒對wxml中的标簽和wxss中的内容進行強驗證,是以html和css能直接被解析,但這種不合法的wxml在手機端微信中是不能正常顯示的。開發過程中我們一定要拿真機進行測試,保證程式能正常運作。
wxml具有資料綁定、清單渲染、條件渲染、模闆、事件等能力。
小程式中頁面渲染時,架構會将wxml檔案同對應page的data進行綁定,在頁面中我們可以直接使用data中的屬性。小程式的資料綁定使用mustache文法(雙大括号)将變量或簡單的運算規則包起來,主要有以下幾種渲染方式。
簡單綁定是指我們使用mustache文法(雙大括号)将變量包起來,在模闆中直接作為字元串輸出使用,可作用于内容、元件屬性、控制屬性、關鍵字等輸出,其中關鍵字輸出是指将javascript中的關鍵字按其真值輸出。
運作效果如圖2-10所示。
在{{}}内可以做一些簡單的運算,支援的運算有三元運算、算數運算、邏輯判斷、字元串運算,這些運算均符合javascript運算規則。我們利用如下示例為大家展示:
執行後界面如圖2-11所示。
data中的資料可以在模闆再次組合成新的資料結構,這種組合常常在數組或對象中使用。
數組組合比較簡單,可以直接将值放置到數組某個下标下:
最終頁面組合成的對象為[0, 2, 3, 'stringtype']。
對象組合有3種組合方式,這裡我們以資料注入模闆為例。
第一種,直接将資料作為value值進行組合:
最終組合出的對象為{ name : 'value1', age : 'value2' }。
第二種,通過“…”将一個對象展開,把key-value值拷貝到新的結構中:
最終組合成的對象為{ key1 : 1, key2 : 2, key5 : 5, key3 : 3 key4 : 4, key6 : 6 }
第三種,如果對象key和value相同,可以隻寫key值:
這種寫法最後組合成的對象是{ key1 : 1, key2 : 2 }
上述3種方式可以根據項目靈活組合,要注意的是和js中的對象一樣,如果一個組合中有相同的屬性名時,後面的屬性将會覆寫前面的屬性,如:
示例中key1是重複的屬性,那麼後面的屬性将會覆寫前面的屬性,最終組合生成的對象為{ key1 : 3, key2 : 2 }。
除了簡單的資料綁定,我們常常會使用邏輯分支,這時候可以使用wx:if=”{{判斷條件}}”來進行條件渲染,當條件成立時渲染該代碼塊:
示例中view代碼塊将不會渲染,隻有當showcontent的值為true時才渲染。
和普通的程式設計語言一樣,wxml也支援wx:elif和wx:else,如:
示例中頁面隻渲染最後一個。wx:elif和wx:else必須和wx:if配合使用,否則會導緻頁面解析出錯,在項目中大家一定要注意。
wx:if是一個控制屬性,可以添置在任何元件标簽上,但如果我們需要包裝多個元件,又不想影響布局,這時就需要使用标簽将需要包裝的元件放置在裡面,通過wx:if作判斷。不是一個元件,僅僅是一個包裝元素,頁面渲染過程中不做任何渲染,由屬性控制,如下所示:
除了wx:if元件,也可以通過hidden屬性控制元件是否顯示,開發者難免有疑問,這兩種方式該怎樣取舍,這裡我們整理了兩種方式的差別:
wx:if控制是否渲染條件塊内的模闆,當其條件值切換時,會觸發局部渲染以確定條件塊在切換時銷毀或重新渲染。wx:if是惰性的,如果在初始渲染條件為false時,架構将什麼也不做,在條件第一次為真時才局部渲染。
hidden控制元件是否顯示,元件始終會被渲染,隻是簡單控制顯示與隐藏,并不會觸發重新渲染和銷毀。
綜合兩個渲染流程可以看出,由于wx:if會觸發架構局部渲染過程,在頻繁切換狀态的場景中,會産生更大的消耗,這時盡量使用hidden;在運作時條件變動不大的場景中我們使用wx:if,這樣能保證頁面有更高效的渲染,而不用把所有元件都渲染出來。
(1)wx:for
元件的wx:for控制屬性用于周遊數組,重複渲染該元件,周遊過程中目前項的下标變量名預設為index,數組目前項變量名預設為item,如:
通過周遊myarray,頁面渲染了兩個,結果如圖2-12所示。
(2)wx:for-index和wx:for-item
index、item變量名可以通過wx:for-index、wx:for-item屬性修改,如:
渲染結果如圖2-13所示。
普通周遊中我們沒必要修改index、item變量名,當wx:for嵌套使用時,就有必要設定變量名,避免變量名沖突,下面我們周遊一個二維數組:
渲染效果如圖2-14所示。
在本示例中,我們使用了<code><block/></code>标簽,和block wx:if一樣,wx:for可以直接在<code><block/></code>标簽上使用,以渲染一個包含多個節點的結構塊。
在項目過程中,常常會遇到某些相同的結構在不同的地方反複出現,這時可以将相同的布局代碼片段放置到一個模闆中,在不同的地方傳入對應的資料進行渲染,這樣能避免重複開發,提升開發效率。
定義模闆非常簡單,在内定義代碼片段,設定的name屬性,指定模闆名稱即可。如:
使用模闆時,設定is屬性指向需要使用的模闆,設定data屬性,将模闆所需的變量傳入。模闆擁有自己的作用域,隻能使用data屬性傳入的資料,而不是直接使用page中的data資料,渲染時,标簽将被模闆中的代碼塊完全替換。
執行效果如圖2-15所示。
模闆可以嵌套使用,如下所示:
渲染結果如圖2-16所示。
圖2-16 嵌套使用模闆
注意 模闆is屬性支援資料綁定,在項目過程中我們可以通過屬性綁定動态決定使用哪個模闆,如:
<code><template is="{{templatename}}" data="mydata"/></code>
wxml中的事件系統和html中dom事件系統極其相似,也是通過在元件上設定“bind(或catch)+事件名”屬性進行事件綁定,當觸發事件時,架構會調用邏輯層中對應的事件處理函數,并将目前狀态通過參數傳遞給事件處理函數,由于小程式中沒有dom節點概念,是以事件隻能通過wxml綁定,不能通過邏輯層動态綁定。官方對wxml事件的定義如下:
事件是視圖層到邏輯層的通訊方式。
事件可以将使用者的行為回報到邏輯層進行處理。
事件可以綁定在元件上,當觸發事件時,就會執行邏輯層中對應的事件處理函數。
事件對象可以攜帶額外資訊,如 id、dataset、touches。
事件分為冒泡事件和非冒泡事件:
冒泡事件:當一個元件上的事件被觸發後,該事件會向父節點傳遞。
非冒泡事件:當一個元件上的事件被觸發後,該事件不會向父節點傳遞。
有前端開發經驗的開發者應該對事件冒泡都有一定了解,當一個事件被觸發後,該事件會沿該元件向其父級對象傳播,從裡到外依次執行,直到節點最頂層,這個是個非常有用的特性,通常用于實作事件代理,具體實作方案将在下文中具體讨論。
wxml冒泡事件如下:
touchstart:手指觸摸動作開始。
touchmove:手指觸摸後移動。
touchcancel:手指觸摸動作被打斷,如來電提醒、彈窗。
touchend:手指觸摸動作結束。
tap:手指觸摸後馬上離開。
longtap:手指觸摸後,超過350ms再離開。
對于冒泡事件每個元件都是預設支援的,除上述事件之外的其他元件自定義事件如無特殊聲明都是非冒泡事件,如:
的submit事件,的scroll事件,詳細資訊請參考各元件文檔。
在之前内容中,已經多次實作事件綁定,大家應該比較熟悉了,事件綁定的寫法群組件的屬性一樣,以key、value形式組織。
key:以bind或catch開頭,然後跟上事件類型,字母均小寫,如:bindtap,catchtouchstart。
value:事件函數名,對應page中定義的同名函數。找不到同名函數會導緻報錯。
綁定時bind事件綁定不會阻止冒泡事件向上冒泡,catch事件綁定會阻止冒泡事件向上冒泡。
冒泡示例如下:
如上述示例中,點選view3時會先後觸發tap3和tap2事件,由于view2通過catch阻止了tap事件冒泡,這時tap1将不會執行,點選view2隻觸發tap2,點選view1隻觸發tap1。
如果沒有特殊說明,當元件觸發事件時,邏輯層綁定該事件的事件處理函數會收到一個事件對象,如:
上述代碼中,myevent參數e便是事件對象,這和javascript事件綁定特别像。上述代碼執行後事件對象輸出如下:
事件對象屬性基本可分為三類:baseevent、customevent、touchevent。
baseevent為基礎事件對象屬性,包括:
type:事件類型。
timestamp:事件生成時的時間戳,頁面打開到觸發所經過的毫秒數。
target:觸發事件源元件(即冒泡開始的元件)的相關屬性集合,屬性如下:
id:事件源元件的id。
tagname:事件源元件的類型。
dataset:事件源元件上由data-開頭的自定義屬性組成的集合。
currenttarget:事件綁定的目前元件的相關屬性集合,屬性如下:
id:目前元件的id。
tagname:目前元件的類型。
dataset:目前元件上由data-開頭的自定義屬性組成的集合。
<code><canvas/></code>中的觸摸事件不可冒泡,是以沒有currenttarget。
dataset是元件的自定義資料,通過這種方式可以将元件的自定義屬性傳遞
給邏輯層。書寫方式為:以data-開頭,多個單詞由連字元“–”連接配接,屬性名不能有大寫(大寫最終會被轉為小寫),最終在dataset中将連字元轉成駝峰形式,如:
最後dataset列印出來為:
customevent為自定義事件對象(繼承baseevent),隻有一個屬性:
detail:額外資訊,通常傳遞元件特殊資訊。
detail沒有統一的格式,在<code><form/></code>的submit方法中它是<code>{"value":{},"formid": ""}</code>,在<code><swiper/></code>的change事件中它是{"current": current},具體内容參考元件相關文檔。
touchevent為觸摸事件對象(繼承baseevent)屬性如下所示:
touches:觸摸事件,目前停留在螢幕中的觸摸點資訊的數組。
changedtouches:觸摸事件,目前變化的觸摸點資訊的數組,如從無變有(touchstart)、位置變化(touchmove)、從有變無(touchend、touchcancel)。
由于支援多點觸摸,是以touches和changedtouches都是數組格式,每個元素為一個touch對象(canvas觸摸事件中為canvastouch對象)。
touch對象相關屬性如下:
identifier:觸摸點的辨別符。
pagex,pagey:距離文檔左上角的距離,文檔的左上角為原點,橫向為x軸,縱向為y軸。
clientx,clienty:距離頁面可顯示區域(螢幕除去導覽列)左上角的距離,橫向為x軸,縱向為y軸。
canvastouch對象相關屬性如下:
x,y:距離canvas左上角的距離,canvas的左上角為原點,橫向為x軸,縱向為y軸。
一個wxml可以通過import或include引入其他wxml檔案,兩種方式都能引入wxml檔案,差別在于import引入wxml檔案後隻接受模闆的定義,忽略模闆定義之外的所有内容,而且使用過程中有作用域的概念。與import相反,include則是引入檔案中除<code><template/></code>以外的代碼直接拷貝到位置,整體來說import是引入模闆定義,include是引入元件。
<code><import/></code>的src屬性是需要被引入檔案的相對位址,<code><import/></code>引入會忽略引入檔案中<code><template/></code>定義以外的内容,如下例中,在a.wxml引入b.wxml,b.wxml中<code><view/></code>和<code><template is="btemplate"/></code>都被忽略,僅引入了模闆的定義,在a.wxml中能使用b.wxml中定義的模闆:
上述代碼中,a.wxml中的并沒有被渲染,如圖2-17所示。
import引用有作用域概念,隻能直接使用引入的定義模闆,而不能使用間接引入的定義模闆,如下例,在a.wxml中引入b.wxml,b.wxml再引入c.wxml,這樣a能直接使用b中定義的模闆,b能使用c中定義的模闆,但a不能使用c中的模闆:
渲染效果如圖2-18所示。
include引入會将模闆定義标簽外的内容(含模闆使用标簽)直接指派替換,我們基于上個案例進行修改,大家對比一下:
運作效果如圖2-19所示。
通過對比發現,import更适合引用模闆定義檔案,include更适合引入元件檔案,在項目中大家可以根據特性靈活使用。
wxml雖然是一門新标簽語言,但大部分規則和其他前端模闆語言大同小異,本節wxml規則整體可分為資料綁定,事件機制,模闆文法(條件渲染、清單渲染),頁面引用(引用規則、模闆),大家可以對比其他模闆語言學習。
wxss(weixin style sheets)是基于css拓展的樣式語言,用于描述wxml的元件樣式,決定wxml的元件該怎麼顯示,它具有css的大部分特性,在css基礎上wxss拓展了尺寸機關、樣式導入特性,對css選擇器屬性上做了部分相容,目前官方文檔沒有給出wxss具體具備css哪些特性,在開發過程中不能想當然地使用css屬性,一定要使用ios和android真機進行調試,本小節主要講述wxss和css的不同點,後續布局章節會講解css盒子模型布局相關屬性,其餘css屬性大家可以參考w3c規範,在wxss中我們甚至能使用一些相容性寫法,不過在開發過程中我們一定要開啟開發者工具中“開啟上傳代碼時樣式檔案自動補全”功能,這樣小程式會自動補全其餘一些樣式的相容性寫法,保證在不同終端達到統一視覺效果。
css中原有尺寸機關在不同尺寸螢幕中不能完美實作元素按比例縮放,wxss在此基礎上拓展了兩種尺寸機關:rpx(responsive pixel)和rem(root em),這兩種尺寸機關都是相對機關,最終會被換算成px,使用rpx和rem布局頁面能讓頁面界面在不同尺寸螢幕中按比例縮放。
在渲染過程中rpx會按比例轉化為px,wxss規定螢幕寬度為750rpx,如在iphone6中,螢幕寬度為375px,即750rpx=375px,那麼在iphone6中1rpx=0.5px。
同rpx一樣,wxss規定螢幕寬度為20rem,同樣在iphone6中,螢幕寬度為375px,即20rem=375px,是以在iphone6中1rem=18.75px。
以正常裝置為例,這些尺寸換算如表2-1所示。
在設計界面時,如果要實作尺寸自适應,設計師可以用iphone6作為視覺标準。由于rpx和rem最終要被換算為px,在某些情況下可能會存在除不盡的情況,會導緻界面中産生毛刺,這種情況大家要多留意、多測試以盡量避免這種情況。
css選擇器用于選擇需要添加樣式的元素,wxss實作了css的部分選擇器,使用規則和css一樣,熟悉css的同學會很快上手,參見表2-2。
wxss和css代碼結構一樣,一段樣式前面是選擇器,後面是以大括号括起來的樣式組合,每個樣式以分号結束,如下所示:
選擇器 { 樣式1; 樣式2; }
選擇器使用規則和css也是一緻的,如下所示:
同html一樣,樣式除了寫在wxss檔案中,也可以通過設定style、class屬性控制樣式,一般靜态樣式可以統一寫到class中,style樣式會在運作時解析,如非特别需要,盡量避免将靜态樣式寫入style,以免影響渲染速度,例如:
通常在項目中為了便于管理會将wxss按職責拆分為多個檔案,這時便需要@import語句在目前wxss檔案中導入其他wxss檔案,@import後寫入需要導入wxss檔案的相對路徑,用“;”表示語句結束,例如:
至此,小程式架構頁面相關的4個檔案已介紹完成,大家對每個檔案的功能、内容應該都有了一定了解,在本章最後一節中,我們将探讨小程式的子產品化。