Coder Social home page Coder Social logo

westore's Introduction

Westore - 小程序项目分层架构

  • Object-Oriented Programming: Westore 强制小程序使用面向对象程序设计,开发者起手不是直接写页面,而是使用职责驱动设计 (Responsibility-Driven Design)的方式抽象出类、类属性和方法以及类之间的关联关系。
  • Write Once, Use Anywhere(Model): 通过面向对象分析设计出的 Model 可以表达整个业务模型,开发者可移植 100% 的 Model 代码不带任何改动到其他环境,并使用其他渲染技术承载项目的 View,比如小程序WebView、小游戏、Web浏览器、Canvas、WebGL。
  • Passive View: Westore 架构下的 View 非常薄,没有参杂任何业务逻辑,只做被动改变。
  • Simple and Intuitive: Westore 内部使用 deepClone + dataDiff 换取最短路径 setData 和更符合直觉的编程体验,只需 update,不需要再使用 setData
  • Testability: View 和 Model 之间没有直接依赖,开发者能够借助模拟对象注入测试两者中的任一方。

Westore 架构和 MVP(Model-View-Presenter) 架构很相似:

  • View 和 Store 是双向通讯,View 和 Store 互相引用
  • View 与 Model 不发生联系,都通过 Store 传递
  • Store 引用 Model 里对象的实例,Model 不依赖 Store
  • View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性
  • Store 非常薄,只复杂维护 View 需要的数据和桥接 View 和 Model
  • Model 非常厚,所有逻辑都部署在那里,Model 可以脱离 Store 和 View 完整表达所有业务/游戏逻辑

Store 层可以理解成 中介者模式 中的中介者,使 View 和 Model 之间的多对多关系数量减少为 0,负责中转控制视图对象 View 和模型对象 Model 之间的交互。

随着小程序承载的项目越来越复杂,合理的架构可以提升小程序的扩展性和维护性。把逻辑写到 Page/Component 是一种罪恶,当业务逻辑变得复杂的时候 Page/Component 会变得越来越臃肿难以维护,每次需求变更如履薄冰, westore 定义了一套合理的小程序架构适用于任何复杂度的小程序,让项目底座更健壮,易维护可扩展。

安装

npm i westore --save

npm 相关问题参考:小程序官方文档: npm 支持

Packages

项目 描述
westore westore 的核心代码
westore-example westore 官方例子
westore-example-ts westore 官方例子(ts+scss)

举个例子

开发如下图所示的重命名 app

按照传统的小程序开发三部曲:

  • 写页面结构 wxml
  • 写页面样式 wxss
  • 写页面逻辑 js/ts

省略 wxml、wxss,js 如下:

Page({
  data: {
    nickName: ''
  },

  async onLoad() {
    const nickName = await remoteService.getNickName()
    this.setData({
      nickName: nickName
    })
  },

  async modifyNickName(newNickName) {
    await remoteService.modifyNickName(newNickName)
  },

  clearInput() {
    this.setData({
      nickName: ''
    })
  }
})

需求开发全部结束。

使用 Westore 重构

定义 User 实体:

class User {
  constructor({ nickName, onNickNameChange }) {
    this.nickName = nickName || ''
    this.onNickNameChange = onNickNameChange || function() { }
  }

  checkNickName() {
    // 省略 nickName 规则校验
  }

  modifyNickName(nickName) {
    if(this.checkNickName(nickName) && nickName !== this.nickName) {
      this.nickName = nickName
      this.onNickNameChange(nickName)
    }
  }
}

module.exports = User

定义 UserStore:

const { Store } = require('westore')
const User = require('../models/user')

class UserStore extends Store {
  constructor(options) {
    super()
    this.options = options
    this.data = {
      nickName: ''
    }
  }

  init() {
    const nickName = await remoteService.getNickName()
    this.user = new User({ 
      nickName,
      onNickNameChange: (newNickName)=>{
        this.data.nickName = newNickName
        this.update()
        await remoteService.modifyNickName(newNickName)
      } 
    })
  }

  async saveNickName(newNickName) {
    this.user.modifyNickName(newNickName)
  },

