1. 程式人生 > >從入門到上線一個天氣小程序

從入門到上線一個天氣小程序

res 允許 The pin vid reg das 字符 justify

作者:wuwhs

segmentfault.com/a/1190000017388333

前言

學習了一段時間小程序,大致過了兩遍開發文檔,抽空做個自己的天氣預報小程序,全當是練手,在這記錄下。小程序開發的安裝、註冊和接入等流程就不羅列了,在小程序接入指南已經寫得很清楚了,以下只對開發過程常用到得一些概念進行簡單梳理,類比 Vue 加強記憶,最後選取個人項目天氣小程序中要註意的幾點來說明。

技術分享圖片

歡迎掃碼體驗:

技術分享圖片

源碼請戳這裏,歡迎start~

初始化項目目錄結構

安裝好開發者工具,填好申請到的 AppID,選好項目目錄,初始化一個普通小程序目錄結構,得到:

  1. --|-- pages

  2. |-- index

  3. |-- index.js // 首頁js文件

  4. |-- index.json // 首頁json文件

  5. |-- index.wxml // 首頁wxml文件

  6. |-- index.wxss // 首頁wxss文件

  7. |-- logs

  8. |-- logs.js // 日誌頁js文件

  9. |-- logs.json // 日誌頁json文件

  10. |-- logs.wxml // 日誌頁wxml文件

  11. |-- logs.wxss // 日誌頁wxss文件

  12. |-- utils

  13. |-- util.js // 小程序公用方法

  14. |-- app.js // 小程序邏輯

  15. |-- app.json // 小程序公共配置

  16. |-- app.wxss // 小程序公共樣式表

  17. |-- 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{{}}等模板語法。

小程序將渲染和邏輯分離,類似於 ReactVueMVVM開發模式,而不是讓 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)initems(item,index)of items 形式特殊語法。

  1. <ul>

  2. <li v-for="(item, index) in items">

  3. {{ index }} - {{ item.message }}

  4. </li>

  5. </ul>

渲染包含多個元素,利用 <template>元素:

  1. <ul>

  2. <template v-for="(item, index) in items">

  3. <li>{{ index }} - {{ item.message }}</li>

  4. <li class="divider" role="presentation"></li>

  5. </template>

  6. </ul>

而在小程序中使用 wx:for 控制屬性綁定一個數組(其實對象也可以),默認數組的當前項的下標變量為 index ,當前項變量為 item

  1. <view wx:for="{{items}}"> {{index}} - {{item.message}} </view>

也可以用 wx:for-item 指定數組當前元素的變量名,用 wx:for-index 指定數組當前下標的變量名。

  1. <view wx:for="{{items}}" wx:for-index="idx" wx:for-item="itemName">

  2. {{idx}}: {{itemName.message}}

  3. </view>

渲染一個包含多節點的結構塊,利用 <block> 標簽:

  1. <block wx:for="{{items}}">

  2. <view> {{index}} - {{item.message}} </view>

  3. <view class="divider" role="presentation"></view>

  4. </block>

條件渲染

Vue 中使用 v-ifv-else-ifv-else指令條件渲染,多個元素使用 <template>包裹,而小程序中使用 wx:ifwx:elseifwx:else來條件渲染,多個組件標簽使用 <block>包裹。

模板

Vue 中定義模板一種方式是在 <script> 元素中,帶上 text/x-template 的類型,然後通過一個id將模板引用過去。

定義模板:

  1. <script type="text/x-template" id="hello-world-template">

  2. <p>Hello hello hello</p>

  3. <p>{{msg}}</p>

  4. </script>

使用模板:

  1. Vue.component(‘hello-world‘, {

  2. template: ‘#hello-world-template‘,

  3. data () {

  4. return {

  5. msg: ‘this is a template‘

  6. }

  7. }

  8. })

而在小程序中,在 <template> 中使用 name 屬性作為模板名稱,使用 is 屬性聲明需要使用的模板,然後將模板所需的 data 傳入。

