從入門到上線一個天氣小程序
作者:wuwhs
segmentfault.com/a/1190000017388333
前言
學習了一段時間小程序,大致過了兩遍開發文檔,抽空做個自己的天氣預報小程序,全當是練手,在這記錄下。小程序開發的安裝、註冊和接入等流程就不羅列了,在小程序接入指南已經寫得很清楚了,以下只對開發過程常用到得一些概念進行簡單梳理,類比 Vue
加強記憶,最後選取個人項目天氣小程序中要註意的幾點來說明。
歡迎掃碼體驗:
源碼請戳這裏,歡迎start~
初始化項目目錄結構
安裝好開發者工具,填好申請到的 AppID
,選好項目目錄,初始化一個普通小程序目錄結構,得到:
-
--|-- 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
的角色,有標簽,有屬性,但是還是有些區別:
1、標簽名不一樣。
寫 HTML
常用標簽 <div>
, <p>
, <span>
等,而小程序中標簽更像是封裝好的組件,比如 <scroll-view>
, <swiper>
, <map>
,提供相應的基礎能力給開發者使用。
2、提供 wx:if
, {{}}
等模板語法。
小程序將渲染和邏輯分離,類似於 React
, Vue
的 MVVM
開發模式,而不是讓 JS
操作 DOM
。
下面針對小程序的數據綁定、列表渲染、條件渲染、模板、事件和應用跟 Vue
類比加深記憶。
數據綁定
WXML
中的動態數據均來自對應 Page
(或 Component
) 的 data
,而在 Vue
中來自當前組件。
小程序和Vue的數據綁定都使用 Mustache
語法,雙括號將變量包起來。區別是 Vue
中使用 Mustache
語法不能作用在 HTML
特性上:
-
<div v-bind:id="‘list-‘ + id">{{msg}}</div>
而小程序作用在標簽屬性上:
-
<view id="item-{{id}}">{{msg}}</view>
列表渲染
Vue
中使用 v-for
指令根據一組數組的選項列表,也可以通過一個對象的屬性叠代進行渲染,使用 (item,index)initems
或 (item,index)of items
形式特殊語法。
-
<ul>
-
<li v-for="(item, index) in items">
-
{{ index }} - {{ item.message }}
-
</li>
-
</ul>
渲染包含多個元素,利用 <template>
元素:
-
<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
。
-
<view wx:for="{{items}}"> {{index}} - {{item.message}} </view>
也可以用 wx:for-item
指定數組當前元素的變量名,用 wx:for-index
指定數組當前下標的變量名。
-
<view wx:for="{{items}}" wx:for-index="idx" wx:for-item="itemName">
-
{{idx}}: {{itemName.message}}
-
</view>
渲染一個包含多節點的結構塊,利用 <block>
標簽:
-
<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將模板引用過去。
定義模板:
-
<script type="text/x-template" id="hello-world-template">
-
<p>Hello hello hello</p>
-
<p>{{msg}}</p>
-
</script>
使用模板:
-
Vue.component(‘hello-world‘, {
-
template: ‘#hello-world-template‘,
-
data () {
-
return {
-
msg: ‘this is a template‘
-
}
-
}
-
})
而在小程序中,在 <template>
中使用 name
屬性作為模板名稱,使用 is
屬性聲明需要使用的模板,然後將模板所需的 data
傳入。
定義模板:
-
<template name="hello-world-template">
-
<view>Hello hello hello</view>
-
<view>{{msg}}</view>
-
</template>
使用模板:
-
<template is="hello-world-template" data="{{...item}}"></template>
-
Page({
-
data: {
-
item: {
-
msg: ‘this is a template‘
-
}
-
}
-
})
事件
在 Vue
中,用 v-on
指令監聽 DOM
事件,並在觸發時運行一些 JavaScript
代碼,對於阻止事件冒泡、事件捕獲分別提供事件修飾符 .stop
和 .capture
的形式。
-
<!-- 阻止單擊事件繼續傳播 -->
-
<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
事件綁定可以阻止冒泡事件向上冒泡。
-
<!-- 單擊事件冒泡繼續傳播 -->
-
<view bindtap="doThis">bindtap</view>
-
<!-- 阻止單擊事件冒泡繼續傳播 -->
-
<view catchtap="doThis">bindtap</view>
采用 capture-bind
、 capture-catch
分別捕獲事件和中斷捕獲並取消冒泡。
-
<!-- 捕獲單擊事件繼續傳播 -->
-
<view capture-bind:tap="doThis">bindtap</view>
-
<!-- 捕獲單擊事件阻止繼續傳播,並且阻止冒泡 -->
-
<view capture-catch="doThis">bindtap</view>
引用
在 Vue
中引用用於組件的服用引入:
-
import ComponentA from ‘./ComponentA‘
-
import ComponentC from ‘./ComponentC‘
在小程序中, WXML
提供兩種引用方式 import
和 include
。
在 item.wxml 中定義了一個叫item的template:
-
<!-- item.wxml -->
-
<template name="item">
-
<text>{{text}}</text>
-
</template>
在 index.wxml 中引用了 item.wxml,就可以使用item模板:
-
<import src="item.wxml" /> <template is="item" data="{{text: ‘forbar‘}}" />
include
可以將目標文件除了 <template>
<wxs>
外整個代碼引入:
-
<!-- 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(Objectdata,Functioncallback)
才起作用,不需要在 this.data
中預先定義,單次設置數據大小不得超過1024kb。
支持以數據路徑的形式改變數組某項或對象某項屬性:
-
// 對於對象或數組字段,可以直接修改一個其下的子字段,這樣做通常比修改整個對象或數組更好
-
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(WeiXinScript)
是小程序的一套腳本語言,結合 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
可以獲取子組件獲取子組件所有屬性和方法。
-
<!-- 父組件 -->
-
<blog-post title="A title"></blog-post>
-
<!-- 子組件 -->
-
<h3>{{ postTitle }}</h3>
-
export default {
-
props: [‘postTitle‘]
-
}
同樣的,在小程序中,父組件定義一些特性,子組件通過 properties
實例屬性獲取,不同的是,提供了 observer
回調函數,可以監聽傳遞值的變化。父組件還可以通過 this.selectComponent
方法獲取子組件實例對象,這樣就可以直接訪問組件的任意數據和方法。
-
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])
回調傳參實現。
-
<!-- 子組件 -->
-
<button v-on:click="$emit(‘enlarge-text‘)">
-
Enlarge text
-
</button>
-
<!-- 父組件 -->
-
<blog-post
-
...
-
v-on:enlarge-text="postFontSize += 0.1"
-
></blog-post>
同樣的,在小程序中也是通過觸發自定義事件 triggerEvent
回調傳參形式實現子組件向父組件傳遞數據。
-
<!-- page.wxml -->
-
<my-component bindcustomevent="pageEventListener2"></my-component>
-
// my-component.js
-
Component({
-
methods: {
-
onTap () {
-
this.triggerEvent(‘customevent‘, {})
-
}
-
}
-
})
天氣預報小程序
說了很多小程序開發的基礎準備,下面就結合個人實際練手項目——天氣預報小程序簡單說明。
物料準備
從需求結果導向,天氣程序首先要能獲取到當前所在地天氣狀況,再次可以自由選擇某地,知道其天氣狀況。這樣就需要有獲取天氣的API和搜索地址API。
-
搜集了很多免費天氣API,最終選中和風天氣,原因很簡單,它提供認證個人開發者申請,擁有更多使用功能和調用次數。
-
地址搜索和城市選擇能力選用微信自家產品騰訊位置服務微信小程序JavaScript SDK。
開發前物料(服務能力)準備好了,接下來就是擼小程序了!
首頁獲取用戶信息、布局相關
布局
微信小程序的樣式已支持大部分 CSS
特性,不用再去考慮太多傳統瀏覽器兼容性問題了,布局方便直接選用 flex
布局。比如:
-
/**app.wxss**/
-
page {
-
background: #f6f6f6;
-
display: flex;
-
flex-direction: column;
-
justify-content: flex-start;
-
}
獲取用戶信息
首頁首次加載獲取用戶,通常會彈窗提示是否允許獲取用戶信息,用戶點擊允許獲取授權,才能成功獲取用戶信息,展示用戶名和用戶頭像等,小程序為了優化用戶體驗,使用 wx.getUserInfo
接口直接彈出授權框的開發方式將逐步不再支持。目前開發環境不彈窗了,正式版暫不受影響。提倡使用 button
組件,指定 open-type
為 getUserInfo
類型,用戶主動點擊後才彈窗。
天氣小程序獲取用戶頭像和用戶名采用的是另一種方式,使用 open-data
可以直接獲取用戶基礎信息,不用彈窗提示。
-
<!-- 用戶信息 -->
-
<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(functioncallback)
獲取布局位置,類似於 DOM
的 getBoundingClientRect
。距離大小為最小負數的標誌位是當前剛滑過的,右側索引欄對應字母應當高亮。
-
<!-- searchGeo.wxml -->
-
<scroll-view bindscroll="scroll" scroll-y="{{true}}">
-
<!-- 城市列表... -->
-
</scroll-view>
-
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>
為動畫滾動效果,滾動到標誌元素位置需要時間,途中可能會經過其它標誌元素,不能立即設置索引焦點,要有一定延時(還沒找到其它好解決方案,暫時這樣)
-
// 點擊索引條
-
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)
-
},
頻繁觸發節流處理
頻繁輸入,或者頻繁滾動,回調觸發會造成性能問題,而其接口也有限定調用頻率,這樣就需要做節流處理。節流是再頻繁觸發的情況下,在大於一定時間間隔才允許觸發。
-
// 節流
-
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/秒,很容易就超了,可以做節流處理。
-
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
的基礎會有很多似曾相識的感覺,當然,在深入的探索過程還有很多“坑”要跨越,本文只是簡單的梳理,具體問題還能多看文檔和小程序社區,還有什麽錯誤歡迎指正哈,完~
從入門到上線一個天氣小程序