  modifyInputNickName(input) {
    this.data.nickName = input
    this.update()
  }
}

module.exports = new UserStore

页面使用 UserStore:

const userStore = require('../../stores/user-store')

Page({
  data: userStore.data,

  onLoad() {
    /* 绑定 view 到 store 
      也可以给 view 取名 userStore.bind('userPage', this)
      取名之后在 store 里可通过 this.update('userPage') 更新 view
      不取名可通过 this.update() 更新 view
    */
    userStore.bind(this)
  },

  saveNickName(newNickName) {
    userStore.saveNickName(newNickName)
  },

  onInputChange(evt) {
    userStore.modifyInputNickName(evt.currentTarget.value)
  },

  clearInput() {
    userStore.modifyInputNickName('')
  }
})

通用 Model 是框架无关的,对于这样简单的程序甚至不值得把这种逻辑分开,但是随着需求的膨胀你会发现这么做带来的巨大好处。所以下面举一个复杂一点点的例子。

贪吃蛇案例

游戏截图:

设计类图:

图中浅蓝色的部分可以在小程序贪吃蛇、小游戏贪吃蛇和Web贪吃蛇项目复用,不需要更改一行代码。

TodoApp 案例

应用截图:

设计类图:

图中浅蓝色的部分可以在小程序 TodoApp 和 Web TodoApp项目复用,不需要更改一行代码。

官方案例

官方例子把贪吃蛇TodoApp做进了一个小程序目录如下:

├─ models    // 业务模型实体
│   └─ snake-game
│       ├─ game.js
│       └─ snake.js   
│  
│  ├─ log.js
│  ├─ todo.js   
│  └─ user.js   
│
├─ pages     // 页面
│  ├─ game
│  ├─ index
│  ├─ logs   
│  └─ other.js  
│
├─ stores    // 页面的数据逻辑,page 和 models 的桥接器
│  ├─ game-store.js   
│  ├─ log-store.js      
│  ├─ other-store.js    
│  └─ user-store.js   
│
├─ utils

详细代码点击这里

扫码体验:

原理

setData 去哪了?

回答 setData 去哪了? 之前先要思考为什么 westore 封装了这个 api,让用户不直接使用。在小程序中,通过 setData 改变视图。

this.setData({
  'array[0].text':'changed text'
})

但是符合直觉的编程体验是:

this.data.array[0].text = 'changed text'

如果 data 不是响应式的,需要手动 update:

this.data.array[0].text = 'changed text'
this.update()

上面的编程体验是符合直觉且对开发者更友好的。所以 westore 隐藏了 setData 不直接暴露给开发者,而是内部使用 diffData 出最短更新路径,暴露给开发者的只有 update 方法。

Diff Data

先看一下 westore diffData 的能力:

diff({
    a: 1, b: 2, c: "str", d: { e: [2, { a: 4 }, 5] }, f: true, h: [1], g: { a: [1, 2], j: 111 }
}, {
    a: [], b: "aa", c: 3, d: { e: [3, { a: 3 }] }, f: false, h: [1, 2], g: { a: [1, 1, 1], i: "delete" }, k: 'del'
})

Diff 的结果是:

{ "a": 1, "b": 2, "c": "str", "d.e[0]": 2, "d.e[1].a": 4, "d.e[2]": 5, "f": true, "h": [1], "g.a": [1, 2], "g.j": 111, "g.i": null, "k": null }

diff

Diff 原理:

  • 同步所有 key 到当前 store.data
  • 携带 path 和 result 递归遍历对比所有 key value
export function diffData(current, previous) {
  const result = {}
  if (!previous) return current
  syncKeys(current, previous)
  _diff(current, previous, '', result)
  return result
}

同步上一轮 state.data 的 key 主要是为了检测 array 中删除的元素或者 obj 中删除的 key。

Westore 实现细节

提升编程体验的同时,也规避了每次 setData 都传递大量新数据的问题,因为每次 diff 之后的 patch 都是 setData 的最短路径更新。

所以没使用 westore 的时候经常可以看到这样的代码:

not-westore