定義模板:

  1. <template name="hello-world-template">

  2. <view>Hello hello hello</view>

  3. <view>{{msg}}</view>

  4. </template>

使用模板:

  1. <template is="hello-world-template" data="{{...item}}"></template>

  1. Page({

  2. data: {

  3. item: {

  4. msg: ‘this is a template‘

  5. }

  6. }

  7. })

事件

Vue 中,用 v-on 指令監聽 DOM 事件,並在觸發時運行一些 JavaScript 代碼,對於阻止事件冒泡、事件捕獲分別提供事件修飾符 .stop.capture的形式。

  1. <!-- 阻止單擊事件繼續傳播 -->

  2. <a v-on:click.stop="doThis"></a>

  3. <!-- 添加事件監聽器時使用事件捕獲模式 -->

  4. <!-- 即元素自身觸發的事件先在此處理,然後才交由內部元素進行處理 -->

  5. <div v-on:click.capture="doThis">...</div>

而在小程序中,綁定事件以 keyvalue 的形式, keybindcatch 開頭,然後跟上事件的類型,如 bindtapcatchtouchstart,也可緊跟一個冒號形式,如 bind:tapcatch:touchstartbind事件綁定不會阻止冒泡事件向上冒泡, catch 事件綁定可以阻止冒泡事件向上冒泡。

  1. <!-- 單擊事件冒泡繼續傳播 -->

  2. <view bindtap="doThis">bindtap</view>

  3. <!-- 阻止單擊事件冒泡繼續傳播 -->

  4. <view catchtap="doThis">bindtap</view>

采用 capture-bindcapture-catch 分別捕獲事件和中斷捕獲並取消冒泡。

  1. <!-- 捕獲單擊事件繼續傳播 -->

  2. <view capture-bind:tap="doThis">bindtap</view>

  3. <!-- 捕獲單擊事件阻止繼續傳播,並且阻止冒泡 -->

  4. <view capture-catch="doThis">bindtap</view>

引用

Vue 中引用用於組件的服用引入:

  1. import ComponentA from ‘./ComponentA‘

  2. import ComponentC from ‘./ComponentC‘

在小程序中, WXML 提供兩種引用方式 importinclude

在 item.wxml 中定義了一個叫item的template:

  1. <!-- item.wxml -->

  2. <template name="item">

  3. <text>{{text}}</text>

  4. </template>

在 index.wxml 中引用了 item.wxml,就可以使用item模板:

  1. <import src="item.wxml" /> <template is="item" data="{{text: ‘forbar‘}}" />

include 可以將目標文件除了 <template> <wxs> 外整個代碼引入:

  1. <!-- index.wxml -->

  2. <include src="header.wxml" /> <view> body </view> <include src="footer.wxml" />

  3. <!-- header.wxml -->

  4. <view> header </view>

  5. <!-- footer.wxml -->

  6. <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。

支持以數據路徑的形式改變數組某項或對象某項屬性:

  1. // 對於對象或數組字段,可以直接修改一個其下的子字段,這樣做通常比修改整個對象或數組更好

  2. this.setData({

  3. ‘array[0].text‘: ‘changed data‘

  4. })

生命周期回調函數

每個 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 可以獲取子組件獲取子組件所有屬性和方法。

  1. <!-- 父組件 -->

  2. <blog-post title="A title"></blog-post>

  1. <!-- 子組件 -->

  2. <h3>{{ postTitle }}</h3>

  3. export default {

  4. props: [‘postTitle‘]

  5. }

