去年年底 o2 開源了 Taro,一直手癢癢沒去玩。考慮到 wx 的稽核制度,所以決定寫個工具類小程式。趕在 Taro 喜提第 2000 個 issues 之際,Natsuha 也終於上線了。原始碼全部釋出(除涉及私鑰部分,GitHub有說明),文章後面會貼出一些仍需優化的點,歡迎大家一起討論。
前言
Natsuha Weather 已開源,歡迎大家一起折騰,給個 star 也是極好的~ GitHub Repo
技術棧是 Taro + mobx + TypeScript
,介面來自 Yahoo Weather API,當然設計也是參 (chao) 考 (xi) 的 Yahoo Weather. 介面有時會訪問失敗,尤其是晚上,我也沒辦法啊。?
功能
- 下拉重新整理
- 華氏溫度、攝氏溫度切換
- 分時展示一天的天氣預報
- 展示未來10天的天氣預報
- 展示當前風向、風速
- 展示日出日落、月相等資訊
- 展示一天內的降水預報
- 城市天氣檢索
- 顯示檢索記錄
踩坑
小程式篇
雲開發解決 Bei An 問題
由於眾所周知的原因,wx 小程式無法呼叫未 bei an 的介面,哪怕是在開發環境。所以我們用雲開發的雲函式來 “反代” 介面,下面通過一個例子說一下技術要點。
首先在根目錄的 project.config.json
檔案裡新增 "cloudfunctionRoot": "functions/"
,然後在根目錄建立資料夾 functions
. 並點選右鍵建立一個新的雲函式,比如我們叫 getRegion
。
因為我們的目標是通過雲函式請求一個未 bei an 的介面,所以為了更方便的處理非同步請求,我們引入 request-promise
這個庫。
通過硬碟開啟
進入到這個雲函式的資料夾,然後安裝依賴:
yarn add request request-promise
複製程式碼
接下來我們在 index.js
中寫邏輯,直接上程式碼。雲函式通過 event
物件來獲取前端傳過來的引數,然後通過 Promise 物件將結果返回。這個例子中我們需要拿到region
,
// 雲函式入口檔案
const cloud = require('wx-server-sdk')
const rp = require('request-promise')
cloud.init()
exports.main = async (event, context) => {
const region = event.region;
const res = await rp({
method: 'get',
uri: `https://www.yahoo.com/news/_tdnews/api/resource/WeatherSearch;text=${region}`,
json: true
}).then((body) => {
return {
regionList: body
}
}).catch(err => {
return err;
})
return res;
}
複製程式碼
接下來是前端發請求了,注意這裡不能再用 Taro.request()
, 而是雲函式獨有的 wx.cloud.callFunction()
, 因為我現在的 Taro 版本尚未實現 Taro.cloud.callFunction()
,所以直接用 wx
打頭即可。
首先封裝一下 wx.cloud.callFunction()
,其實感覺什麼卵用?:
export const httpClient = (url: string, data: any) => new Promise((resolve, reject): void => {
wx.cloud.callFunction({
name: url,
data,
}).then(res => {
resolve(res.result);
}).catch(e => {
reject(e)
});
});
複製程式碼
然後我們在 store 裡面寫邏輯,這樣基本上就解決了資料請求的坑。
public getRegion = (text: string) => {
httpClient('getRegion', {
region: encodeURI(text),
})
.then((res: any) => {
runInAction(() => {
if (res.regionList) {
this.regionList = res.regionList;
}
});
})
.catch(() => {
setToast(toastTxt.cityFail);
});
};
複製程式碼
?題外話:因為當前版本尚未實現 Taro.cloud.callFunction()
,所以 lint 會報錯,雖然不影響使用,大家有什麼好的方法,可以說一下。
地理資訊授權問題
在這個專案裡,我們需要通過小程式拿到的經緯度來反查城市資訊,而小程式獲取經緯度需要使用者授權。這裡有個坑,當使用者拒絕授權後,小程式預設詢問授權的 dialog 在一段時間內不會重複彈出,所以我們必須手動將使用者引導到授權頁面。
以前小程式有個介面叫做 wx.openSetting()
,但 tx 把它廢掉了,現在只能讓使用者點選一個特定的按鈕。
為此我做了一個 modal,這裡貼出關鍵程式碼。
<Button openType='openSetting' onOpenSetting={() => this.onOpenSetting()}>
OK
</Button>
複製程式碼
首先我們必須給按鈕宣告 openType='openSetting'
,這樣當使用者點選了之後就會跳轉到設定頁面。
其次,我們需要在使用者離開授權頁面時,也就是點選了左上角那個返回按鈕時,再次去檢查一下使用者的授權情況。所以我們要新增
onOpenSetting={() => this.onOpenSetting()
,不得不吐槽這個事件命名,明明應該叫做 onLeaveSetting
才合理。
在 onOpenSetting()
方法中我們再次執行判斷使用者是否授權的方法,未授權的話接著彈 modal,否則放行請求相應的資料介面。
文字有些累,直接看圖。
無法用傳統方式清空文字框文字
當使用者關閉搜尋
dialog 時,文字框的文字應當被清空,所以一開始寫成下面的這樣,即點選關閉按鈕時將 inputValue = ''
,然而發現不行。
<Input
type='text'
value={inputValue}
placeholder='Enter City or ZIP code'
onInput={e => handleInputTextChange(e)}
/>
<Button onClick={() => hideSearchDialog()}>Close</Button>
複製程式碼
查了一下官方文件,必須將 Input
和 Button
包裹在一個 Form
下,且要給關閉按鈕加上 formType='reset'
,最後給 Form
新增 onReset
事件指向關閉 dialog 的方法。
<Form onReset={() => hideSearchDialog()}>
<Input
className={styles.input}
type='text'
placeholder='Enter City or ZIP code'
onInput={e => handleInputTextChange(e)}
/>
<Button formType='reset'>Close</Button>
</Form>;
複製程式碼
Taro篇
大多是編譯問題和它 webpack 配的問題,相應的我都提了 issue,有興趣的話可以跟進。
Taro 編譯會忽略模版兩個之間的空格
舉個例子,<Text>day - night</Text>
,可以正常編譯,頁面可以正常看到 day - night
,但是假如是變數,就會被編譯成 day- night
,注意,空格被吃掉了。
const day = 'day'
const night = 'night'
<Text>{day} - {night}</Text>
複製程式碼
我提了個 issue #2261,然並沒人鳥我,有興趣可以跟進一下。
ts 不能識別wx
因為用到了雲開發,而 Taro 現階段還沒有Taro.cloud(...)
,所以在使用原生的wx.cloud(...)
時,
ts 肯定會報錯。
css module 等靜態檔案 找不到路徑
一開始用的import
來引入靜態檔案,但報“找不到路徑”,可以看下圖(但不影響使用)。提了個 issue #2213,
按照大佬的回覆修改也沒解決問題,實在受不了一片紅,索性改成了commonJS
.
Problem
下面是專案中存在的一些問題,有興趣的話歡迎大家一起討論。
圖片載入不友好
介面圖片的 url 來自aws
,因為眾所周知的原因,圖片經常會掛掉,
所以有必要在圖片掛掉的時候觸發onError
事件,然後給使用者一個提示。
因為小程式不支援new Image()
,所以只能用官方提供的Image
元件,幸好這個
元件支援onLoad
和onError
事件。
載入失敗的問題解決了,但因為aws
的速度太慢,所以正常載入時也很不友好(可以自行體會)
做了一些嘗試,比如先載入縮圖,再展示完整圖片,但介面提供的最小尺寸的圖片也已經達到了 70 多 k,並且該死的 Yahoo 恰好將圖片 url 控制大小的那段用了加密,所以這個方式 pass 掉了。
搜尋輸入框加個節流
現在的做法是在 store 的 構造器加個節流,但不知道這樣合不合理。
construtor() {
this.getRegion = _.debounce(this.getRegion, 150);
}
複製程式碼
TODO
- 國際化
- 效能優化
- 圖片載入優化
- Jest 搞起來(初始化已搭好)
- Travis CI 搞起來(初始化已搭好)
- 將搜尋模組放到一個新頁面(強行加個路由?)
最後
老子再也不寫小程式了!