使用完 westore 之后:

this.data.a.b[1].c = 'f'
this.update()

小结

从目前来看,绝大部分的小程序项目都把业务逻辑堆积在小程序的 Page 构造函数里,可读性基本没有,给后期的维护带来了巨大的成本,westore 架构的目标把业务/游戏逻辑解耦出去,Page 就是纯粹的 Page,它只负责展示和接收用户的输入、点击、滑动、长按或者其他手势指令,把指令中转给 store,store 再去调用真正的程序逻辑 model,这种分层边界清晰,维护性、扩展性和可测试性极强,单个文件模块大小也能控制得非常合适。

贡献者

License

MIT

westore's People

Contributors

aeroxy avatar cattussameer avatar chelestewang avatar cnlon avatar cyndra0 avatar damonare avatar dntzhang avatar dwightngan avatar gcaufy avatar gsbl avatar haozewu avatar iceapriler avatar jadexusq avatar means88 avatar minikey avatar nanhupatar avatar ppgee avatar rain20199 avatar rogerleung0411 avatar s3boy avatar strongcode9527 avatar xuemuyang avatar yunyoujun avatar zqjimlove avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

westore's Issues

diff data 与 diff 页面疑问(存在即使 data 的值一样,但也要更新组件状态的情况)

我目前的理解是:无论是用 setdata 还是 update,最后在虚拟 dom 里面,都要把整个页面生成出来,再跟前一刻的状态来 diff,最后更新为新视图(react.js 是如此),所以即使 setdata diff 了一下,那些相同的数据被 pass,但最后是否都要重新 render 整个page 在虚拟 dom 里面??

还有一个很重要的问题是,这个很重要!!在表单组件里面,比如 switch 的 value 属性,即使每次 setdata 的时候,值是一样的,但都需要重新更新switch的状态的。场景是:switch 初始值为 true,用户手动改变了 switch 为 false,然后点击另外的按钮使得 switch 重置为默认 true,这个时候,在 this.update 来看,前后两次的值都为 true,就不会更新页面了

Array的重新赋值不会被捕捉到

以下不可行

var cache = [];
     observe(cache, function (name, value, old) {
            console.log(name + '__' + value + '__' + old);
        });
cache = [1]

这样可行:

var cache = {
      NoticeCache: []
}
observe(cache, function (name, value, old) {
            console.log(name + '__' + value + '__' + old);
        });

        cache.NoticeCache = [1, 2, 3];

在set和add函数中有无必要加入$observer.propertyChangedHandler的处理

var obj = {
  a: 1,
  b: 2
};
observe(obj, 'a', function (name, value, old) {
    console.log(name + "__" + value+"__"+old);
});
observe.set(obj, 'c', 2, true); // 并不会触发回调函数

解决方法:可将源码改成如下

    observe.set = function(obj, prop,value,exec) { 
        if(!exec){
            obj[prop] = value; 
        }
        var $observer = obj.$observer; 
        $observer.watch(obj, prop); 
        $observer.propertyChangedHandler.forEach(function(handler) {
            handler.eventPropArr.push(prop);
        })
        if(exec){
           obj[prop] = value;
        }
    };

不过这个有待商议哈

这种现象是正常现象吗?

原来的data 有一项 userInfo

我通过 store.data = new_data
new data 有一项 userInfo

通过update更新后
数据结构变成 这样

diff并没有把userInfo更新成空,而是把其中的每个属性都变成空了,我觉得这样逻辑是不对的,使用起来也有问题

关于使用接口的更友好建议

我最近发现小程序es6转译支持 Proxy,那么可不可以用Proxy来代理 Page的data属性?
直接在onload函数中这样写

   this.data = new Proxy(this.data, {
                get: (target, key, proxy) => {
                    console.log("get")
                    return target[key]
                },
                set: (target, key, value, proxy) => {
                    this.update({
                        [key]: value
                    })
                    return Reflect.set(target, key, value, proxy)
                }
            })

so

this.msg= "hello"

就可以更新数据.而不用反复的调用发放,至于又没有什么问题 我暂时还没看

Add demo gif to README