同樣的,在小程序中,父組件定義一些特性,子組件通過 properties 實例屬性獲取,不同的是,提供了 observer 回調函數,可以監聽傳遞值的變化。父組件還可以通過 this.selectComponent 方法獲取子組件實例對象,這樣就可以直接訪問組件的任意數據和方法。

  1. Component({

  2. properties: {

  3. myProperty: { // 屬性名

  4. type: String, // 類型(必填),目前接受的類型包括:String, Number, Boolean, Object, Array, null(表示任意類型)

  5. value: ‘‘, // 屬性初始值(可選),如果未指定則會根據類型選擇一個

  6. observer(newVal, oldVal, changedPath) {

  7. // 屬性被改變時執行的函數(可選),也可以寫成在methods段中定義的方法名字符串, 如:‘_propertyChange‘

  8. // 通常 newVal 就是新設置的數據, oldVal 是舊數據

  9. }

  10. },

  11. myProperty2: String // 簡化的定義方式

  12. }

  13. })

子組件傳值到父組件

在Vue 中通過自定義事件系統觸發 vm.$emit(eventName,[…args]) 回調傳參實現。

  1. <!-- 子組件 -->

  2. <button v-on:click="$emit(‘enlarge-text‘)">

  3. Enlarge text

  4. </button>

  1. <!-- 父組件 -->

  2. <blog-post

  3. ...

  4. v-on:enlarge-text="postFontSize += 0.1"

  5. ></blog-post>

同樣的,在小程序中也是通過觸發自定義事件 triggerEvent 回調傳參形式實現子組件向父組件傳遞數據。

  1. <!-- page.wxml -->

  2. <my-component bindcustomevent="pageEventListener2"></my-component>

  1. // my-component.js

  2. Component({

  3. methods: {

  4. onTap () {

  5. this.triggerEvent(‘customevent‘, {})

  6. }

  7. }

  8. })

天氣預報小程序

說了很多小程序開發的基礎準備,下面就結合個人實際練手項目——天氣預報小程序簡單說明。

物料準備

從需求結果導向,天氣程序首先要能獲取到當前所在地天氣狀況,再次可以自由選擇某地,知道其天氣狀況。這樣就需要有獲取天氣的API和搜索地址API。

  • 搜集了很多免費天氣API,最終選中和風天氣,原因很簡單,它提供認證個人開發者申請,擁有更多使用功能和調用次數。

  • 地址搜索和城市選擇能力選用微信自家產品騰訊位置服務微信小程序JavaScript SDK。

開發前物料(服務能力)準備好了,接下來就是擼小程序了!

首頁獲取用戶信息、布局相關

布局

微信小程序的樣式已支持大部分 CSS 特性,不用再去考慮太多傳統瀏覽器兼容性問題了,布局方便直接選用 flex 布局。比如:

  1. /**app.wxss**/

  2. page {

  3. background: #f6f6f6;

  4. display: flex;

  5. flex-direction: column;

  6. justify-content: flex-start;

  7. }

獲取用戶信息

首頁首次加載獲取用戶,通常會彈窗提示是否允許獲取用戶信息,用戶點擊允許獲取授權,才能成功獲取用戶信息,展示用戶名和用戶頭像等,小程序為了優化用戶體驗,使用 wx.getUserInfo 接口直接彈出授權框的開發方式將逐步不再支持。目前開發環境不彈窗了,正式版暫不受影響。提倡使用 button 組件,指定 open-typegetUserInfo類型,用戶主動點擊後才彈窗。

天氣小程序獲取用戶頭像和用戶名采用的是另一種方式,使用 open-data 可以直接獲取用戶基礎信息,不用彈窗提示。

  1. <!-- 用戶信息 -->

  2. <view class="userinfo">

  3. <open-data type="userAvatarUrl" class="userinfo-avatar"/>

  4. <text class="userinfo-nickname">{{greetings}},</text>

  5. <open-data type="userNickName"/>

  6. </view>

城市拼音首字母錨點

上下滑動城市列表,當滑過當前可視區的城市拼音首字母,右側字母索引欄對應的字母也會切換到高亮顯示。

