微信小程式最近很火,火到什麼程度,只要你一打開微信,就是它的身影,幾乎你用的各個APP都可以在微信中找到它的複製版,另外官方自帶的跳一跳更是將它推到了空前至高的位置。對比公眾號,就我的感覺來說,有以下區別: 公眾號略顯繁瑣:我首先要關註才能看到內容,而小程式不用(個人對微信公眾號研究不深,不對之處還 ...
微信小程式最近很火,火到什麼程度,只要你一打開微信,就是它的身影,幾乎你用的各個APP都可以在微信中找到它的複製版,另外官方自帶的跳一跳更是將它推到了空前至高的位置。對比公眾號,就我的感覺來說,有以下區別:
- 公眾號略顯繁瑣:我首先要關註才能看到內容,而小程式不用(個人對微信公眾號研究不深,不對之處還望見諒)
- 小程式性能要好一些:雖然我不是很清楚小程式用什麼實現,就體驗來說確實更接近原生一點;但是微信公眾號是用網頁的形式來展示內容的,其中的相容性和性能問題不用我說,各位luer就已經清楚了吧
- 小程式更易開發:小程式發佈了一套新的代碼規則,也提供了一系列的組件,對比公眾號百家爭鳴的形式確實要統一得多
廢話說了這麼多,我也是最近才開始看小程式的實現方式,體驗了一把,確實比較爽,以下就是個人開發總結:
簡易的官網小程式
微信小程式官網中有個簡單的小demo,地址在這裡:https://mp.weixin.qq.com/debug/wxadoc/dev/index.html,按照它的步驟來,一定是可以運行一個和官方一樣的例子出來的,這裡就不貼過程了。主要說一下個人整體感受:
- js還是原來的js,css還是原來的css,html方面來說,是改了一點東西,比如:div變成了view,文本變成了text,以及img變成了image,但是換湯不換藥,該怎麼用還是怎麼用,而且語義也更加明確。
- 增加了配置文件
.json
,全局有一個app.json
,是全局的配置,比如導航欄、TAB的配置,全局路由的配置等等,而在每個頁面中,依然是可以進行全局覆蓋的,比如list.json
中單獨規定了列表頁面長啥樣子。 - 每個頁面都具有生命周期(包括啟動頁),類似於
react/vue
的聲明周期,更加明確在哪個階段可以做哪些事情 - 代碼組件化,很多封裝的組件都可以簡單引用,比如
map
,而在微信公眾號上開發的時候,你可能還需要專門寫一個地圖插件 - API更加好用,雖然我沒多少開發過公眾號,但是就之前配置的jssdk來說,就感覺比小程式複雜,小程式只需要一個appId就可以了,然後在代碼中直接使用
wx
對象來調用各種API
開發一個類似微信UI的簡單聊天程式
只是感興趣稍微做了一下案例,其中功能可能根本就還只是九牛一毛,但是覺得有必要記錄一下,說說自己遇到的問題以及解決辦法,界面整體如下:
首先,在app.json
中編寫頁面路由,如下:
{
"pages":[
"pages/index/index",
"pages/list/list",
"pages/chat/chat"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#000",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"#fff"
}
}
這裡有3個頁面,首頁放一個按鈕作為入口,列表頁表示聊天記錄,還有一個聊天頁。
列表頁沒有什麼可以講的,設置列表頁的標題可以在list.json
中設置即可,如下:
// list.json
{
"navigationBarTitleText": "聊天列表"
}
列表頁模擬了一些數據,然後再點擊每一條的時候,進入單個聊天頁面當中,其中需要將當前點擊的一些信息傳入下一個頁面當中,這裡僅僅只有名字。
//chat.js
//獲取應用實例
const app = getApp()
const friends = require('./list-mock-data.js')
Page({
data: {
friends: friends.list
},
gotoChat(event) {
const currentUser = event.currentTarget.dataset.user;
wx.navigateTo({
url: '../chat/chat?nickname=' + currentUser.nickname
})
}
})
然後進入聊天頁面,首先進入聊天頁面我想到的是,每一個氣泡加上它的頭像是否可以做成一個組件,因為只有左右的區分而已,另外如果再加上時間的話,再將時間傳遞過去就可以了。
因此chat.wxml
最開始就是這樣規劃的:
<block wx:for="{{ messages }}" wx:key="messages{{ index }}" >
<template id="{{ item.id }}" is="bubble" data="{{ ...item }}" />
</block>
template
中的代碼就不展示了,最開始我寫模板的時候,是開了一個codePen
,然後模擬寫出來之後,再往模板中套,保證基本的樣子差不多,然後再在模板上進行細微的改動就可以了。
聊天頁頂部的標題是通過列表頁中傳過來的,在頁面載入完成的時候,設置就好了:
// chat.js
// 設置昵稱
setNickName(option) {
const nickname = option.nickname || 'Marry';
wx.setNavigationBarTitle({
title: nickname
});
},
最開始的樣子就是這樣子的:
至此,基本的頁面形態就已經完成了。
遇到的一些問題:
- 每次進入頁面的時候,即使聊天內容已經超過了聊天區域,都會顯示為最開始的地方
- 輸入新的聊天記錄的時候,如果聊天內容不是處於最底部,那麼新加的內容會看不到
針對這兩個問題,我按照自己最初的想法是:進入頁面獲取scrollHieght
然後計算scrollTop
值,將其滾動就好了,至於第二個問題按照類似的方法就可以解決了,但是我查看小程式的API之後,並沒有發現如何計算scrollHeight
的方法。只有類似的API,如:boundingClientRect
和scrollTop
好在天無絕人之路,看到了scroll-view
中的scroll-into-view
屬性,於是就想出瞭解決上面兩個問題的方法:
- 進入頁面,獲取歷史紀錄,獲取最後一條消息的
ID
值,記為lastId
,在渲染的時候,消息列表中的每個ID值傳入組件,作為每個消息記錄的唯一標識,然後使用scroll-in-view={{ id }}
就可以輕鬆地使最後一條消息進入視野當中 - 在聊天的時候,新加的記錄會更新這個
lastId
值,這樣就自動更新視圖了
// chat.wxml
<scroll-view
scroll-y
scroll-with-animation
class="chat-content"
scroll-top="{{ scrollTop }}"
scroll-into-view="{{ lastId }}">
<block wx:for="{{ messages }}" wx:key="messages{{ index }}" >
<template id="msg{{ index }}" is="bubble" data="{{ ...item }}" />
</block>
</scroll-view>
// chat.js
Page({
data: {
messages: [], // 聊天記錄
msg: '', // 當前輸入
lastId: '' // 最後一條消息的ID
// ...
},
// ...
send() {
// ...
const data = {
id: `msg${++nums}`,
message: msg,
messageType: 0,
url: '../../images/5.png'
};
this.setData({ msg: '', lastId: data.id });
}
});
這樣就可以大致實現類似於聊天的效果了,但是還有一個小問題,每次從列表中進入單個聊天頁面的時候,會有一個斜向左上方滑動的過程,原因是:頁面的轉場動畫是向左的,但是自動滾動到最後一條記錄的動作是向上的,所以會有動作疊加,既然這樣,我只需要讓滾動的過程延遲一段時間就好
// 延遲頁面向頂部滑動
delayPageScroll() {
const messages = this.data.messages;
const length = messages.length;
const lastId = messages[length - 1].id;
setTimeout(() => {
this.setData({ lastId });
}, 300);
},
至此問題就算是解決了,在真機模擬的時候,IOS還有一個問題,就是當點擊輸入框的時候,整體頁面會向上頂起來,這個問題我在論壇中也有看到,但是沒有找到解決辦法,如果各位有遇到,還望不吝賜教。
擴展延伸
如果是一個真正的聊天程式應該怎麼做呢?我的設想是這樣的:
由於當時自己的機器由於莫名的原因不能夠進行登錄,後來採用了本地開了一個websocket
的伺服器來實現消息的發送。伺服器代碼相當簡單,只是消息的轉發而已
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 12112 });
wss.on('connection', ws => {
console.log('connection established');
ws.on('message', message => {
console.log("on message coming");
ws.send(message);
});
});
在chat.js
中需模擬歷史消息的發送以及新加消息的發送,因此代碼整體看起來是這樣的:
//chat.js
//獲取應用實例
const app = getApp()
const msgs = require('./chat-mock-data.js');
Page({
data: {
messages: [], // 聊天記錄
msg: '', // 當前輸入
scrollTop: 0, // 頁面的滾動值
socketOpen: false, // websocket是否打開
lastId: '', // 最後一條消息的ID
isFirstSend: true // 是否第一次發送消息(區分歷史和新加)
},
onLoad(option) {
// 設置標題
this.setNickName(option);
},
//事件處理函數
onReady() {
// 連接websocket伺服器
this.connect();
},
onUnload() {
const socketOpen = this.data.socketOpen;
if (socketOpen) {
wx.closeSocket({});
wx.onSocketClose(res => {
console.log('WebSocket 已關閉!')
});
}
},
connect() {
wx.connectSocket({
url: 'ws://localhost:12112'
});
wx.onSocketOpen(res => {
this.setData({ socketOpen: true });
// 模擬歷史消息的發送
wx.sendSocketMessage({
data: JSON.stringify(msgs),
})
});
wx.onSocketMessage(res => {
const isFirstSend = this.data.isFirstSend;
const data = JSON.parse(res.data);
let messages = this.data.messages;
let lastId = '';
// 第一次為接收歷史消息,
// 之後的為新加的消息
if (isFirstSend) {
messages = messages.concat(data);
lastId = messages[0].id;
this.setData({ messages, lastId, isFirstSend: false });
// 延遲頁面向頂部滑動
this.delayPageScroll();
} else {
messages.push(data);
const length = messages.length;
lastId = messages[length - 1].id;
this.setData({ messages, lastId });
}
});
wx.onSocketError(res => {
console.log(res);
console.log('WebSocket連接打開失敗,請檢查!')
})
},
// 設置昵稱
setNickName(option) {
const nickname = option.nickname || 'Marry';
wx.setNavigationBarTitle({
title: nickname
});
},
// 延遲頁面向頂部滑動
delayPageScroll() {
const messages = this.data.messages;
const length = messages.length;
const lastId = messages[length - 1].id;
setTimeout(() => {
this.setData({ lastId });
}, 300);
},
// 輸入
onInput(event) {
const value = event.detail.value;
this.setData({ msg: value });
},
// 聚焦
onFocus() {
this.setData({ scrollTop: 9999999 });
},
// 發送消息
send() {
const socketOpen = this.data.socketOpen;
let messages = this.data.messages;
let nums = messages.length;
let msg = this.data.msg;
if (msg === '') {
return false;
}
const data = {
id: `msg${++nums}`,
message: msg,
messageType: 0,
url: '../../images/5.png'
};
this.setData({ msg: '' });
if (socketOpen) {
wx.sendSocketMessage({
data: JSON.stringify(data)
})
}
}
})
整體來說,自己的思路就像是上面的代碼所描述的,這個只是初步的構想,還有很多東西需要完善:
- 頭像
- 列表頁和聊天頁新消息的處理
- 資料庫的歷史消息存儲
- 圖片以及語音的發送
- 消息本地化存儲
這些問題對於剛接觸的我來說,還需要一點時間來消化,暫且就貼這麼多吧。