Disclaimer: This is a bot

It looks like your repo is trending. The github_trending_videos Instgram account automatically shows the demo gifs of trending repos in Github.

Your README doesn't seem to have any demo gifs. Add one and the next time the parser runs it will pick it up and post it on its Instagram feed. If you don't want to just close this issue we won't bother you again.

希望改进的

希望再页面中可以 用this.data.my_info = xxx 的方式修改 而不是 this.store.data.myinfo
this.update == store.update ?在页面中使用哪个好 还是一样的

新手有几个疑问😳

1、『Please using store.method to set method prop of data!』需要如何处理哈

我在组件里,如果没有设置pure:true 会出现这个提示,不知道有什么影响也不知道如何解决哈

2、还是不明白 pure:true的区别
我只明白,true的时候,data不会合并

仍可以修改this.store.data.xxx
却不能调用this.store.func(),调用某个store里的函数

3、组件里如果在lifetimesready生命周期的处理事件,不能够获取this.store的值
但是如果把ready拿到外面写就可以获取this.store

4、有一个很神奇的情况
页面获取数据,然后赋值
this.store.data["goodsList"] = [];
然后跳转到别的页面,在后退的时候,出现
image

但是如果使用setData就不会有这样情况

Update

这个情况,我发现复习场景是

页面A(有赋值data)=> redirectTo到页面B => redirectTo到页面A => 报错

但是如果是

页面A(有赋值data)=> navigateTo到页面B => redirectTo到页面A

就没问题

能否支持一些全局data及函数

使用场景:
例如用户信息(userInfo),信息这个基本上每个页面都用的到
而目前使用的是局部更新,就必须要在每个页面的data里再输入一次,有点繁琐

解决方案:
支持局部更新下
定义全局的data和函数

有个疑问 关于页面data 和storejs 的data