要滿足當前的這個場景需求,首先要為城市列表的拼音首字母標題添加標誌( id),當 <scroll-view>滾動觸發時獲取各個標誌位距離視窗頂部的位置,此處用到小程序 WXML 節點API NodesRef.boundingClientRect(functioncallback) 獲取布局位置,類似於 DOMgetBoundingClientRect。距離大小為最小負數的標誌位是當前剛滑過的,右側索引欄對應字母應當高亮。

  1. <!-- searchGeo.wxml -->

  2. <scroll-view bindscroll="scroll" scroll-y="{{true}}">

  3. <!-- 城市列表... -->

  4. </scroll-view>

  1. Page({

  2. // ...

  3. // 城市列表滾動

  4. scroll () {

  5. wx.createSelectorQuery().selectAll(‘.city-list-title‘)

  6. .boundingClientRect((rects) => {

  7. let index = rects.findIndex((item) => {

  8. return item.top >= 0

  9. })

  10. if (index === -1) {

  11. index = rects.length

  12. }

  13. this.setIndex(index - 1)

  14. }).exec()

  15. },

  16. // ...

點擊右側字母索引欄的字母,城市列表自動滑動使得對應字母標題可視。

滿足這個需求場景,可以利用 <scroll-view> 組件的 scroll-into-view 屬性,由於已有拼音首字母標題添加標誌( id),只需將當前點擊的字母對應的元素 id滾動到可視即可。需要註意:

  • 頻繁 setData 造成性能問題,在這裏過濾重復賦值;

  • 由於設置了 <scroll-view> 為動畫滾動效果,滾動到標誌元素位置需要時間,途中可能會經過其它標誌元素,不能立即設置索引焦點,要有一定延時(還沒找到其它好解決方案,暫時這樣)

  1. // 點擊索引條

  2. tapIndexItem (event) {

  3. let id = event.currentTarget.dataset.item

  4. this.setData({

  5. scrollIntoViewId: `title_${id === ‘#‘ ? 0 : id}`

  6. })

  7. // 延時設置索引條焦點

  8. setTimeout(() => {

  9. this.setData({

  10. barIndex: this.data.indexList.findIndex((item) => item === id)

  11. })

  12. }, 500)

  13. },

技術分享圖片

頻繁觸發節流處理

頻繁輸入,或者頻繁滾動,回調觸發會造成性能問題,而其接口也有限定調用頻率,這樣就需要做節流處理。節流是再頻繁觸發的情況下,在大於一定時間間隔才允許觸發。

  1. // 節流

  2. const throttle = function(fn, delay) {

  3. let lastTime = 0

  4. return function () {

  5. let nowTime = Date.now()

  6. if (nowTime - lastTime > delay || !lastTime) {

  7. fn.apply(this, arguments)

  8. lastTime = nowTime

  9. }

  10. }

  11. }

具體對一些場景,比如騰訊位置服務提供的關鍵字搜索地址,就限定5次/key/秒,很容易就超了,可以做節流處理。

  1. Page({

  2. // ...

  3. // 輸入搜索關鍵字

  4. input: util.throttle(function () {

  5. let val = arguments[0].detail.value

  6. if (val === ‘‘) {

  7. this.setData({

  8. suggList: []

  9. })

  10. this.changeSearchCls()

  11. return false

  12. }

  13. api.getSuggestion({

  14. keyword: val

  15. })

  16. .then((res) => {

  17. this.setData({

  18. suggList: res

  19. })

  20. this.changeSearchCls()

  21. })

  22. .catch((err) => {

  23. console.error(err)

  24. })

  25. }, 500),

  26. // ...

  27. })

技術分享圖片

對於上面城市列表滾動,獲取標誌元素位置也應用節流處理。

總結

小程序的基本入門學習門檻不高,小程序的設計應該借鑒了很多現在流行的框架,如果有 ReactVue 的基礎會有很多似曾相識的感覺,當然,在深入的探索過程還有很多“坑”要跨越,本文只是簡單的梳理,具體問題還能多看文檔和小程序社區,還有什麽錯誤歡迎指正哈,完~

從入門到上線一個天氣小程序