我們?cè)陂_發(fā)微信小程序通常會(huì)因?yàn)閷?shí)例和數(shù)據(jù)池的數(shù)據(jù)推送而煩惱
通俗的說(shuō)這樣一個(gè)痛點(diǎn)就是啃匿,什么時(shí)候setData溯乒,在哪setData裆悄,怎么把實(shí)例的數(shù)據(jù)中取出來(lái)進(jìn)行setData
那么有沒有一種方法
當(dāng)我們對(duì)變量或?qū)嵗薷暮蠊饧冢涂梢宰詣?dòng)的渲染界面而不用再setData呢艾君?
當(dāng)然有!
分析需求測(cè)試可行性
在微信小程序中
Page({
data:{
//存放數(shù)據(jù)
test:{
content:"helloworld"
}
}
})
<block>{{test.content}}</block>
即可顯示出“helloworld”
這表示前端頁(yè)面對(duì)數(shù)據(jù)具備多層訪問(wèn)
的能力
繼續(xù)虹茶,如果是訪問(wèn)一個(gè)不存在的數(shù)據(jù)呢
<block>{{test.content2.nodata}}</block>
我們將得到一個(gè)為空的頁(yè)面写烤,并沒有任何報(bào)錯(cuò)洲炊,即是具備訪問(wèn)安全
性的
那么我們把一個(gè)對(duì)象放在data中也能正常訪問(wèn)嗎?經(jīng)過(guò)測(cè)試這也是可行的狂巢。
也就是說(shuō)我們完全可以把任何Object
放在data中唧领。
通過(guò)閱讀微信小程序的開發(fā)文檔斩个,我們可以知道
如果你放置一個(gè)對(duì)象在data中,在前端進(jìn)行渲染時(shí)會(huì)進(jìn)行
JSON.stringify + JSON.parse
對(duì)數(shù)據(jù)進(jìn)行一次操作后再安全
的引用做个,但不會(huì)修改源數(shù)據(jù)
所以居暖,如果我們?cè)赿ata中放置一個(gè)類,也是能被正常引用的
class Person{
constructor(name){
this.name = name;
}
}
let myPerson = new Person("張三");
let pageInstance;
Page({
data:{
person:myPerson
},
onLoad(){
pageInstance = this;
}
})
<block>{{person.name}}</block>
前端取值時(shí)等同于
JSON.parse(JSON.stringify(data)).?person.?name => "張三"
其中
.?
表示安全取值
那么如果需要修改名字則需要
myPerson.name = "李四";
pageInstance.setData({
person:myPerson
})
總結(jié)需求
我們需要在設(shè)置名字的同時(shí)就能夠立即響應(yīng)到頁(yè)面跟束,也就是說(shuō)
myPerson.name = "李四";//的同時(shí)灭贷,頁(yè)面就得到了更新
解決方案 Proxy + 語(yǔ)法欺騙
- 我們可以通過(guò)Proxy對(duì)類的實(shí)例進(jìn)行鏈?zhǔn)剑ㄉ疃龋┍O(jiān)聽仗岖,并生成事件響應(yīng)來(lái)進(jìn)行setData
- 但Proxy會(huì)造成類的實(shí)例方法提示功能丟失轧拄,并不方便Coding,所以需要進(jìn)行構(gòu)造函數(shù)的語(yǔ)法欺騙
代碼部分
我們先來(lái)看一下經(jīng)過(guò)修改后的實(shí)例
// pages/myPage/myPage.js
const { EasyPage, LaunchEnv, EasyModule } = require("../../lib/EasyAPP/EasyAPP");
class Person{
constructor(name){
this.name = name;
//語(yǔ)法欺騙,同時(shí)為該類進(jìn)行鏈?zhǔn)剑ㄉ疃龋┍O(jiān)聽
return new EasyModule(this);
}
}
class MyPage extends EasyPage{
async onShow() {//同微信小程序自帶的onShow方法
if(await Platform.isLogin_sync()){
if(Platform.getUser().credits == undefined){
//獲取用戶Credits
Platform.getUser().getCredits();
}
}
}
}
new LaunchEnv({
page:new MyPage (),
data:{
Person:new Person("張三");
}
})
可以看到俐末,我們替換了微信原生的使用Page()
來(lái)啟動(dòng)頁(yè)面的方式
通過(guò)定義Mine對(duì)象奄侠,來(lái)設(shè)置頁(yè)面的事件。
再通過(guò)new LaunchEnv()
來(lái)生成界面烹卒,并定義它的的數(shù)據(jù)旅急,而這次敏沉,我們簡(jiǎn)單明了的傳入了一個(gè)Person的對(duì)象
如果此時(shí)任何引用去修改person的name屬性盟迟,都將自動(dòng)發(fā)起界面的更新
同時(shí)攒菠,在微信小程序的熱更新下辖众,這種鏈接性也不會(huì)丟失。
如此一來(lái)啤它,我們可以將編程模式和前端數(shù)據(jù)“完全隔離”開來(lái),而不必將功能混雜在各個(gè)頁(yè)面中塌碌,我們只需潛心開發(fā)我們的業(yè)務(wù)相關(guān)的類台妆,在任何時(shí)候频丘,任何地方甚至控制臺(tái)搂漠,都能修改數(shù)據(jù)并顯示在屏幕上而克。
前端需要什么數(shù)據(jù)直接訪問(wèn)即可员萍。
位于"../../lib/EasyAPP/EasyAPP"
下的代碼EasyAPP.js
在之后的章節(jié)中,將對(duì)代碼進(jìn)行功能刨析
// Auther MingZeY
// Email: 1552904342@qq.com
/**
* 微信小程序自帶的一些APP方法提示接口
*/
class AppInterface{
/**
* 生命周期回調(diào)——監(jiān)聽小程序初始化筋帖。
* 小程序初始化完成時(shí)觸發(fā),全局只觸發(fā)一次代箭。參數(shù)也可以使用 wx.getLaunchOptionsSync 獲取嗡综。
* 參數(shù):與 wx.getLaunchOptionsSync 一致
*/
onLaunch(obj){}
/**
* 生命周期回調(diào)——監(jiān)聽小程序啟動(dòng)或切前臺(tái)。
* 小程序啟動(dòng),或從后臺(tái)進(jìn)入前臺(tái)顯示時(shí)觸發(fā)沟涨。也可以使用 wx.onAppShow 綁定監(jiān)聽。
* 參數(shù):與 wx.onAppShow 一致
*/
onShow(obj){}
/**
* 生命周期回調(diào)——監(jiān)聽小程序切后臺(tái)棋返。
* 小程序從前臺(tái)進(jìn)入后臺(tái)時(shí)觸發(fā)。也可以使用 wx.onAppHide 綁定監(jiān)聽射沟。
*/
onHide(){}
/**
* 錯(cuò)誤監(jiān)聽函數(shù)猖吴。
* 小程序發(fā)生腳本錯(cuò)誤或 API 調(diào)用報(bào)錯(cuò)時(shí)觸發(fā)海蔽。也可以使用 wx.onError 綁定監(jiān)聽。
* 參數(shù):與 wx.onError 一致
*/
onError(error){}
/**
* 頁(yè)面不存在監(jiān)聽函數(shù)刑然。
* 1.9.90
* 小程序要打開的頁(yè)面不存在時(shí)觸發(fā)。也可以使用 wx.onPageNotFound 綁定監(jiān)聽择镇。注意事項(xiàng)請(qǐng)參考 wx.onPageNotFound。
* 參數(shù):與 wx.onPageNotFound 一致
*/
onPageNotFound(obj){}
/**
* 未處理的 Promise 拒絕事件監(jiān)聽函數(shù)吝梅。
* 2.10.0
* 小程序有未處理的 Promise 拒絕時(shí)觸發(fā)。也可以使用 wx.onUnhandledRejection 綁定監(jiān)聽右冻。注意事項(xiàng)請(qǐng)參考 wx.onUnhandledRejection纱扭。
* 參數(shù):與 wx.onUnhandledRejection 一致
*/
onUnhandledRejection(obj){}
/**
* 監(jiān)聽系統(tǒng)主題變化
* 2.11.0
* 系統(tǒng)切換主題時(shí)觸發(fā)。也可以使用 wx.onThemeChange 綁定監(jiān)聽忆首。
* 參數(shù):與 wx.onThemeChange 一致
*/
onThemeChange(obj){}
}
/**
* 微信小程序自帶的一些Page方法提示接口
*/
class PageInterface{
data(){}
/**
* 頁(yè)面加載時(shí)觸發(fā)糙及。一個(gè)頁(yè)面只會(huì)調(diào)用一次浸锨,可以在 onLoad 的參數(shù)中獲取打開當(dāng)前頁(yè)面路徑中的參數(shù)柱搜。
*/
onLoad(query){}
onShow(){}
onReady(){}
onHide(){}
onUnload(){}
onPullDownRefersh(){}
onReachBottom(){}
onShareAppMessage(){}
onShareTimeline(){}
onAddToFavorites(){}
onPageScroll(){}
onResize(){}
onTabItemTap(){}
onSaveExitState(){}
}
/**
* APP 主類
*/
class EasyAPP extends AppInterface{
constructor(){
super();
}
/**
* 創(chuàng)建頁(yè)面表制,但不創(chuàng)建Page實(shí)例娜遵,只有onLoad事件觸發(fā)后才能有相關(guān)操作
*/
launch(){
let that = this;
let config = {};
let keys = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
for(let key of keys){
config[key] = function(){
that[key]();
}
}
App(config);
}
}
/**
* Page 主類
*/
class EasyPage extends PageInterface{
constructor(){
super();
this.pageInstance = undefined;
this.dataContiner = this.data() || {};
}
launch(passData = {}){
let that = this;
let config = {};
//載入數(shù)據(jù)
Object.assign(that.dataContiner,passData);
Object.assign(config,{
data:that.dataContiner
})
//載入方法
let keys = Object.getOwnPropertyNames(Object.getPrototypeOf(that));
if(keys.indexOf("onLoad") == -1){
//修復(fù)沒有Load無(wú)法刷新界面的問(wèn)題
keys.push("onLoad");
}
for(let key of keys){
if(key == "onLoad"){
config[key] = function(...args){
that.pageInstance = this;
//TODO 更新/掛載數(shù)據(jù)
this.setData(that.dataContiner);
that["onLoad"].apply(that,args);
}
continue;
}
if(key == "data"){
continue;
}
config[key] = function(...args){
that.pageInstance = this;
that[key].apply(that,args);
}
}
Page(config);
}
setData(data){
if(this.pageInstance == undefined){
// console.log(`[Warn] ${this.constructor.name} 的 Page 對(duì)象實(shí)例未生成!更新界面失敗久脯,操作該頁(yè)面以重新獲得 Page 對(duì)象`);
return;
}
Object.assign(this.dataContiner,data);
this.pageInstance.setData(data);
}
}
/**
* 數(shù)據(jù)虛擬空間對(duì)象
*/
class DataVM{
constructor(obj,listener = function(){}){
this.source = obj;
this.proxy = this.bind();
this.listeners = [listener];
return this.proxy;
}
update(t,k,v,r){
console.log("更新渲染界面!");
for(let f of this.listeners){
f(t,k,v,r);
}
}
bind(){
let that = this;
let getCache = [];
let handler = {
get:function(t,k,r){
if(k == "__DataVM"){
return that;
}
if(k == "__Refersh"){
that.update(t,k,undefined,r);
return;
}
if(k == "__NOTRACK"){
return t;
}
if(t == that.source){
getCache = [];
}
getCache.push(t[k]);
let isRepeat = function(o){
for(let i = 0; i < getCache.length-1; i++){
if(o instanceof Object && getCache[i] == o){
return true;
}
}
return false;
}
if(typeof t[k] == "object" && t[k].__DataVM == undefined){
if(isRepeat(t[k])){
// console.warn("循環(huán)訪問(wèn)對(duì)象相寇,已拒絕");
return undefined;
}
return reactive(t[k]);
}else{
return t[k];
}
},
set:function(t,k,v,r){
t[k] = v;
//喚起更新事件
that.update(t,k,v,r);
return true;
}
}
let reactive = function(obj){
return new Proxy(obj,handler);
}
return reactive(this.source);
}
getProxy(){
return this.proxy;
}
getSource(){
return this.source;
}
addListener(f){
this.listeners.push(f);
}
}
class EasyModule extends DataVM{}
class LaunchEnv{
constructor(obj){
let {
page,
data,
} = obj;
this.page = page;
this.data = data;
this.build();
}
async build(){
let page = this.page;
let data = this.data;
if(!page instanceof EasyPage){
throw Error();
}
page.launch(data);
//綁定數(shù)據(jù)更新事件
for(let name in data){
let targetEnv = data[name];
let dataBuilder = function(key,value){
let obj = {};
obj[key] = value;
return obj;
}
if(targetEnv.__DataVM instanceof DataVM){
let vm = targetEnv.__DataVM;
vm.addListener((target,key,value,reciver) => {
page.setData(dataBuilder(
name,
vm.getProxy()
))
})
}
}
}
}
module.exports.EasyAPP = EasyAPP;
module.exports.EasyPage = EasyPage;
module.exports.EasyModule = EasyModule;
module.exports.DataVM = DataVM;
module.exports.LaunchEnv = LaunchEnv;