页面声明了data但是没有用,一定要在storejs 声明data吗`//index.js
//获取应用实例
import store from '../../store'
import create from '../../utils/create'
var app = getApp()
const http = require('wehttp')
create(store,{
data: {
islogin: false,
offline: false,
remind: '加载中',

cores: [
  [{
    id: 'kb',
    name: '课表查询',
    disabled: false,
    teacher_disabled: false,
    offline_disabled: false
  },
    /**{ id: 'cj', name: '成绩查询', disabled: false, teacher_disabled: true, offline_disabled: false },
    { id: 'ks', name: '考试安排', disabled: false, teacher_disabled: false, offline_disabled: false },
    { id: 'kjs', name: '空教室', disabled: false, teacher_disabled: false, offline_disabled: true },
    { id: 'xs', name: '学生查询', disabled: false, teacher_disabled: false, offline_disabled: true },
    { id: 'ykt', name: '一卡通', disabled: false, teacher_disabled: false, offline_disabled: false },
    { id: 'jy', name: '借阅信息', disabled: false, teacher_disabled: false, offline_disabled: false },
    { id: 'xf', name: '学费信息', disabled: false, teacher_disabled: true, offline_disabled: false },
    { id: 'sdf', name: '电费查询', disabled: false, teacher_disabled: true, offline_disabled: false },
    { id: 'bx', name: '物业报修', disabled: false, teacher_disabled: false, offline_disabled: true }
  ],[
      /**{ id: 'cet', name: '四六级', disabled: false, teacher_disabled: true, offline_disabled: true},
    { id: 'fw', name: "志愿活动", disabled: false, teacher_disabled: true, offline_disabled: false}**/
  ]
],
card: {
  'kb': {
    show: false,
    time_list: [{
      begin: '8:00',
      end: '8:45'
    },
    {
      begin: '8:55',
      end: '9:40'
    },
    {
      begin: '10:05',
      end: '10:50'
    },
    {
      begin: '11:00',
      end: '11:45'
    },
    {
      begin: '14:00',
      end: '14:45'
    },
    {
      begin: '14:55',
      end: '15:40'
    },
    {
      begin: '16:05',
      end: '16:50'
    },
    {
      begin: '17:00',
      end: '17:45'
    },
    {
      begin: '19:00',
      end: '19:45'
    },
    {
      begin: '19:55',
      end: '20:40'
    },
    {
      begin: '20:50',
      end: '21:35'
    },
    {
      begin: '21:45',
      end: '22:30'
    }
    ],
    data: {}
  },
  'ykt': {
    show: false,
    data: {
      'last_time': '',
      'balance': 0,
      'cost_status': false,
      'today_cost': {
        value: [],
        total: 0
      }
    }
  },
  'jy': {
    show: false,
    data: {}
  },
  'sdf': {
    show: false,
    data: {
      'room': '',
      'record_time': '',
      'cost': 0,
      'spend': 0
    }
  }
},
user: {},
disabledItemTap: false //点击了不可用的页面

},
//分享
onShareAppMessage: function () {
return {
title: 'We重邮',
desc: '碎片化、一站式、一体化校园移动门户',
path: '/pages/index/index'
};
},
//下拉更新
onPullDownRefresh: function () {
if (app._user.is_bind) {
this.getCardData();
} else {
wx.stopPullDownRefresh();
}
},
checkbind(userID) {

},
onShow: function () {
//this.update()
//var that = this
//this.store.data.cores = {}
var _this = this;
var that = this;
// console.log("onshow")
var res = app.islogin()
if (res != false) {
that.setData({
islogin: true,
})

  //判断是否绑定
  res.then((res) => {
    console.log(res.data.is_bind)
    if (res.data.is_bind == false) {
      _this.setData({
        'remind': '未绑定',
      });
    } else {
      _this.setData({
        'remind': '加载中',
        student_ID: res.data.student_ID,
        student_Password: res.data.student_Password
      });
      //清空数据
      _this.setData({
        user: app.globalData.userinfo,
        'card.kb.show': false,
        'card.ykt.show': false,
        'card.jy.show': false,
        'card.sdf.show': false
      });
      _this.getCardData();
    }
  })
}
//离线模式重新登录
if (_this.data.offline) {
  _this.login();
  return false;
}

function isEmptyObject(obj) {
  for (var key in obj) {
    return false;
  }
  return true;
}

function isEqualObject(obj1, obj2) {
  if (JSON.stringify(obj1) != JSON.stringify(obj2)) {
    return false;
  }
  return true;
}
var l_user = _this.data.user, //本页用户数据
  g_user = app._user; //全局用户数据
//排除第一次加载页面的情况(全局用户数据未加载完整 或 本页用户数据与全局用户数据相等)
if (isEmptyObject(l_user) || !g_user.openid || isEqualObject(l_user.we, g_user.we)) {
  return false;
}
//全局用户数据和本页用户数据不一致时,重新获取卡片数据
if (!isEqualObject(l_user.we, g_user.we)) {
  //判断绑定状态
  if (!g_user.is_bind) {
    _this.setData({
      'remind': '未绑定'
    });
  } else {
    _this.setData({
      'remind': '加载中'
    });
    //清空数据
    _this.setData({
      user: app._user,
      'card.kb.show': false,
      'card.ykt.show': false,
      'card.jy.show': false,
      'card.sdf.show': false
    });
    _this.getCardData();
  }
}

},
onLoad: function () {
this.login();
if (this.data.user = {}) {
console.log("没登录")
}
},
userInfoHandler(data) {
var that = this
wx.BaaS.handleUserInfo(data).then(res => {
// res 包含用户完整信息,详见下方描述
that.setData({
islogin: true,
})
app.globalData.userinfo = res
console.log(res)
that.getCardData();
}, res => {
// res 有两种情况:用户拒绝授权,res 包含基本用户信息:id、openid、unionid;其他类型的错误,如网络断开、请求超时等,将返回 Error 对象(详情见下方注解)
})
},
login: function () {
var _this = this;
console.log(app.islogin())
//如果有缓存,则提前加载缓存
// 微信用户登录小程序

},
response: function (status) {
var _this = this;
if (status) {
if (status != '离线缓存模式') {
//错误
_this.setData({
'remind': status
});
return;
} else {
//离线缓存模式
_this.setData({
offline: true
});
}
}
_this.setData({
user: app._user
});
//判断绑定状态
if (!app._user.is_bind) {
_this.setData({
'remind': '未绑定'
});
} else {
_this.setData({
'remind': '加载中'
});
_this.getCardData();
}
},
disabled_item: function () {
var _this = this;
if (!_this.data.disabledItemTap) {
_this.setData({
disabledItemTap: true
});
setTimeout(function () {
_this.setData({
disabledItemTap: false
});
}, 2000);
}
},
//课表渲染
kbRender(info) {
if (info) {
var classlist = info
} else {
var classlist = wx.getStorageInfoSync('kb_today')
}

//console.log(classlist)
var _this = this
_this.setData({
  'card.kb.data': classlist,
  'card.kb.show': true,
  'card.kb.nothing': !classlist.length,
  'remind': ''
});

},
//获取当前时间
getCurrentWeekday() {
var myDate = new Date();
return myDate.getDay();
},
getCardData: function () {
var _this = this;
var student_ID = _this.data.student_ID
var student_Password = _this.data.student_Password
//判断并读取缓存

if (wx.getStorageSync('kb_today')) {
  _this.kbRender(wx.getStorageSync('kb_today'));
}
if (app.globalData.ykt) {
  yktRender(app.cache.ykt);
}
if (app.globalData.sdf) {
  sdfRender(app.cache.sdf);
}
if (app.globalData.jy) {
  jyRender(app.cache.jy);
}
if (_this.data.offline) {
  return;
}
// wx.showNavigationBarLoading();

http.request('https://coes-stud.must.edu.mo/coes/login.do', 'get', {
  'userid': student_ID,
  'password': student_Password
}, res => {
  console.log("res",res)
  http.request('https://coes-stud.must.edu.mo/coes/AcademicRecordsForm.do?intake=1809&formAction=Timetable', 'post', {
    'userid': student_ID,
    'password': student_Password
  }, {
    success: res => {
      console.log(res.statusCode)
      if (res.statusCode == 200) {
        var txt = res.data.toString()
        var info = ''
        var classlist = []
        var classlist_all = []
        var nowweek = _this.getCurrentWeekday()
        //str = '';
        var patt = /timetable\.add\('(\d+)',[\n\s]+'(\d+:\d+)',[\n\s]+'(\d+:\d+)',[\n\s]+'(\w+)',[\n\s]+'([^']+)',[\n\s]+'(\w+)',[\n\s]+'(\w+)',[\n\s]+'([^']+)',[\n\s]+'([^']+)'\+'\s+-\s+'\+[\n\s]+'([^']+)'\);/g;
        while (info = patt.exec(txt)) {
          console.log(info)
          var tempinfo = {
            "week": info[1],
            "when": info[2],
            "end": info[3],
            "what": info[5],
            "where": info[7]
          }
          classlist_all.push(info)
          if (tempinfo.week == nowweek) {
            classlist.push(tempinfo)
          }
        }
        //console.log(classlist)

        //保存课表缓存
        app.saveCache('kb', classlist_all);
        app.saveCache('kb_today', classlist);
        _this.kbRender(classlist);

      } else {
        //app.removeCache('kb');
      }
    }



      // _this.setData({ list: classlist})
      //_this.kbRender(classlist)
    })
})

//获取课表数据
//var kb_data = classlist
if (app._user.teacher) {
  kb_data.type = 'teacher';
}
/**var loadsum = 0; //正在请求连接数
loadsum++; //新增正在请求连接
wx.request({
  url: app._server + '/api/get_kebiao.php',
  method: 'POST',
  data: app.key(kb_data),

  complete: function() {
    loadsum--; //减少正在请求连接
    if (!loadsum) {
      if (_this.data.remind == '加载中') {
        _this.setData({
          remind: '首页暂无展示'
        });
      }
      wx.hideNavigationBarLoading();
      wx.stopPullDownRefresh();
    }
  }
});**/

/**一卡通渲染
function yktRender(list) {
  if (list.length > 0) {
    var last = list[0],
      last_time = last.time.split(' ')[0],
      now_time = app.util.formatTime(new Date()).split(' ')[0];
    //筛选并计算当日消费(一卡通数据有一定延迟,无法成功获取到今日数据,主页卡片通常不能展示)
    for (var i = 0, today_cost = [], cost_total = 0; i < list.length; i++) {
      if (list[i].time.split(' ')[0] == now_time && list[i].cost.indexOf('-') == 0) {
        var cost_value = Math.abs(parseInt(list[i].cost));
        today_cost.push(cost_value);
        cost_total += cost_value;
      }
    }
    if (today_cost.length) {
      _this.setData({
        'card.ykt.data.today_cost.value': today_cost,
        'card.ykt.data.today_cost.total': cost_total,
        'card.ykt.data.cost_status': true
      });
    }
    _this.setData({
      'card.ykt.data.last_time': last_time,
      'card.ykt.data.balance': parseFloat(last.balance),
      'card.ykt.show': true,
      'remind': ''
    });
  }
}
//获取一卡通数据
loadsum++; //新增正在请求连接
wx.request({
  url: app._server + '/api/get_yktcost.php',
  method: 'POST',
  data: app.key({
    yktID: app._user.we.ykth
  }),
  success: function(res) {
    if (res.data && res.data.status === 200) {
      var list = res.data.data;
      if (list) {
        //保存一卡通缓存
        app.saveCache('ykt', list);
        yktRender(list);
      }
    } else {
      app.removeCache('ykt');
    }
  },
  complete: function() {
    loadsum--; //减少正在请求连接
    if (!loadsum) {
      if (_this.data.remind) {
        _this.setData({
          remind: '首页暂无展示'
        });
      }
      wx.hideNavigationBarLoading();
      wx.stopPullDownRefresh();
    }
  }
});

//水电费渲染
function sdfRender(info) {
  _this.setData({
    'card.sdf.data.room': info.room.split('-').join('栋'),
    'card.sdf.data.record_time': info.record_time.split(' ')[0].split('/').join('-'),
    'card.sdf.data.cost': info.elec_cost,
    'card.sdf.data.spend': info.elec_spend,
    'card.sdf.show': true,
    'remind': ''
  });
}
if (!!app._user.we.room && !!app._user.we.build) {
  //获取水电费数据
  loadsum++; //新增正在请求连接
  wx.request({
    url: app._server + '/api/get_elec.php',
    method: 'POST',
    data: app.key({
      buildingNo: app._user.we.build,
      floor: app._user.we.room.slice(0, 1),
      room: parseInt(app._user.we.room.slice(1))
    }),
    success: function(res) {
      if (res.data && res.data.status === 200) {
        var info = res.data.data;
        if (info) {
          //保存水电费缓存
          app.saveCache('sdf', info);
          sdfRender(info);
        }
      } else {
        app.removeCache('sdf');
      }
    },
    complete: function() {
      loadsum--; //减少正在请求连接
      if (!loadsum) {
        if (_this.data.remind) {
          _this.setData({
            remind: '首页暂无展示'
          });
        }
        wx.hideNavigationBarLoading();
        wx.stopPullDownRefresh();
      }
    }
  });
}

//借阅信息渲染
function jyRender(info) {
  if (parseInt(info.books_num) && info.book_list && info.book_list.length) {
    var nowTime = new Date().getTime();
    info.book_list.map(function(e) {
      var oDate = e.yhrq.split('-'),
        oTime = new Date(oDate[0], oDate[1] - 1, oDate[2]).getTime();
      e.timing = parseInt((oTime - nowTime) / 1000 / 60 / 60 / 24);
      return e;
    });
    _this.setData({
      'card.jy.data': info,
      'card.jy.show': true,
      'remind': ''
    });
  }
}
//获取借阅信息
loadsum++; //新增正在请求连接
wx.request({
  url: app._server + "/api/get_books.php",
  method: 'POST',
  data: app.key({
    ykth: app._user.we.ykth
  }),
  success: function(res) {
    if (res.data && res.data.status === 200) {
      var info = res.data.data;
      if (info) {
        //保存借阅缓存
        app.saveCache('jy', info);
        jyRender(info);
      }
    } else {
      app.removeCache('jy');
    }
  },
  complete: function() {
    loadsum--; //减少正在请求连接
    if (!loadsum) {
      if (_this.data.remind) {
        _this.setData({
          remind: '首页暂无展示'
        });
      }
      wx.hideNavigationBarLoading();
      wx.stopPullDownRefresh();
    }
  }
});**/

}
});`

