在微信小程序中实现数据绑定、自动更新(一)

2022-08-06  本文已影响0人  明ZeY

我们在开发微信小程序通常会因为实例和数据池的数据推送而烦恼
通俗的说这样一个痛点就是,什么时候setData,在哪setData,怎么把实例的数据中取出来进行setData

那么有没有一种方法
当我们对变量或实例修改后,就可以自动的渲染界面而不用再setData呢?
当然有!

分析需求测试可行性

在微信小程序中

Page({
data:{
    //存放数据
    test:{
        content:"helloworld"
    }
}
})
<block>{{test.content}}</block>

即可显示出“helloworld”

这表示前端页面对数据具备多层访问的能力

继续,如果是访问一个不存在的数据呢

<block>{{test.content2.nodata}}</block>

我们将得到一个为空的页面,并没有任何报错,即是具备访问安全性的

那么我们把一个对象放在data中也能正常访问吗?经过测试这也是可行的。
也就是说我们完全可以把任何Object放在data中。

通过阅读微信小程序的开发文档,我们可以知道

如果你放置一个对象在data中,在前端进行渲染时会进行 JSON.stringify + JSON.parse 对数据进行一次操作后再安全的引用,但不会修改源数据

所以,如果我们在data中放置一个类,也是能被正常引用的

class Person{
    constructor(name){
        this.name = name;
    }
}

let myPerson = new Person("张三");
let pageInstance;
Page({
  data:{
      person:myPerson
  },
  onLoad(){
    pageInstance = this;
  }
})
<block>{{person.name}}</block>

前端取值时等同于

JSON.parse(JSON.stringify(data)).?person.?name => "张三"

其中.?表示安全取值

那么如果需要修改名字则需要

myPerson.name = "李四";
pageInstance.setData({
  person:myPerson
})

总结需求

我们需要在设置名字的同时就能够立即响应到页面,也就是说

myPerson.name = "李四";//的同时,页面就得到了更新

解决方案 Proxy + 语法欺骗

代码部分

我们先来看一下经过修改后的实例

// pages/myPage/myPage.js

const { EasyPage, LaunchEnv, EasyModule } = require("../../lib/EasyAPP/EasyAPP");

class Person{
  constructor(name){
    this.name = name;

    //语法欺骗,同时为该类进行链式(深度)监听
    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()来启动页面的方式
通过定义Mine对象,来设置页面的事件。

再通过new LaunchEnv()来生成界面,并定义它的的数据,而这次,我们简单明了的传入了一个Person的对象
如果此时任何引用去修改person的name属性,都将自动发起界面的更新
同时,在微信小程序的热更新下,这种链接性也不会丢失。

如此一来,我们可以将编程模式和前端数据“完全隔离”开来,而不必将功能混杂在各个页面中,我们只需潜心开发我们的业务相关的类,在任何时候,任何地方甚至控制台,都能修改数据并显示在屏幕上。
前端需要什么数据直接访问即可。

位于"../../lib/EasyAPP/EasyAPP"下的代码EasyAPP.js
在之后的章节中,将对代码进行功能刨析


// Auther MingZeY
// Email: 1552904342@qq.com

/**
 * 微信小程序自带的一些APP方法提示接口
 */
class AppInterface{
  /**
   * 生命周期回调——监听小程序初始化。  
   * 小程序初始化完成时触发,全局只触发一次。参数也可以使用 wx.getLaunchOptionsSync 获取。  
   * 参数:与 wx.getLaunchOptionsSync 一致  
   */
  onLaunch(obj){}

  /**
   * 生命周期回调——监听小程序启动或切前台。  
   * 小程序启动,或从后台进入前台显示时触发。也可以使用 wx.onAppShow 绑定监听。  
   * 参数:与 wx.onAppShow 一致  
   */
  onShow(obj){}

  /**
   * 生命周期回调——监听小程序切后台。  
   * 小程序从前台进入后台时触发。也可以使用 wx.onAppHide 绑定监听。  
   */
  onHide(){}

  /**
   * 错误监听函数。  
   * 小程序发生脚本错误或 API 调用报错时触发。也可以使用 wx.onError 绑定监听。  
   * 参数:与 wx.onError 一致  
   */
  onError(error){}

  /**
   * 页面不存在监听函数。  
   * 1.9.90  
   * 小程序要打开的页面不存在时触发。也可以使用 wx.onPageNotFound 绑定监听。注意事项请参考 wx.onPageNotFound。  
   * 参数:与 wx.onPageNotFound 一致  
   */
  onPageNotFound(obj){}

  /**
   * 未处理的 Promise 拒绝事件监听函数。  
   * 2.10.0  
   * 小程序有未处理的 Promise 拒绝时触发。也可以使用 wx.onUnhandledRejection 绑定监听。注意事项请参考 wx.onUnhandledRejection。  
   * 参数:与 wx.onUnhandledRejection 一致  
   */
  onUnhandledRejection(obj){}

  /**
   * 监听系统主题变化  
   * 2.11.0  
   * 系统切换主题时触发。也可以使用 wx.onThemeChange 绑定监听。  
   * 参数:与 wx.onThemeChange 一致  
   */
  onThemeChange(obj){}
}

/**
 * 微信小程序自带的一些Page方法提示接口
 */
class PageInterface{

  data(){}

  /**
   * 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。  
   */
  onLoad(query){}

  onShow(){}

  onReady(){}

  onHide(){}

  onUnload(){}

  onPullDownRefersh(){}

  onReachBottom(){}

  onShareAppMessage(){}

  onShareTimeline(){}

  onAddToFavorites(){}

  onPageScroll(){}

  onResize(){}

  onTabItemTap(){}

  onSaveExitState(){}

}

/**
 * APP 主类
 */
class EasyAPP extends AppInterface{

  constructor(){
    super();
  }


  /**
   * 创建页面,但不创建Page实例,只有onLoad事件触发后才能有相关操作
   */
  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 = {};

    //载入数据
    Object.assign(that.dataContiner,passData);
    
    Object.assign(config,{
      data:that.dataContiner
    })

    //载入方法
    let keys = Object.getOwnPropertyNames(Object.getPrototypeOf(that));

    if(keys.indexOf("onLoad") == -1){
      //修复没有Load无法刷新界面的问题
      keys.push("onLoad");
    }

    for(let key of keys){
      if(key == "onLoad"){
        config[key] = function(...args){
          that.pageInstance = this;
          //TODO 更新/挂载数据
          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 对象实例未生成!更新界面失败,操作该页面以重新获得 Page 对象`);
      return;
    }
    Object.assign(this.dataContiner,data);
    this.pageInstance.setData(data);
  }
}

/**
 * 数据虚拟空间对象
 */
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("循环访问对象,已拒绝");
                  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);


    //绑定数据更新事件
    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;

上一篇下一篇

猜你喜欢

热点阅读