前言
學習了一段時間小程式,大致過了兩遍開發文件,抽空做個自己的天氣預報小程式,全當是練手,在這記錄下。小程式開發的安裝、註冊和接入等流程就不羅列了,在小程式接入指南已經寫得很清楚了,以下只對開發過程常用到得一些概念進行簡單梳理,類比 Vue
加強記憶,最後選取個人專案天氣小程式中要注意的幾點來說明。
歡迎掃碼體驗
原始碼請戳這裡,歡迎start~
初始化專案目錄結構
安裝好開發者工具,填好申請到的AppID
,選好專案目錄,初始化一個普通小程式目錄結構,得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--|-- pages |-- index |-- index.js // 首頁js檔案 |-- index.json // 首頁json檔案 |-- index.wxml // 首頁wxml檔案 |-- index.wxss // 首頁wxss檔案 |-- logs |-- logs.js // 日誌頁js檔案 |-- logs.json // 日誌頁json檔案 |-- logs.wxml // 日誌頁wxml檔案 |-- logs.wxss // 日誌頁wxss檔案 |-- utils |-- util.js // 小程式公用方法 |-- app.js // 小程式邏輯 |-- app.json // 小程式公共配置 |-- app.wxss // 小程式公共樣式表 |-- project.config.json // 小程式專案配置 |
可以看到,專案檔案主要分為.json
、.wxml
,.wxss
和.js
型別,每一個頁面由四個檔案組成,為了方便開發者減少配置,描述頁面的四個檔案必須具有相同的路徑與檔名。
JSON配置
小程式配置 app.json
app.json配置是當前小程式的全域性配置,包括小程式的所有頁面路徑、介面表現、網路超時時間、底部 tab 等。
工具配置 project.config.json
工具配置在小程式的根目錄,對工具做的任何配置都會寫入這個檔案,使得只要載入同一個專案程式碼包,開發則工具會自動恢復當時你開發專案時的個性設定。
頁面配置 page.json
頁面配置 是小程式頁面相關的配置,讓開發者可以獨立定義每個頁面的一些屬性,比如頂部顏色,是否下拉等。
WXML 模板
WXML
充當類似 HTML
的角色,有標籤,有屬性,但是還是有些區別:
- 標籤名不一樣。
寫HTML
常用標籤<div>
,<p>
,<span>
等,而小程式中標籤更像是封裝好的元件,比如<scroll-view>
,<swiper>
,<map>
,提供相應的基礎能力給開發者使用。 - 提供
wx:if
,{{}}等模板語法。
小程式將渲染和邏輯分離,類似於React
,Vue
的MVVM
開發模式,而不是讓JS
操作DOM
。
下面針對小程式的資料繫結、列表渲染、條件渲染、模板、事件和應用跟 Vue
類比加深記憶。
資料繫結
WXML
中的動態資料均來自對應 Page
(或 Component
) 的 data
,而在 Vue
中來自當前元件。
小程式和Vue的資料繫結都使用 Mustache
語法,雙括號將變數包起來。區別是 Vue
中使用Mustache
語法不能作用在 HTML
特性上
1 |
<div v-bind:id="'list-' + id">{{msg}}</div> |
而小程式作用在標籤屬性上
1 |
<view id="item-{{id}}">{{msg}}</view> |
列表渲染
Vue
中使用 v-for
指令根據一組陣列的選項列表,也可以通過一個物件的屬性迭代進行渲染,使用 (item, index) in items
或 (item, index) of items
形式特殊語法。
1 2 3 4 5 |
<ul> <li v-for="(item, index) in items"> {{ index }} - {{ item.message }} </li> </ul> |
渲染包含多個元素,利用 <template>
元素
1 2 3 4 5 6 |
<ul> <template v-for="(item, index) in items"> <li>{{ index }} - {{ item.message }}</li> <li class="divider" role="presentation"></li> </template> </ul> |
而在小程式中使用 wx:for
控制屬性繫結一個陣列(其實物件也可以),預設陣列的當前項的下標變數為 index
,當前項變數為 item
。
1 |
<view wx:for="{{items}}"> {{index}} - {{item.message}} </view> |
也可以用 wx:for-item
指定陣列當前元素的變數名,用 wx:for-index
指定陣列當前下標的變數名。
1 2 3 |
<view wx:for="{{items}}" wx:for-index="idx" wx:for-item="itemName"> {{idx}}: {{itemName.message}} </view> |
渲染一個包含多節點的結構塊,利用<block>標籤
1 2 3 4 |
<block wx:for="{{items}}"> <view> {{index}} - {{item.message}} </view> <view class="divider" role="presentation"></view> </block> |
條件渲染
Vue
中使用v-if
、v-else-if
、v-else
指令條件渲染,多個元素使用<template>
包裹,而小程式中使用wx:if
、wx:elseif
、wx:else
來條件渲染,多個元件標籤使用<block>
包裹。
模板
在 Vue
中定義模板一種方式是在 <script>
元素中,帶上 text/x-template
的型別,然後通過一個id將模板引用過去。
定義模板:
1 2 3 4 |
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> <p>{{msg}}</p> </script> |
使用模板:
1 2 3 4 5 6 7 8 |
Vue.component('hello-world', { template: '#hello-world-template', data () { return { msg: 'this is a template' } } }) |
而在小程式中,在 <template>
中使用 name
屬性作為模板名稱,使用 is
屬性宣告需要使用的模板,然後將模板所需的 data
傳入。
定義模板:
1 2 3 4 |
<template name="hello-world-template"> <view>Hello hello hello</view> <view>{{msg}}</view> </template> |
使用模板:
1 |
<template is="hello-world-template" data="{{...item}}"></template> |
1 2 3 4 5 6 7 |
Page({ data: { item: { msg: 'this is a template' } } }) |
事件
在 Vue
中,用 v-on
指令監聽 DOM
事件,並在觸發時執行一些 JavaScript
程式碼,對於阻止事件冒泡、事件捕獲分別提供事件修飾符.stop
和.capture
的形式
1 2 3 4 5 |
<!-- 阻止單擊事件繼續傳播 --> <a v-on:click.stop="doThis"></a> <!-- 新增事件監聽器時使用事件捕獲模式 --> <!-- 即元素自身觸發的事件先在此處理,然後才交由內部元素進行處理 --> <div v-on:click.capture="doThis">...</div> |
而在小程式中,繫結事件以 key
,value
的形式,key
以 bind
或 catch
開頭,然後跟上事件的型別,如 bindtap
、catchtouchstart
,也可緊跟一個冒號形式,如 bind:tap
、catch:touchstart
。bind
事件繫結不會阻止冒泡事件向上冒泡,catch
事件繫結可以阻止冒泡事件向上冒泡。
1 2 3 4 |
<!-- 單擊事件冒泡繼續傳播 --> <view bindtap="doThis">bindtap</view> <!-- 阻止單擊事件冒泡繼續傳播 --> <view catchtap="doThis">bindtap</view> |
採用 capture-bind
、capture-catch
分別捕獲事件和中斷捕獲並取消冒泡。
1 2 3 4 |
<!-- 捕獲單擊事件繼續傳播 --> <view capture-bind:tap="doThis">bindtap</view> <!-- 捕獲單擊事件阻止繼續傳播,並且阻止冒泡 --> <view capture-catch="doThis">bindtap</view> |
引用
在 Vue
中引用用於元件的服用引入
1 2 |
import ComponentA from './ComponentA' import ComponentC from './ComponentC' |
在小程式中,WXML
提供兩種引用方式 import
和 include
。
在 item.wxml 中定義了一個叫item的template:
1 2 3 4 |
<!-- item.wxml --> <template name="item"> <text>{{text}}</text> </template> |
在 index.wxml 中引用了 item.wxml,就可以使用item模板:
1 |
<import src="item.wxml" /> <template is="item" data="{{text: 'forbar'}}" /> |
include
可以將目標檔案除了 <template>
<wxs>
外整個程式碼引入:
1 2 3 4 5 6 |
<!-- index.wxml --> <include src="header.wxml" /> <view> body </view> <include src="footer.wxml" /> <!-- header.wxml --> <view> header </view> <!-- footer.wxml --> <view> footer </view> |
WXSS 樣式
WXSS(WeiXin Style Sheets) 具有 CSS 大部分的特性,也做了一些擴充和修改。
尺寸單位rpx
支援新的尺寸單位 rpx
,根據螢幕寬度自適應,規定螢幕寬為750rpx,免去開發換算的煩惱(採用浮點計算,和預期結果會有點偏差)。
裝置 | rpx換算px(屏寬/750) | px換算rpx(750/屏寬) |
---|---|---|
iPhone5 | 1rpx = 0.42px | 1px = 2.34rpx |
iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
iPhone6 Plus | 1rpx = 0.552px | 1px = 1.81rpx |
iPhone6上,換算相對最簡單,1rpx = 0.5px = 1物理畫素,建議設計師以 iPhone6 為設計稿。
樣式匯入
使用 @import
語句匯入外聯樣式表,注意路徑為相對路徑。
全域性樣式與區域性樣式
app.wxss
中的樣式為全域性樣式,在 Page
(或 Component
) 的 wxss
檔案中定義的樣式為區域性樣式,自作用在對應頁面,並會覆蓋 app.wxss
中相同選擇器。
頁面註冊
小程式是以 Page(Object)
構造頁面獨立環境,app載入後,初始化某個頁面,類似於 Vue 的例項化過程,有自己的初始資料、生命週期和事件處理回撥函式。
初始化資料
和 Vue
一樣,在構造例項屬性上都有一個 data
物件,作為初始資料。
Vue
中修改 data
中某個屬性值直接賦值即可,而在小程式中需要使用 Page
的例項方法 setData(Object data, Function callback)
才起作用,不需要在 this.data
中預先定義,單次設定資料大小不得超過1024kb。
支援以資料路徑的形式改變陣列某項或物件某項屬性:
1 2 3 4 |
// 對於物件或陣列欄位,可以直接修改一個其下的子欄位,這樣做通常比修改整個物件或陣列更好 this.setData({ 'array[0].text': 'changed data' }) |
生命週期回撥函式
每個 Vue
例項在被建立時都要經過一系列的初始化過程,每一個階段都有相應鉤子函式被呼叫,created
mounted
updated
destroyed
。
對於小程式生命週期,分為 Page
的生命週期和 Component
的生命週期。
Page
的生命週期回撥函式有:
onLoad
生命週期回撥-監聽頁面載入onShow
生命週期回撥-監聽頁面顯示onReady
生命週期回撥-監聽頁面初次渲染完成onHide
生命週期回撥-監聽頁面隱藏onUnload
生命週期回撥-監聽頁面解除安裝onPullDownRefresh
監聽使用者下拉動作onReachBotton
頁面上拉觸底事件的處理函式onShareAppMessage
使用者點選右上角轉發onPageScroll
頁面滾動觸發事件的處理函式onTabItemTap
當前是tab
頁時,點選tab
觸發
Component
的生命週期有:
created
在元件例項剛剛被建立時執行attached
在元件例項進入頁面節點樹時執行ready
在元件在檢視層佈局完成後執行moved
在元件例項被移動到節點樹另一個位置時執行detached
在元件例項被從頁面節點樹移除時執行error
每當元件方法丟擲錯誤時執行show
元件所在的頁面被展示時執行hide
元件所在的頁面被隱藏時執行resize
元件所在的頁面尺寸變化時執行
wxs
WXS(WeiXin Script)
是小程式的一套指令碼語言,結合 WXML
,可以構建出頁面的結構。wxs
的執行環境和其他 JavaScript
程式碼是隔離的,wxs
中不能呼叫其他 JavaScript
檔案中定義的函式,也不能呼叫小程式提供的API。從語法上看,大部分和 JavaScript
是一樣的,以下列出一些注意點和差別:
<wxs>
模組只能在定義模組的WXML
檔案中被訪問。使用<include>
或<import>
時,<wxs>
模組不會被引用到對應的WXML
檔案中;<template>
標籤中,只能使用定義該<template>
的WXML
檔案中定義的<wxs>
模組;Date
物件,需要使用getDate
函式,返回一個當前時間的物件;RegExp
物件,使用getRegExp
函式;- 使用
constructor
屬性判斷資料型別。
元件間通訊
小程式元件間通訊和Vue 元件間通訊很相似
父元件傳值到子元件
在 Vue
中,父元件定義一些自定義特性,子元件通過 props
例項屬性獲取,也可通過 wm.$refs
可以獲取子元件獲取子元件所有屬性和方法。
1 2 |
<!-- 父元件 --> <blog-post title="A title"></blog-post> |
1 2 3 4 5 |
<!-- 子元件 --> <h3>{{ postTitle }}</h3> export default { props: ['postTitle'] } |
同樣的,在小程式中,父元件定義一些特性,子元件通過 properties
例項屬性獲取,不同的是,提供了 observer
回撥函式,可以監聽傳遞值的變化。父元件還可以通過 this.selectComponent
方法獲取子元件例項物件,這樣就可以直接訪問元件的任意資料和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Component({ properties: { myProperty: { // 屬性名 type: String, // 型別(必填),目前接受的型別包括:String, Number, Boolean, Object, Array, null(表示任意型別) value: '', // 屬性初始值(可選),如果未指定則會根據型別選擇一個 observer(newVal, oldVal, changedPath) { // 屬性被改變時執行的函式(可選),也可以寫成在methods段中定義的方法名字串, 如:'_propertyChange' // 通常 newVal 就是新設定的資料, oldVal 是舊資料 } }, myProperty2: String // 簡化的定義方式 } }) |
子元件傳值到父元件
在Vue 中通過自定義事件系統觸發 vm.$emit( eventName, […args] )
回撥傳參實現。
1 2 3 4 |
<!-- 子元件 --> <button v-on:click="$emit('enlarge-text')"> Enlarge text </button> |
1 2 3 4 5 |
<!-- 父元件 --> <blog-post ... v-on:enlarge-text="postFontSize += 0.1" ></blog-post> |
同樣的,在小程式中也是通過觸發自定義事件 triggerEvent
回撥傳參形式實現子元件向父元件傳遞資料。
1 2 |
<!-- page.wxml --> <my-component bindcustomevent="pageEventListener2"></my-component> |
1 2 3 4 5 6 7 8 |
// my-component.js Component({ methods: { onTap () { this.triggerEvent('customevent', {}) } } }) |
天氣預報小程式
說了很多小程式開發的基礎準備,下面就結合個人實際練手專案——天氣預報小程式簡單說明。
物料準備
從需求結果導向,天氣程式首先要能獲取到當前所在地天氣狀況,再次可以自由選擇某地,知道其天氣狀況。這樣就需要有獲取天氣的API和搜尋地址API。
- 蒐集了很多免費天氣API,最終選中和風天氣,原因很簡單,它提供認證個人開發者申請,擁有更多使用功能和呼叫次數。
- 地址搜尋和城市選擇能力選用微信自家產品騰訊位置服務微信小程式JavaScript SDK。
開發前物料(服務能力)準備好了,接下來就是擼小程式了!
首頁獲取使用者資訊、佈局相關
佈局
微信小程式的樣式已支援大部分 CSS
特性,不用再去考慮太多傳統瀏覽器相容性問題了,佈局方便直接選用 flex
佈局。
比如:
1 2 3 4 5 6 7 |
/**app.wxss**/ page { background: #f6f6f6; display: flex; flex-direction: column; justify-content: flex-start; } |
獲取使用者資訊
首頁首次載入獲取使用者,通常會彈窗提示是否允許獲取使用者資訊,使用者點選允許獲取授權,才能成功獲取使用者資訊,展示使用者名稱和使用者頭像等,小程式為了優化使用者體驗,使用 wx.getUserInfo
介面直接彈出授權框的開發方式將逐步不再支援。目前開發環境不彈窗了,正式版暫不受影響。提倡使用 button
元件,指定 open-type
為 getUserInfo
型別,使用者主動點選後才彈窗。
天氣小程式獲取使用者頭像和使用者名稱採用的是另一種方式,使用open-data
可以直接獲取使用者基礎資訊,不用彈窗提示。
1 2 3 4 5 6 |
<!-- 使用者資訊 --> <view class="userinfo"> <open-data type="userAvatarUrl" class="userinfo-avatar"/> <text class="userinfo-nickname">{{greetings}},</text> <open-data type="userNickName"/> </view> |
城市拼音首字母錨點
上下滑動城市列表,當滑過當前可視區的城市拼音首字母,右側字母索引欄對應的字母也會切換到高亮顯示。
要滿足當前的這個場景需求,首先要為城市列表的拼音首字母標題新增標誌(id
),當<scroll-view>
滾動觸發時獲取各個標誌位距離視窗頂部的位置,此處用到小程式 WXML
節點API NodesRef.boundingClientRect(function callback)
獲取佈局位置,類似於 DOM
的 getBoundingClientRect
。距離大小為最小負數的標誌位是當前剛滑過的,右側索引欄對應字母應當高亮。
1 2 3 4 |
<!-- searchGeo.wxml --> <scroll-view bindscroll="scroll" scroll-y="{{true}}"> <!-- 城市列表... --> </scroll-view> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Page({ // ... // 城市列表滾動 scroll () { wx.createSelectorQuery().selectAll('.city-list-title') .boundingClientRect((rects) => { let index = rects.findIndex((item) => { return item.top >= 0 }) if (index === -1) { index = rects.length } this.setIndex(index - 1) }).exec() }, // ... |
點選右側字母索引欄的字母,城市列表自動滑動使得對應字母標題可視
滿足這個需求場景,可以利用<scroll-view>元件的 scroll-into-view
屬性,由於已有拼音首字母標題新增標誌(id
),只需將當前點選的字母對應的元素id
滾動到可視即可。需要注意:
- 頻繁
setData
造成效能問題,在這裡過濾重複賦值; - 由於設定了
<scroll-view>
為動畫滾動效果,滾動到標誌元素位置需要時間,途中可能會經過其它標誌元素,不能立即設定索引焦點,要有一定延時(還沒找到其它好解決方案,暫時這樣)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 點選索引條 tapIndexItem (event) { let id = event.currentTarget.dataset.item this.setData({ scrollIntoViewId: `title_${id === '#' ? 0 : id}` }) // 延時設定索引條焦點 setTimeout(() => { this.setData({ barIndex: this.data.indexList.findIndex((item) => item === id) }) }, 500) }, |
頻繁觸發節流處理
頻繁輸入,或者頻繁滾動,回撥觸發會造成效能問題,而其介面也有限定呼叫頻率,這樣就需要做節流處理。節流是再頻繁觸發的情況下,在大於一定時間間隔才允許觸發。
1 2 3 4 5 6 7 8 9 10 11 |
// 節流 const throttle = function(fn, delay) { let lastTime = 0 return function () { let nowTime = Date.now() if (nowTime - lastTime > delay || !lastTime) { fn.apply(this, arguments) lastTime = nowTime } } } |
具體對一些場景,比如騰訊位置服務提供的關鍵字搜尋地址,就限定5次/key/秒,很容易就超了,可以做節流處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Page({ // ... // 輸入搜尋關鍵字 input: util.throttle(function () { let val = arguments[0].detail.value if (val === '') { this.setData({ suggList: [] }) this.changeSearchCls() return false } api.getSuggestion({ keyword: val }) .then((res) => { this.setData({ suggList: res }) this.changeSearchCls() }) .catch((err) => { console.error(err) }) }, 500), // ... }) |
對於上面城市列表滾動,獲取標誌元素位置也應用節流處理。
總結
小程式的基本入門學習門檻不高,小程式的設計應該借鑑了很多現在流行的框架,如果有 React
或 Vue
的基礎會有很多似曾相識的感覺,當然,在深入的探索過程還有很多“坑”要跨越,本文只是簡單的梳理,具體問題還能多看文件和小程式社群,還有什麼錯誤歡迎指正哈,完~