使用中遇到的不好的体验或者说是不足

1,在非页面,非组件内update store,store的数据只能同步到路由栈已存在的页面
2,打开新的页面 store的内容无法同步到新页面
关于问题出现的原因,create时候合并的data 是直接执行的,
那么在create执行之后,在页面被加入路由之前,这段时间的store的更新在 打开新页面的时候是无法相应更新的,这真的是很难受

update方法缺乏回调功能

可以用将setData的方法promisify然后promise.all所有update产生的setData,但是可能对性能的影响较大。我改了以后会测一下,如果性能还行会发一个pull request。

有一个疑问,就是当多个页面 每个页面使用到的data属性很多时,会不会很冗杂

举个栗子
pageA

Page({
  data: {
     a:1,
     b: 2,
     .....
    z: ''
  }
}) 

但是其实 只有 一两个属性是需要跨页面传输的(例如z),其他的都只是本页面使用,那么 如果这种情况 要采用westore 是不是必须所有属性都写到store (即使采用 分模块来加载,模块里面也都一一对应写好吧)

可能我表述的不是很清楚, 最终想表达的是 就是能不能只有写在store的属性时 才会被覆盖,其他的当前页面没有的,就不用覆盖。

对象被添加了属性

对象会被加上了$observeProps、$observer属性,如何能使用只有原有的属性的对象
image

