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

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

作者: 明ZeY | 来源:发表于2022-08-06 22:13 被阅读0次

    我们在开发微信小程序通常会因为实例和数据池的数据推送而烦恼
    通俗的说这样一个痛点就是,什么时候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 + 语法欺骗

    • 我们可以通过Proxy对类的实例进行链式(深度)监听,并生成事件响应来进行setData
    • 但Proxy会造成类的实例方法提示功能丢失,并不方便Coding,所以需要进行构造函数的语法欺骗

    代码部分

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

    // 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;
    
    

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/tqmvwrtx.html