在《微信小程序開發實戰 之 「配置項」與「邏輯層」》中咱們詳細闡述了小程序開發的程序和頁面各配置項與邏輯層的基礎知識。下面咱們繼續解析小程序開發框架中的「視圖層」部分。學習完這兩篇文章的基礎知識,動手開發一個簡單的小程序應用已經不成問題了。css
框架中視圖層以給定的樣式展示數據並反饋事件給邏輯層。html
視圖層由WXML(WeiXin Markup language)與WXSS(WeiXin Style Sheet)編寫,由組件來進行展現,組件是視圖層的基本組成單元。前端
微信小程序提供了視圖窗口、基礎內容、表單組件、導航、媒體、地圖、畫布、開放能力等十餘類數十個組件。關於組件的種類和用法,咱們能夠參考小程序開發者文檔中的組件部分。後續咱們一塊兒在一些開發實例中對組件用法進行解析。歡迎關注。小程序
於微信小程序而言,視圖層就是全部.wxml文件與.wxss文件的集合。微信小程序
WXML是MINA框架設計的一套相似於HTML的標籤語言,與基礎組件、事件系統一塊兒構建頁面的結構,保存在.wxml文件中。數組
WXML目前具備數據綁定、列表渲染、條件渲染、模板、引用及事件綁定的能力。下面咱們經過一些簡單的例子具體學習感覺一下WXML的這些能力。微信
在.wxml文件中動態顯示的數據均來自對應頁面的.js文件中的Page方法的data對象。數據綁定使用Mustache(中文翻譯做「鬍子」)語法(即「雙大括號{{}}」)將變量包括起來。網絡
數據綁定有多種用法,能夠簡單的用於表現數據,也能夠用在組件屬性、控制屬性中,還能夠進行運算、組合構成新的數據。app
直接用來呈現動態數據:框架
<!--wxml--> <view> {{content}} </view> //page.js Page({ data:{ content: 'Hello MINA !' } })
用在標籤自身的屬性值中,須要加雙引號:
<!--wxml--> <view id="res-{{id}}"> {{content}} </view> //page.js Page({ data:{ content: 'Hello MINA !', id: 0 } })
用於控制語句的條件判斷中,也須要加雙引號:
<!--wxml--> <view wx:if="{{condition}}"> </view> //page.js Page({ data:{ condition: false } })
能夠在{{}}內進行簡單的運算,包括三元運算、算數運算、邏輯判斷、數據路徑運算等。
三元運算:
<!--wxml-->
<view hidden="{{flag ? true : false}}">Hidden</view>
算術運算:
<!--wxml--> <view>{{a+b}}+{{c}}+d</view> //page.js Page({ data:{ a:1, b:2, c:3 } })
//結果:view中的內容爲3+3+d
邏輯判斷:
<view wx:if="{{count > 1}}"></view>
字符串運算:
<!--wxml--> <view>{{"Hello" + name}}</view> //page.js Page({ data:{ name: "World !" } })
路徑運算:
<!--wxml--> <view>{{obj.key}} {{array[0]}}</view> //page.js Page({ data:{ obj:{ key: 'Hello' }, array:['World'] } })
在Mustache內直接進行組合,構成新的對象或數組。
數組:
<!--wxml--> <view wx:for="{{[0,1,2,3,four]}}">{{item}}</view> //page.js Page({ data:{ four: 4 } }) //最終組合成數組[0,1,2,3,4]
對象:
<!--wxml--> <template is="objCombine" data="{{for:a , bar:b}}"></template> //page.js Page({ data:{ a: 1, b: 2 } }) //最終組合成的對象是{for:1 , bar:2}
也能夠用「擴展運算符」「...」來展開對象:
<!--wxml--> <template is="objCombine" data="{{...obj1 , ...obj2 , e: 5}}"></template> //page.js Page({ data:{ obj1: { a: 1, b: 2 }, obj2: { c: 3, d: 4 } } }) //最終組合成的對象是{a: 1 , b: 2 , c: 3 , d: 4 , e: 5}
若是對象的key和value相同,也能夠間接的表示:
<!--wxml--> <template is="objCombine" data="{{foo, bar}}"></template> //page.js Page({ data:{ foo: 'my-foo', bar: 'my-bar' } }) //最終組合成的對象是{foo:'my-foo' , bar:'my-bar'}
須要注意,上述方式能夠隨意組合,但若是變量名相同,後面的對象會覆蓋前面的對象。
<!--wxml--> <template is="objCombine" data="{{...obj1 , ...obj2 , a , c: 7}}"></template> //page.js Page({ data:{ obj1: { a: 1, b: 2 }, obj2: { b: 3, c: 4 }, a: 6 } }) //最終組合成的對象是{a: 6 , b: 3 , c: 7}
條件語句可用於.wxml中進行條件渲染。
wx:if
咱們用 wx:if = "{{condition}}" 來判斷是否須要渲染該代碼塊。如:
<view wx:if="{{condition}}">條件爲真</view>
也能夠用wx:elif和wx:else來添加一個else塊:
<view wx:if = "{{len > 5}}"> 1 </view> <view wx:elif = "{{len > 2}}"> 2 </view> <view wx:else > 3> 3 </view>
wx:if 是一個控制屬性,須要將它添加到一個組件標籤上。若是想一次性控制多個組件標籤該如何操做呢?咱們能夠藉助<block/>標籤來實現這一操做,也就是把wx:if做用在<block/>標籤上。如:
<block wx:if = "{{true}}">
<view> 標籤1 </view>
<view> 標籤2 </view>
</block>
須要注意<block />並非組件,它只是一個包裝元素,不會在頁面中作任何渲染,只接受控制屬性。
wx:if 也是惰性的,若是在初始渲染時條件爲false,框架什麼也不作,在條件第1次爲true時纔開始局部渲染。
相比之下,hidden就簡單的多,組件始終會被渲染,只須要簡單的控制顯示OR隱藏。
那麼什麼狀況下用hidden,什麼狀況下用wx:if呢?二者並無明確的界限。通常來講,wx:if有較高的切換消耗,而hidden有更高的初始渲染消耗。所以,若是須要頻繁切換,用hidden更好;若是運行時條件改變頻率不大,則wx:if更合適。
列表語句(for循環)可用於.wxml中進行列表渲染。
wx:for
在組件上使用 wx:for 控制屬性綁定一個數組,便可使用數組中各項的數據重複渲染該組件。
數組默認下標變量名爲index,數組默認元素變量名爲item。示例以下:
<view wx:for ="{{items}}"> {{index}}: {{item.message}} </view> //page.js Page({ data:{ items:[{ message:'foo' },{ message:'bar' }] } }) //結果顯示爲
0:foo
1:bar
可使用 wx:for-item 指定數組元素的變量名,使用 wx:for-index 指定數組下標的變動名。如:
<view wx:for ="{{items}}" wx:for-index= "idx" wx:for-item= "itemName"> {{idx}}: {{itemName.message}} </view>
wx:for也能夠嵌套使用:
<view wx:for ="{{items}}" wx:for-item= "i"> <view wx:for ="{{items}}" wx:for-item= "j"> <view wx:if = "{{i <= j}}"> {{i}} * {{i}} = {{i * j}} </view> </view> </view> //page.js Page({ data:{ items:[1,2,3,4,5,6,7,8,9] } })
也能夠藉助<block />標籤使用wx:for來控制多個組件的渲染:
<block wx:for= "{{1,2,3}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
若是列表中項目的位置會發生變更,或者有新的項目添加到列表中,而且但願列表中的項目保持本身的特徵和狀態(如<input />中輸入的內容,<switch />的選中狀態),須要使用wx:key來指定列表中項目的惟一的標識符。
wx:key的值有兩種形式:
若是不使用wx:key,會報出一個警示(warning),若是明確知道該列表是靜態的,或者沒必要關注其順序,能夠選擇忽略。
示例代碼以下:
<!--wx-key-demo.wxml--> <switch wx:for="{{objectArray}}" wx:key="unique" style="display: block;"> {{item.id}} </switch> <button bindtap="switch"> Switch </button> <button bindtap="addToFront"> Add to the front </button> <switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch> <button bindtap="addNumberToFront"> Add to the front </button> //wx-key-demo.js Page({ data: { objectArray:[ {id: 5, unique: 'unique_5'}, {id: 4, unique: 'unique_4'}, {id: 3, unique: 'unique_3'}, {id: 2, unique: 'unique_2'}, {id: 1, unique: 'unique_1'}, {id: 0, unique: 'unique_0'} ], numberArray:[1,2,3,4] }, switch: function(e){ const length = this.data.objectArray.length for(let i=0; i < length; i++){ const x = Math.floor(Math.random() * length) const y = Math.floor(Math.random() * length) const temp = this.data.objectArray[x] this.data.objectArray[x] = this.data.objectArray[y] this.data.objectArray[y] = temp } this.setData({ objectArray:this.data.objectArray }) }, addToFront:function(e){ const length = this.data.objectArray.length this.data.objectArray = [{id:length, unique: 'unique_'+length}].concat(this.data.objectArray) this.setData({objectArray:this.data.objectArray}) }, addNumberToFront:function(e){ this.data.numberArray = [this.data.numberArray.length + 1].concat(this.data.numberArray) this.setData({ numberArray:this.data.numberArray }) } })
(能夠小程序開發工具中預覽效果,注意將wxml片斷和j片斷分別保存在.wxml文件和.js文件中)
WXML支持使用模版(template)。能夠在模版中定義代碼片斷,而後在別的地方引用。
定義模版時,使用name屬性爲模版命名。 在<template />標籤內定義模版代碼片斷,下面是一段電影列表頁面顯示電影評級星數的模版:
<template name="starsTemplate"> <view class="stars-container"> <view class="stars"> <block wx:for = "{{stars}}" wx:for-item="i"> <image wx:if="{{i}}" src="/images/icon/star.png"></image> <image wx:else src="/images/icon/none-star.png"></image> </block> </view> <text class="star-score">{{score}}</text> </view> </template>
使用is屬性,聲明須要使用的模版,還須要將模版所須要的data傳入,例如:
<!--wxml--> <template is="starsTemplate" data="{{stars:stars,score:average}}"/>
is 屬性還能夠藉助 Mustache 語法,來動態決定具體須要渲染哪一個模版:
<template name="fir">
<view> first </view>
</template>
<template name="sec">
<view> second </view>
</template>
<block wx:for="{{[1,2,3,4,5]}}">
<template is="{{item % 2 == 0 ? 'fir' : 'sec'}}" />
</block>
模版擁有本身的做用域,它只能使用data傳入的數據。
WXML提供兩種文件引用的方式:import 和 include。
import 能夠在當前文件中使用目標文件定義的template,例如,在 item.wxml 中定義了一個叫 item 的 template:
<!-- item.wxml-- > <template name = "item"> <view>{{text}}</view> </template>
在index.wxml中引用item.wxml,就可使用item模版:
<import src = "item.wxml" /> <template is = "item" data = "{{text: 'forbar'}}" />
import是有做用域概念的,只會引用目標文件中定義的template,而不能引用目標文件嵌套import的template。好比,C import B , B import A,在C中可使用B定義的template,在B中可使用A定義的template,可是C不能使用A中定義的template。
include可將目標文件除模版代碼(<template />)塊的全部代碼引入,至關於拷貝到include位置。
<!-- index.wxml --> <include src = "header.wxml" /> <view> body </view> <include src = "footer.wxml" /> <!-- header.wxml --> <view> header </view> <!-- footer.wxml --> <view> footer </view>
事件是視圖層到邏輯層的通訊方式,能夠將用戶的行爲反饋到邏輯層進行處理。事件綁定到組件上,當觸發事件時,就會執行邏輯層中對應的事件處理函數。事件對象能夠攜帶額外的信息,如id、dataset、touches。
小程序與用戶的交互,多數是經過事件來完成的。
首先,在組件中綁定一個事件處理函數。以下所示,事件綁定的屬性是bindtap,綁定的事件名稱是tapName,當用戶點擊該組件的時候會在該頁面對應的Page中找到相應的事件處理函數tapName。
//view組件的惟一標識id值爲tapTest;自定義屬性hi,其值爲MINA;綁定事件tapName。 <view id = "tapTest" data-hi = "MINA" bindtap = "tapName"> Click me </view>
(bindtap=bind+tap,即綁定的是冒泡事件tap。)
其次,要在頁面.js文件的Page定義中寫上相應的事件處理函數,參數是event。以下所示:
Page({ tapName: function(event){ console.log(event) } })
若是咱們將上述兩段代碼分別放入.wxml和.js文件中,編譯以後咱們就能夠看到控制檯的console中顯示的log信息,大體以下:
{ 「type": "tap", "timeStamp": 1252, "target": { "id": "tapTest", "offsetLeft": 0, "offsetTop": 0, "dataset": { "hi": "MINA" } }, "currentTarget": { "id": "tapTest", "offsetLeft": 0, "offsetTop": 0, "dataset": { "hi": "MINA" } }, "touches": [{ "pageX": 30, "pageY": 12, "clientX": 30, "clientY": 12, "screenX": 112, "screenY": 151 }], "detail": { "x": 30, "y": 12 } }
微信小程序中的事件分爲兩種:冒泡與非冒泡。
WXML中的冒泡事件僅有6個:
touchstart 手指觸摸; touchmove 手指觸摸後移動; touchcancel 手指觸摸動做被中斷,如來電提醒、彈窗;
touchend 手指觸摸動做結束; tap 手指觸摸後離開; longtap 手指觸摸後,超過350ms再離開
除上述事件以外的其餘組件自定義事件都是非冒泡事件。
事件綁定的寫法跟組件屬性寫法相同,都是以key、value的形式:
key以bind或catch開頭,後面緊跟事件類型,如bindtap、catchtap。
value是一個字符串,須要在對應的Page中定義同名函數, 否則在事件被觸發時會報錯。
bind和catch的區別在於,bind事件綁定不會阻止冒泡事件向上冒泡,catch事件綁定能夠阻止冒泡事件向上冒泡。
例如:
<view id = "outter" bindtap = "handleTap1"> out view <view id = "middle" catchtap = "handleTap2"> middle view <view id = "inner" bindtap = "handleTap3"> inner view </view> </view> </view>
上面的代碼片斷中,點擊 id 爲 inner 的view組件會前後觸發 handleTap3 和 handleTap2 ,由於事件會冒泡到 id 爲 middle 的組件,而 middle 組件阻止了事件冒泡,再也不向上傳遞。點擊 id 爲 middle 的view組件會觸發 handleTap2,點擊 id 爲 outter 的view組件會觸發 handleTap1。
邏輯層的事件處理函數會收到一個事件對象,這個事件對象具備的屬性以下:
其中,target是指觸發事件的源組件,是一個對象,它自己也有三個屬性:
而currentTarget是事件的當前組件。與target相似也是一個對象而且一樣具備上述的3個屬性 。
target和currentTarget的區別能夠參考上面的代碼片斷中,點擊 inner view 時,handleTap3 收到的事件對象 target 和 currentTarget 都是inner,而 handleTap2 收到的事件對象 target 就是 inner ,currentTarget 就是 middle。
dataset 在組件中能夠定義數據,這些數據將經過事件傳遞給 App Service 。dataset 書寫方式以 data- 開頭,多個單詞由連字符 「-」 鏈接,不能有大寫(會自動轉換成小寫)。如data-element-type,最終在 event.target.dataset 中會將連字符轉成駝峯形式:elementType。
示例代碼以下:
//bindviewtap.wxml <view data-alpha-beta = "1" data-alphaBata = "2" bindtap = "bindViewTap" > DataSet Test </view> //bindViewtap.js Page({ bindViewTap:function(event){ event.target.dataset.alphaBeta == 1 // -會轉換成駝峯寫法 event.target.dataset.alphabeta == 2 // 大寫會轉換成小寫 } })
touches是一個觸摸點的數組。每一個元素爲一個Touch對象,具備以下屬性:
identifier ,觸摸點的標識符;
pageX,pageY ,距離文檔左上角的距離,文檔的左上角爲原點,橫向爲X軸,縱向爲Y軸;
clientX,clientY,距離頁面可顯示區域(屏幕除去導航條)左上角的距離,橫向爲X軸,縱向爲Y軸。
changedTouches 數據格式同 touches。表示有變化的觸摸點,如 touchstart 從無變有,touchmove 位置變化,touchend、touchcancel 從有變無。
wxss是一套樣式語言,用於描述wxml的組件樣式。它將決定wxml的組件應該怎麼顯示。
wxss的選擇器目前支持(「.class」、「#id」、「element」、「element,element」、「::after」、「::before」),並且本地資源沒法經過wxss獲取,因此wxss中的樣式都是用的網絡圖片,或者base64。這樣對於某些前端開發者而言,會有所侷限。
好在wxss具備css大部分特性,同時與css相比,wxss擴展的特性有:尺寸單位、樣式導入。
wxss新增了針對移動端屏幕的兩種尺寸單位:rpx與rem。
rpx(responsive pixel):能夠根據屏幕寬度進行自適應。規定屏幕寬爲750rpx。
如在iphone6上,屏幕寬度爲375px,共有750個物理像素,則750rpx = 375px = 750物理像素,1rpx=0.5px=1物理像素。
設備 rpx換算px(屏幕寬度/750) px換算rpx(750/屏幕寬度)
iphone5 1rpx = 0.42px 1px = 2.34rpx
iphone6/6s 1rpx = 0.5px 1px = 2rpx
iphnoe6s Plus 1rpx = 0.552px 1px = 1.81rpx
rem(root em):規定屏幕寬度爲20rem;1rem=(750/20)rpx。
所以建議開發微信小程序時設計師能夠用iphon6屏幕做爲視覺稿的標準。
rpx計量的最大優點在於750設計稿不須要進行任何轉換便可適配。750設計稿是多少就是多少。非750的設計稿則須要進行一次轉換,如640的設計稿就須要進行一次換算,在640設計稿中的 1rpx = 640/750rpx ,而在wxss中並不支持算術運算符,因此小程序的視覺設計稿儘可能使用750來給出。
可使用 @import 語句來導入外聯樣式表。@import 後跟須要導入的外聯樣式表的相對路徑,並用「;」表示語句結束。示例以下:
/**common.wxss**/ .small-p { padding: 5px; } /**app.wxss**/ @import "common.wxss"; .middle-p { padding:15px; }
內聯樣式指的是框架組件上支持使用 style、class 屬性來控制組件的樣式:
style:style接收動態的樣式,在運行時會進行解析,因此應該避免將靜態的樣式寫到 style 中,以避免影響渲染速度:
<view style = "color:{{color}};" />
class:用於指定樣式規則,其屬性值是樣式規則中類選擇器名(樣式類名)的集合,樣式類名不須要帶上「.」,如「.normal-view」樣式類的使用:
<view class = "normal_view" />
wxss 目前支持的選擇有:
.class, 樣例:intro,選擇全部擁有 class="intro" 的組件。
#id , 樣例:#firstname,選擇擁有 id="firstname" 的組件。
element, 樣例:view,選擇全部view組件。
element,element 樣例:view,checkbox,選擇全部文檔的 view 組件和全部的 checkbox 組件。
::after 樣例:view::after,在view組件後面插入內容。
::before 樣例:view::before,在view組件前面插入內容。
定義在app.wxss 中的樣式稱爲全局樣式,做用於每個頁面。在Page 的.wxss文件中定義的樣式爲局部樣式,只做用在對應的頁面,並會覆蓋app.wxss中相同的選擇器。
組件是視圖層的基本構成單元。
一個組件一般包含「開始標籤」和「結束標籤」,組件由屬性來定義和修飾,放在「開始標籤」中。組件的內容則包含在兩個標籤以內。全部的組件與屬性都須要使用小寫字符。組件代碼樣式以下:
<tagname property = "value">
Content goes here...
</tagname>
全部組件都有共同的屬性:
屬性名 | 類型 | 描述 | 註釋 |
id | String | 組件的惟一標示符 | 保持整個頁面惟一 |
class | String | 組件的樣式類 | 在對應的wxss 中定義的樣式類 |
style | String | 組件的內聯樣式 | 能夠動態設置的內聯樣式 |
hidden | Boolean | 組件是否顯示 | 全部組件默認顯示 |
data-* | Any | 自定義屬性 | 組件上觸發事件時會發送給事件處理函數 |
bind*/catch* | EventHandler | 組件的事件 | 詳見本文前面的wxml事件綁定部分 |
同時每個組件也能夠有自定義的屬性(稱爲特殊獨有屬性),用於該組件的功能或樣式進行修飾。自定義屬性只支持如下幾種數據類型:
Boolean、Number、String、Array、Object、EventHandler。
微信小程序爲開發者提供的組件分爲經常使用組件和高級組件兩個大類,其中經常使用組件包括視圖容器、基礎內容、表單、互動操做、頁面導航。高級組件包括媒體、地圖、畫布、客服會話組件。
對於這些組件的常規使用方法,咱們能夠參考微信官方提供的小程序開發者文檔。