关于globalData的一点问题

globalData如果不在组件和页面声明data依赖,在绑定视图初始显示会有问题,而且更改globalData的值时候还需要手动update()一次?

  this.store.data.globalPropTest = 'aaaa'
   this.update()

组织自己的store可以使用这种方法

a.js

export default  {
    data: {
        numa: 1,
        numb: 2
    },
    getA() {
        console.log('a');
    }
}

b.js

export default  {
    data: {
        numc: 3,
        numd: 4
    },
    getA() {
        console.log('b');
    }
}

store.js

import a from './a.js'
import b from './b.js'

const commonData = {
    commona: 'common'
}
function storeMixin(options) {
    let result = {
        data: commonData ,
    }
    for (let k in options) {
        let value = options[k];
        if (value.data) {
            result.data[k] = value.data
            delete value.data
        }
        Object.assign(result, value)
    }
    return result;
}

export default storeMixin({a, b})

根据page划分store, 不过函数名字就不能重复了

全页插件

使用westore-plugin中的两个文件create-plug.js,diff.js

在开发全页插件的时候,页面不能正常执行。尝试使用普通开发模式中的两个文件就可以正常运行。

而且我看示例中好几种模式的create.js/diff.js都不太一样,使用上有什么区别的?

observejs会触发多次事件

在使用observejs时为了跟原生的Object.observe 兼容,我写了如下代码

                objectObserve = Object.observe || function (obj, fun){
                        observe(obj, function (name, value, oldValue) {
                                fun && fun([{
                                    type: 'update',
                                    name: name,
                                    oldValue: oldValue
                                }]);
                        })
                    };

结果发现fun回调会被执行多次,目前我的办法是加一个定时器,100ms内只允许执行一次,这样可以解决多次触发的问题,但不太理解为什么会有触发多次的问题?

__nosuchmethod__类似需求没有被支持

var arr = [1,2,3];
observe(arr, function() {console.log("change")});
arr[3] = 2;
var obj = {a:1,b:2};
observe(obj, function() {console.log("change")});
obj.c = 3;

如果直接使用未定义的键的操作相关的监测事件未触发,如果在实际使用中要一直避免访问到未定义的键,那使用起来需很谨慎

polymer的observerjs通过暴露一个Platform.performMicrotaskCheckpoint()接口手动刷新,其余的方案采用定时器机制检查

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.