chore: rename omix
This commit is contained in:
parent
e7edcb8e71
commit
8659efabc9
|
@ -1,143 +0,0 @@
|
|||
## omix
|
||||
|
||||
> 小程序全局状态管理
|
||||
|
||||
极小却精巧的小程序框架,对小程序入侵性几乎为零
|
||||
|
||||
## 3分钟精通
|
||||
|
||||
### API
|
||||
|
||||
* `create(store, option)` 创建页面, store 可跨页面共享
|
||||
* `create(option)` 创建组件
|
||||
* `this.store.data` 全局 store 和 data,页面和页面所有组件可以拿到, 操作 data 会自动更新视图
|
||||
|
||||
|
||||
## 实战
|
||||
|
||||
定义 store:
|
||||
|
||||
```js
|
||||
export default {
|
||||
data: {
|
||||
logs: []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
定义页面:
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
import util from '../../utils/util'
|
||||
import store from '../../store'
|
||||
|
||||
create(store, {
|
||||
onLoad: function () {
|
||||
this.store.data.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs[0] = 'Changed!'
|
||||
}, 1000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs.push(Math.random(), Math.random())
|
||||
}, 2000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs.splice(this.store.data.logs.length - 1, 1)
|
||||
}, 3000)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<view class="container log-list">
|
||||
<block wx:for="{{store.logs}}" wx:for-item="log">
|
||||
<text class="log-item">{{index + 1}}. {{log}}</text>
|
||||
</block>
|
||||
<view>
|
||||
<test-store></test-store>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
可以看到里面使用 test-store 组件, 看下组件源码:
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
|
||||
create({
|
||||
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<view class="ctn">
|
||||
<view>
|
||||
<text>Log Length: {{store.logs.length}}</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
喜欢中心化还是喜欢去中心化任你挑选,或者同一个小程序可以混合两种模式。
|
||||
|
||||
## 调试
|
||||
|
||||
修改 store.js 的 debug 字段用来打开和关闭 log 调试:
|
||||
|
||||
```js
|
||||
export default {
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: {},
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
logs: [],
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
debug: true //我是开关
|
||||
}
|
||||
```
|
||||
|
||||
默认是打开的,`store.data` 的所以变动都会出现在开发者工具 log 面板,如下图所示:
|
||||
|
||||
![](../../assets/omix.png)
|
||||
|
||||
## 注意
|
||||
|
||||
|
||||
这里需要注意,改变数组的 length 不会触发视图更新,需要使用 size 方法:
|
||||
|
||||
```js
|
||||
this.oData.arr.size(2) //会触发视图更新
|
||||
this.oData.arr.length = 2 //不会触发视图更新
|
||||
|
||||
this.oData.arr.push(111) //会触发视图更新
|
||||
//每个数组的方法都有对应的 pureXXX 方法
|
||||
this.oData.arr.purePush(111) //不会触发视图更新
|
||||
```
|
||||
|
||||
#### 函数属性
|
||||
|
||||
```js
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
其中 reverseMotto 可以直接绑定在 wxml 里,motto 更新会自动更新 reverseMotto 的值。
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT © Tencent
|
|
@ -1,5 +0,0 @@
|
|||
<view class="ctn">
|
||||
<view>
|
||||
<text>Log Length: {{logs.length}}</text>
|
||||
</view>
|
||||
</view>
|
|
@ -1,52 +0,0 @@
|
|||
import create from '../../utils/create'
|
||||
import store from '../../store'
|
||||
|
||||
//获取应用实例
|
||||
const app = getApp()
|
||||
|
||||
create(store, {
|
||||
|
||||
//事件处理函数
|
||||
bindViewTap: function () {
|
||||
wx.navigateTo({
|
||||
url: '../logs/logs'
|
||||
})
|
||||
},
|
||||
onLoad: function () {
|
||||
if (app.globalData.userInfo) {
|
||||
this.store.data.userInfo = app.globalData.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
|
||||
} else if (this.data.canIUse) {
|
||||
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
|
||||
// 所以此处加入 callback 以防止这种情况
|
||||
app.userInfoReadyCallback = res => {
|
||||
this.store.data.userInfo = res.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
}
|
||||
} else {
|
||||
// 在没有 open-type=getUserInfo 版本的兼容处理
|
||||
wx.getUserInfo({
|
||||
success: res => {
|
||||
app.globalData.userInfo = res.userInfo
|
||||
this.store.data.userInfo = res.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs.push('abc')
|
||||
this.store.data.motto = '123456'
|
||||
}, 1000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.motto = 'abcdefg'
|
||||
}, 2000)
|
||||
},
|
||||
getUserInfo: function (e) {
|
||||
this.store.data.userInfo = e.detail.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
|
||||
}
|
||||
})
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"usingComponents": {
|
||||
"test-store": "/components/test-store/test-store"
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
//logs.js
|
||||
import create from '../../utils/create'
|
||||
import store from '../../store'
|
||||
|
||||
const util = require('../../utils/util.js')
|
||||
|
||||
create(store, {
|
||||
onLoad: function () {
|
||||
this.store.data.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
}
|
||||
})
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"navigationBarTitleText": "查看启动日志",
|
||||
"usingComponents": {}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
## omix
|
||||
|
||||
> 极小却精巧的小程序框架,对小程序入侵性几乎为零
|
||||
|
||||
## 5分钟精通
|
||||
|
||||
### API
|
||||
|
||||
#### 去中心化 API
|
||||
|
||||
* `create.Page(option)` 创建页面,option.context 页面级别共享
|
||||
* `create.Component(option)` 创建组件
|
||||
* `this.oData` 操作页面或组件的数据(会自动更新视图)
|
||||
* `this.context` 页面注入的 context,页面和页面所有组件可以拿到
|
||||
* `create.mitt()` 事件发送和监听器
|
||||
|
||||
#### 中心化 API
|
||||
|
||||
* `create(store, option)` 创建页面, store 可跨页面共享
|
||||
* `create(option)` 创建组件
|
||||
* `this.store.data` 全局 store 和 data,页面和页面所有组件可以拿到, 操作 data 会自动更新视图
|
||||
* `create.emitter` 事件发送和监听器,不同于 mitt() 每次会创建新的实例,emitter 是全局唯一,可以用于跨页面通讯
|
||||
|
||||
### 实战去中心化 API
|
||||
|
||||
#### 页面
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
|
||||
const app = getApp()
|
||||
|
||||
create.Page({
|
||||
//页面级别上下文,跨页面不共享
|
||||
context: {
|
||||
abc: '公共数据从页面注入到页面的所有组件'
|
||||
//事件发送和监听器,或者 create.mitt()
|
||||
emitter: create.emitter
|
||||
},
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: { },
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo')
|
||||
},
|
||||
...
|
||||
...
|
||||
onLoad: function () {
|
||||
|
||||
...
|
||||
...
|
||||
...
|
||||
//监听事件
|
||||
this.context.emitter.on('foo', e => console.log('foo', e) )
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo = {
|
||||
nickName: 'dnt',
|
||||
avatarUrl: this.data.userInfo.avatarUrl
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo.nickName = 'dntzhang'
|
||||
}, 4000)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
这里需要注意,oData 必须直接操作 data 里定义好的数据才能直接更新视图,比如 `nickName` 一开始没有定义好,更新它是不会更新视图,只有通过下面代码执行之后,才能更新 nickName,因为 userInfo 的变更会自动监听 userInfo 内的所有属性:
|
||||
|
||||
```js
|
||||
this.oData.userInfo = {
|
||||
nickName: 'dnt',
|
||||
avatarUrl: this.data.userInfo.avatarUrl
|
||||
}
|
||||
```
|
||||
|
||||
当然你也可以直接在 data 里声明好:
|
||||
|
||||
```js
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: { nickName: null },
|
||||
...
|
||||
```
|
||||
|
||||
#### 组件
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
|
||||
create.Component({
|
||||
data: {
|
||||
a: { b: Math.random() }
|
||||
},
|
||||
|
||||
ready: function () {
|
||||
//这里可以获取组件所属页面注入的 store
|
||||
console.log(this.context)
|
||||
//触发事件
|
||||
this.context.emitter.emit('foo', { a: 'b' })
|
||||
setTimeout(()=>{
|
||||
this.oData.a.b = 1
|
||||
},3000)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### 数组
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
import util from '../../utils/util'
|
||||
|
||||
create.Page({
|
||||
data: {
|
||||
logs: []
|
||||
},
|
||||
onLoad: function () {
|
||||
this.oData.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs[0] = 'Changed!'
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
这里需要注意,改变数组的 length 不会触发视图更新,需要使用 size 方法:
|
||||
|
||||
```js
|
||||
this.oData.yourArray.size(3)
|
||||
```
|
||||
|
||||
#### 其他
|
||||
|
||||
```js
|
||||
this.oData.arr.push(111) //会触发视图更新
|
||||
//每个数组的方法都有对应的 pureXXX 方法
|
||||
this.oData.arr.purePush(111) //不会触发视图更新
|
||||
|
||||
this.oData.arr.size(2) //会触发视图更新
|
||||
this.oData.arr.length = 2 //不会触发视图更新
|
||||
|
||||
```
|
||||
|
||||
#### 函数属性
|
||||
|
||||
```js
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
其中 reverseMotto 可以直接绑定在 wxml 里,motto 更新会自动更新 reverseMotto 的值。
|
||||
|
||||
#### mitt 语法
|
||||
|
||||
```js
|
||||
const emitter = mitt()
|
||||
|
||||
// listen to an event
|
||||
emitter.on('foo', e => console.log('foo', e) )
|
||||
|
||||
// listen to all events
|
||||
emitter.on('*', (type, e) => console.log(type, e) )
|
||||
|
||||
// fire an event
|
||||
emitter.emit('foo', { a: 'b' })
|
||||
|
||||
// working with handler references:
|
||||
function onFoo() {}
|
||||
emitter.on('foo', onFoo) // listen
|
||||
emitter.off('foo', onFoo) // unlisten
|
||||
```
|
||||
|
||||
[详细参见 mitt github](https://github.com/developit/mitt)
|
||||
|
||||
omix 为 mitt 新增的语法:
|
||||
|
||||
```js
|
||||
emitter.off('foo') // unlisten all foo callback
|
||||
```
|
||||
|
||||
### 实战中心化 API
|
||||
|
||||
定义 store:
|
||||
|
||||
```js
|
||||
export default {
|
||||
data: {
|
||||
logs: []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
定义页面:
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
import util from '../../utils/util'
|
||||
import store from '../../store'
|
||||
|
||||
create(store, {
|
||||
onLoad: function () {
|
||||
this.store.data.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs[0] = 'Changed!'
|
||||
}, 1000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs.push(Math.random(), Math.random())
|
||||
}, 2000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.store.data.logs.splice(this.store.data.logs.length - 1, 1)
|
||||
}, 3000)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<view class="container log-list">
|
||||
<block wx:for="{{store.logs}}" wx:for-item="log">
|
||||
<text class="log-item">{{index + 1}}. {{log}}</text>
|
||||
</block>
|
||||
<view>
|
||||
<test-store></test-store>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
可以看到里面使用 test-store 组件, 看下组件源码:
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
|
||||
create({
|
||||
|
||||
})
|
||||
```
|
||||
|
||||
```html
|
||||
<view class="ctn">
|
||||
<view>
|
||||
<text>Log Length: {{store.logs.length}}</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
喜欢中心化还是喜欢去中心化任你挑选,或者同一个小程序可以混合两种模式。
|
||||
<!--
|
||||
## 原理
|
||||
|
||||
最开始 `omix` 打算使用 proxy,但是调研了下兼容性,还是打算使用 obaa 来进行数据变更监听。
|
||||
|
||||
因为小程序 IOS 使用内置的 jscore,安卓使用 x5,所以 Proxy 兼容性(IOS10+支持,安卓基本都支持)
|
||||
|
||||
![](https://github.com/Tencent/westore/raw/master/asset/ios.jpg)
|
||||
|
||||
实时统计地址:https://developer.apple.com/support/app-store/
|
||||
|
||||
|
||||
|
||||
```js
|
||||
this.setData({
|
||||
logs: [1, 2, 3]
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
'logs[2]': null
|
||||
})
|
||||
}, 2000)
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.data.logs.length)
|
||||
}, 3000)
|
||||
```
|
||||
|
||||
#### 页面生命周期函数
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------ | ------ |
|
||||
| onLoad | 监听页面加载 |
|
||||
| onShow | 监听页面显示 |
|
||||
| onReady | 监听页面初次渲染完成 |
|
||||
| onHide | 监听页面隐藏 |
|
||||
| onUnload | 监听页面卸载 |
|
||||
|
||||
### 组件生命周期函数
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------ | ------ |
|
||||
| created | 在组件实例进入页面节点树时执行,注意此时不能调用 setData |
|
||||
| attached | 在组件实例进入页面节点树时执行 |
|
||||
| ready | 在组件布局完成后执行,此时可以获取节点信息(使用 SelectorQuery ) |
|
||||
| moved | 在组件实例被移动到节点树另一个位置时执行 |
|
||||
| detached | 在组件实例被从页面节点树移除时执行 | -->
|
||||
|
||||
|
||||
## MIT Lic
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/logs/logs"
|
||||
"pages/logs/logs",
|
||||
"pages/other/other"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
|
@ -1,6 +1,7 @@
|
|||
import create from '../../utils/create'
|
||||
|
||||
// components/hello/hello.js
|
||||
Component({
|
||||
create.Component({
|
||||
/**
|
||||
* 组件的属性列表
|
||||
*/
|
|
@ -0,0 +1,5 @@
|
|||
<view class="ctn">
|
||||
<view>
|
||||
<text>Log Length: {{store.logs.length}}</text>
|
||||
</view>
|
||||
</view>
|
|
@ -0,0 +1,90 @@
|
|||
import create from '../../utils/create'
|
||||
|
||||
const app = getApp()
|
||||
|
||||
create.Page({
|
||||
context: {
|
||||
abc: '公共数据从页面注入到页面的所有组件',
|
||||
//事件发送和监听器,或者 create.mitt()
|
||||
emitter: create.emitter
|
||||
},
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: { },
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
//事件处理函数
|
||||
bindViewTap: function () {
|
||||
wx.navigateTo({
|
||||
url: '../logs/logs'
|
||||
})
|
||||
},
|
||||
onLoad: function () {
|
||||
console.log(this.context)
|
||||
if (app.globalData.userInfo) {
|
||||
this.setData({
|
||||
userInfo: app.globalData.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
} else if (this.data.canIUse) {
|
||||
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
|
||||
// 所以此处加入 callback 以防止这种情况
|
||||
app.userInfoReadyCallback = res => {
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 在没有 open-type=getUserInfo 版本的兼容处理
|
||||
wx.getUserInfo({
|
||||
success: res => {
|
||||
app.globalData.userInfo = res.userInfo
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.context.emitter.on('foo', e => console.log('foo', e) )
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo = {
|
||||
nickName: 'dnt',
|
||||
avatarUrl: this.data.userInfo.avatarUrl
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo.nickName = 'dntzhang'
|
||||
}, 4000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.motto = 'abc'
|
||||
}, 4000)
|
||||
|
||||
|
||||
|
||||
},
|
||||
getUserInfo: function (e) {
|
||||
console.log(e)
|
||||
app.globalData.userInfo = e.detail.userInfo
|
||||
this.setData({
|
||||
userInfo: e.detail.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
},
|
||||
|
||||
onTap: function(){
|
||||
wx.navigateTo({
|
||||
url: "/pages/other/other"
|
||||
});
|
||||
}
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationBarTitleText": "首页",
|
||||
"usingComponents": {
|
||||
"hello": "/components/hello/hello"
|
||||
}
|
||||
}
|
|
@ -8,7 +8,9 @@
|
|||
</block>
|
||||
</view>
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}-{{reverseMotto}}</text>
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
<text class="user-motto">-{{reverseMotto}}</text>
|
||||
</view>
|
||||
<test-store />
|
||||
<hello c="{{abc}}" ddd="{{ddd}}"></hello>
|
||||
<button bindtap="onTap">点击我测试全局单一 store</button>
|
||||
</view>
|
|
@ -0,0 +1,31 @@
|
|||
import create from '../../utils/create'
|
||||
import util from '../../utils/util'
|
||||
|
||||
create.Page({
|
||||
data: {
|
||||
logs: [],
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
onLoad: function () {
|
||||
this.oData.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs[0] = 'Changed!'
|
||||
this.oData.motto = Math.random() + ''
|
||||
}, 1000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs.push(Math.random(), Math.random())
|
||||
}, 2000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs.splice(this.oData.logs.length-1,1)
|
||||
}, 4000)
|
||||
}
|
||||
})
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"navigationBarTitleText": "查看启动日志"
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
<!--logs.wxml-->
|
||||
<view class="container log-list">
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
<text class="user-motto">-{{reverseMotto}}</text>
|
||||
</view>
|
||||
<block wx:for="{{logs}}" wx:for-item="log">
|
||||
<text class="log-item">{{index + 1}}. {{log}}</text>
|
||||
</block>
|
|
@ -9,13 +9,12 @@
|
|||
"postcss": true,
|
||||
"minified": true,
|
||||
"newFeature": true,
|
||||
"autoAudits": false,
|
||||
"coverView": true
|
||||
"autoAudits": false
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "2.8.3",
|
||||
"libVersion": "2.4.1",
|
||||
"appid": "wxfaf6dad43f57c6bd",
|
||||
"projectname": "miniprogram-1",
|
||||
"projectname": "omix",
|
||||
"debugOptions": {
|
||||
"hidedInDevtools": []
|
||||
},
|
|
@ -1,13 +1,9 @@
|
|||
export default {
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: {},
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
logs: [],
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
debug: true
|
||||
}
|
||||
}
|
|
@ -1,15 +1,97 @@
|
|||
/*!
|
||||
* omix v2.0.0 by dntzhang
|
||||
* omix v1.0.2 by dntzhang
|
||||
* Github: https://github.com/Tencent/omi
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
import obaa from './obaa'
|
||||
import mitt from './mitt'
|
||||
import config from './config'
|
||||
|
||||
const ARRAYTYPE = '[object Array]'
|
||||
const OBJECTTYPE = '[object Object]'
|
||||
const FUNCTIONTYPE = '[object Function]'
|
||||
|
||||
function _Page(option) {
|
||||
const onLoad = option.onLoad
|
||||
option.onLoad = function (e) {
|
||||
this.context = option.context
|
||||
this.oData = JSON.parse(JSON.stringify(option.data))
|
||||
if (!option.data.___walked) {
|
||||
walk(option.data, true)
|
||||
}
|
||||
//fn prop
|
||||
this.setData(option.data)
|
||||
observe(this, option.data)
|
||||
onLoad && onLoad.call(this, e)
|
||||
}
|
||||
Page(option)
|
||||
}
|
||||
|
||||
function _Component(option) {
|
||||
const ready = (option.lifetimes && option.lifetimes.ready) || option.ready
|
||||
option.lifetimes = option.lifetimes || {}
|
||||
option.ready = option.lifetimes.ready = function () {
|
||||
const page = getCurrentPages()[getCurrentPages().length - 1]
|
||||
this.context = option.context || page.context
|
||||
option.data = option.data || {}
|
||||
this.oData = JSON.parse(JSON.stringify(option.data))
|
||||
if (!option.data.___walked) {
|
||||
walk(option.data, true)
|
||||
}
|
||||
observe(this, option.data)
|
||||
ready && ready.call(this)
|
||||
}
|
||||
Component(option)
|
||||
}
|
||||
|
||||
function fixPath(path) {
|
||||
let mpPath = ''
|
||||
const arr = path.replace('#-', '').split('-')
|
||||
arr.forEach((item, index) => {
|
||||
if (index) {
|
||||
if (isNaN(parseInt(item))) {
|
||||
mpPath += '.' + item
|
||||
} else {
|
||||
mpPath += '[' + item + ']'
|
||||
}
|
||||
} else {
|
||||
mpPath += item
|
||||
}
|
||||
})
|
||||
return mpPath
|
||||
}
|
||||
|
||||
function observe(ele, data) {
|
||||
obaa(ele.oData, (prop, value, old, path) => {
|
||||
let patch = {}
|
||||
if (prop.indexOf('Array-push') === 0) {
|
||||
let dl = value.length - old.length
|
||||
for (let i = 0; i < dl; i++) {
|
||||
patch[fixPath(path + '-' + (old.length + i))] = value[(old.length + i)]
|
||||
}
|
||||
} else if (prop.indexOf('Array-') === 0) {
|
||||
patch[fixPath(path)] = value
|
||||
} else {
|
||||
patch[fixPath(path + '-' + prop)] = value
|
||||
}
|
||||
|
||||
|
||||
ele.setData(patch)
|
||||
//update fn prop
|
||||
updateByFnProp(ele, data)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function updateByFnProp(ele, data) {
|
||||
let patch = {}
|
||||
for (let key in data.__fnMapping) {
|
||||
patch[key] = data.__fnMapping[key].call(ele.oData)
|
||||
}
|
||||
ele.setData(patch)
|
||||
}
|
||||
|
||||
|
||||
function create(store, option) {
|
||||
if (arguments.length === 2) {
|
||||
|
@ -19,17 +101,27 @@ function create(store, option) {
|
|||
|
||||
getApp().globalData && (getApp().globalData.store = store)
|
||||
|
||||
option.data = store.data
|
||||
option.data = option.data || {}
|
||||
option.data.store = store.data
|
||||
observeStore(store)
|
||||
const onLoad = option.onLoad
|
||||
|
||||
option.onLoad = function (e) {
|
||||
this.store = store
|
||||
|
||||
this.context = option.context
|
||||
const temp = option.data.store
|
||||
delete option.data.store
|
||||
this.oData = JSON.parse(JSON.stringify(option.data))
|
||||
if (!option.data.___walked) {
|
||||
walk(option.data, true)
|
||||
}
|
||||
observe(this, option.data)
|
||||
option.data.store = temp
|
||||
|
||||
store.instances[this.route] = []
|
||||
store.instances[this.route].push(this)
|
||||
if (!option.data.___walked) {
|
||||
if (!option.data.store.___walked) {
|
||||
walk(this.store.data)
|
||||
}
|
||||
this.setData.call(this, option.data)
|
||||
|
@ -41,10 +133,11 @@ function create(store, option) {
|
|||
store.lifetimes = store.lifetimes || {}
|
||||
store.ready = store.lifetimes.ready = function () {
|
||||
const page = getCurrentPages()[getCurrentPages().length - 1]
|
||||
|
||||
this.context = store.context || page.context
|
||||
this.store = page.store
|
||||
|
||||
store.data = this.store.data
|
||||
store.data = store.data || {}
|
||||
store.data.store = this.store.data
|
||||
this.setData.call(this, store.data)
|
||||
|
||||
this.store.instances[page.route].push(this)
|
||||
|
@ -61,12 +154,12 @@ function observeStore(store) {
|
|||
if (prop.indexOf('Array-push') === 0) {
|
||||
let dl = value.length - old.length
|
||||
for (let i = 0; i < dl; i++) {
|
||||
patch[ fixPath(path + '-' + (old.length + i))] = value[(old.length + i)]
|
||||
patch['store.' + fixPath(path + '-' + (old.length + i))] = value[(old.length + i)]
|
||||
}
|
||||
} else if (prop.indexOf('Array-') === 0) {
|
||||
patch[ fixPath(path)] = value
|
||||
patch['store.' + fixPath(path)] = value
|
||||
} else {
|
||||
patch[ fixPath(path + '-' + prop)] = value
|
||||
patch['store.' + fixPath(path + '-' + prop)] = value
|
||||
}
|
||||
|
||||
_update(patch, store)
|
||||
|
@ -83,7 +176,7 @@ function _update(kv, store) {
|
|||
})
|
||||
}
|
||||
store.onChange && store.onChange(kv)
|
||||
store.debug && storeChangeLogger(store)
|
||||
config.logger.isOpen && storeChangeLogger(store)
|
||||
}
|
||||
|
||||
function storeChangeLogger (store) {
|
||||
|
@ -104,16 +197,34 @@ function storeChangeLogger (store) {
|
|||
}
|
||||
|
||||
function updateStoreByFnProp(ele, data) {
|
||||
if(data){
|
||||
if(data.store){
|
||||
let patch = {}
|
||||
for (let key in data.__fnMapping) {
|
||||
patch[key] = data.__fnMapping[key].call(data)
|
||||
for (let key in data.store.__fnMapping) {
|
||||
patch['store.' + key] = data.store.__fnMapping[key].call(data)
|
||||
}
|
||||
ele.setData(patch)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function extendStoreMethod(data) {
|
||||
data.method = function (path, fn) {
|
||||
//fnMapping[path] = fn
|
||||
//data??
|
||||
data.__fnMapping = data.__fnMapping || {}
|
||||
data.__fnMapping[path] = fn
|
||||
let ok = getObjByPath(path, data)
|
||||
Object.defineProperty(ok.obj, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(data)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getObjByPath(path, data) {
|
||||
const arr = path.replace(/]/g, '').replace(/\[/g, '.').split('.')
|
||||
|
@ -129,60 +240,73 @@ function getObjByPath(path, data) {
|
|||
}
|
||||
}
|
||||
|
||||
function walk(data) {
|
||||
//___walked 用于标记是否已经观察遍历了
|
||||
function walk(data, tag) {
|
||||
data.___walked = true
|
||||
Object.keys(data).forEach(key => {
|
||||
const obj = data[key]
|
||||
const tp = type(obj)
|
||||
if (tp == FUNCTIONTYPE) {
|
||||
setProp(key, obj, data)
|
||||
setProp(key, obj, data, tag)
|
||||
} else if (tp == OBJECTTYPE) {
|
||||
Object.keys(obj).forEach(subKey => {
|
||||
_walk(obj[subKey], key + '.' + subKey, data)
|
||||
_walk(obj[subKey], key + '.' + subKey, data, tag)
|
||||
})
|
||||
|
||||
} else if (tp == ARRAYTYPE) {
|
||||
obj.forEach((item, index) => {
|
||||
_walk(item, key + '[' + index + ']', data)
|
||||
_walk(item, key + '[' + index + ']', data, tag)
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _walk(obj, path, data) {
|
||||
function _walk(obj, path, data, tag) {
|
||||
const tp = type(obj)
|
||||
if (tp == FUNCTIONTYPE) {
|
||||
setProp(path, obj, data)
|
||||
setProp(path, obj, data, tag)
|
||||
} else if (tp == OBJECTTYPE) {
|
||||
Object.keys(obj).forEach(subKey => {
|
||||
_walk(obj[subKey], path + '.' + subKey, data)
|
||||
_walk(obj[subKey], path + '.' + subKey, data, tag)
|
||||
})
|
||||
|
||||
} else if (tp == ARRAYTYPE) {
|
||||
obj.forEach((item, index) => {
|
||||
_walk(item, path + '[' + index + ']', data)
|
||||
_walk(item, path + '[' + index + ']', data, tag)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function setProp(path, fn, data) {
|
||||
function setProp(path, fn, data, tag) {
|
||||
const ok = getObjByPath(path, data)
|
||||
|
||||
data.__fnMapping = data.__fnMapping || {}
|
||||
data.__fnMapping[path] = fn
|
||||
Object.defineProperty(ok.obj, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(ok.obj)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
|
||||
if (tag) {
|
||||
data.__fnMapping = data.__fnMapping || {}
|
||||
data.__fnMapping[path] = fn
|
||||
Object.defineProperty(ok.obj, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(ok.obj)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
data.store = data.store || {}
|
||||
data.store.__fnMapping = data.store.__fnMapping || {}
|
||||
data.store.__fnMapping[path] = fn
|
||||
Object.defineProperty(ok.obj.store, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(ok.obj)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -190,37 +314,11 @@ function type(obj) {
|
|||
return Object.prototype.toString.call(obj)
|
||||
}
|
||||
|
||||
|
||||
|
||||
function fixPath(path) {
|
||||
let mpPath = ''
|
||||
const arr = path.replace('#-', '').split('-')
|
||||
arr.forEach((item, index) => {
|
||||
if (index) {
|
||||
if (isNaN(parseInt(item))) {
|
||||
mpPath += '.' + item
|
||||
} else {
|
||||
mpPath += '[' + item + ']'
|
||||
}
|
||||
} else {
|
||||
mpPath += item
|
||||
}
|
||||
})
|
||||
return mpPath
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateByFnProp(ele, data) {
|
||||
let patch = {}
|
||||
for (let key in data.__fnMapping) {
|
||||
patch[key] = data.__fnMapping[key].call(ele.oData)
|
||||
}
|
||||
ele.setData(patch)
|
||||
}
|
||||
|
||||
|
||||
create.Page = _Page
|
||||
create.Component = _Component
|
||||
create.obaa = obaa
|
||||
create.mitt = mitt
|
||||
create.emitter = mitt()
|
||||
|
||||
|
||||
export default create
|
|
@ -1,195 +1,19 @@
|
|||
## omix
|
||||
|
||||
> 极小却精巧的小程序框架,对小程序入侵性几乎为零
|
||||
> 小程序全局状态管理
|
||||
|
||||
## 5分钟精通
|
||||
极小却精巧的小程序框架,对小程序入侵性几乎为零
|
||||
|
||||
## 3分钟精通
|
||||
|
||||
### API
|
||||
|
||||
#### 去中心化 API
|
||||
|
||||
* `create.Page(option)` 创建页面,option.context 页面级别共享
|
||||
* `create.Component(option)` 创建组件
|
||||
* `this.oData` 操作页面或组件的数据(会自动更新视图)
|
||||
* `this.context` 页面注入的 context,页面和页面所有组件可以拿到
|
||||
* `create.mitt()` 事件发送和监听器
|
||||
|
||||
#### 中心化 API
|
||||
|
||||
* `create(store, option)` 创建页面, store 可跨页面共享
|
||||
* `create(option)` 创建组件
|
||||
* `this.store.data` 全局 store 和 data,页面和页面所有组件可以拿到, 操作 data 会自动更新视图
|
||||
* `create.emitter` 事件发送和监听器,不同于 mitt() 每次会创建新的实例,emitter 是全局唯一,可以用于跨页面通讯
|
||||
|
||||
### 实战去中心化 API
|
||||
|
||||
#### 页面
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
|
||||
const app = getApp()
|
||||
|
||||
create.Page({
|
||||
//页面级别上下文,跨页面不共享
|
||||
context: {
|
||||
abc: '公共数据从页面注入到页面的所有组件'
|
||||
//事件发送和监听器,或者 create.mitt()
|
||||
emitter: create.emitter
|
||||
},
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: { },
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo')
|
||||
},
|
||||
...
|
||||
...
|
||||
onLoad: function () {
|
||||
|
||||
...
|
||||
...
|
||||
...
|
||||
//监听事件
|
||||
this.context.emitter.on('foo', e => console.log('foo', e) )
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo = {
|
||||
nickName: 'dnt',
|
||||
avatarUrl: this.data.userInfo.avatarUrl
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo.nickName = 'dntzhang'
|
||||
}, 4000)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
这里需要注意,oData 必须直接操作 data 里定义好的数据才能直接更新视图,比如 `nickName` 一开始没有定义好,更新它是不会更新视图,只有通过下面代码执行之后,才能更新 nickName,因为 userInfo 的变更会自动监听 userInfo 内的所有属性:
|
||||
|
||||
```js
|
||||
this.oData.userInfo = {
|
||||
nickName: 'dnt',
|
||||
avatarUrl: this.data.userInfo.avatarUrl
|
||||
}
|
||||
```
|
||||
|
||||
当然你也可以直接在 data 里声明好:
|
||||
|
||||
```js
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: { nickName: null },
|
||||
...
|
||||
```
|
||||
|
||||
#### 组件
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
|
||||
create.Component({
|
||||
data: {
|
||||
a: { b: Math.random() }
|
||||
},
|
||||
|
||||
ready: function () {
|
||||
//这里可以获取组件所属页面注入的 store
|
||||
console.log(this.context)
|
||||
//触发事件
|
||||
this.context.emitter.emit('foo', { a: 'b' })
|
||||
setTimeout(()=>{
|
||||
this.oData.a.b = 1
|
||||
},3000)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### 数组
|
||||
|
||||
```js
|
||||
import create from '../../utils/create'
|
||||
import util from '../../utils/util'
|
||||
|
||||
create.Page({
|
||||
data: {
|
||||
logs: []
|
||||
},
|
||||
onLoad: function () {
|
||||
this.oData.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs[0] = 'Changed!'
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
这里需要注意,改变数组的 length 不会触发视图更新,需要使用 size 方法:
|
||||
|
||||
```js
|
||||
this.oData.yourArray.size(3)
|
||||
```
|
||||
|
||||
#### 其他
|
||||
|
||||
```js
|
||||
this.oData.arr.push(111) //会触发视图更新
|
||||
//每个数组的方法都有对应的 pureXXX 方法
|
||||
this.oData.arr.purePush(111) //不会触发视图更新
|
||||
|
||||
this.oData.arr.size(2) //会触发视图更新
|
||||
this.oData.arr.length = 2 //不会触发视图更新
|
||||
|
||||
```
|
||||
|
||||
#### 函数属性
|
||||
|
||||
```js
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
其中 reverseMotto 可以直接绑定在 wxml 里,motto 更新会自动更新 reverseMotto 的值。
|
||||
|
||||
#### mitt 语法
|
||||
|
||||
```js
|
||||
const emitter = mitt()
|
||||
|
||||
// listen to an event
|
||||
emitter.on('foo', e => console.log('foo', e) )
|
||||
|
||||
// listen to all events
|
||||
emitter.on('*', (type, e) => console.log(type, e) )
|
||||
|
||||
// fire an event
|
||||
emitter.emit('foo', { a: 'b' })
|
||||
|
||||
// working with handler references:
|
||||
function onFoo() {}
|
||||
emitter.on('foo', onFoo) // listen
|
||||
emitter.off('foo', onFoo) // unlisten
|
||||
```
|
||||
|
||||
[详细参见 mitt github](https://github.com/developit/mitt)
|
||||
|
||||
omix 为 mitt 新增的语法:
|
||||
|
||||
```js
|
||||
emitter.off('foo') // unlisten all foo callback
|
||||
```
|
||||
|
||||
### 实战中心化 API
|
||||
## 实战
|
||||
|
||||
定义 store:
|
||||
|
||||
|
@ -261,53 +85,58 @@ create({
|
|||
```
|
||||
|
||||
喜欢中心化还是喜欢去中心化任你挑选,或者同一个小程序可以混合两种模式。
|
||||
<!--
|
||||
## 原理
|
||||
|
||||
最开始 `omix` 打算使用 proxy,但是调研了下兼容性,还是打算使用 obaa 来进行数据变更监听。
|
||||
|
||||
因为小程序 IOS 使用内置的 jscore,安卓使用 x5,所以 Proxy 兼容性(IOS10+支持,安卓基本都支持)
|
||||
|
||||
![](https://github.com/Tencent/westore/raw/master/asset/ios.jpg)
|
||||
|
||||
实时统计地址:https://developer.apple.com/support/app-store/
|
||||
|
||||
## 调试
|
||||
|
||||
修改 store.js 的 debug 字段用来打开和关闭 log 调试:
|
||||
|
||||
```js
|
||||
this.setData({
|
||||
logs: [1, 2, 3]
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
'logs[2]': null
|
||||
})
|
||||
}, 2000)
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(this.data.logs.length)
|
||||
}, 3000)
|
||||
export default {
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: {},
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
logs: [],
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
debug: true //我是开关
|
||||
}
|
||||
```
|
||||
|
||||
#### 页面生命周期函数
|
||||
默认是打开的,`store.data` 的所以变动都会出现在开发者工具 log 面板,如下图所示:
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------ | ------ |
|
||||
| onLoad | 监听页面加载 |
|
||||
| onShow | 监听页面显示 |
|
||||
| onReady | 监听页面初次渲染完成 |
|
||||
| onHide | 监听页面隐藏 |
|
||||
| onUnload | 监听页面卸载 |
|
||||
![](../../assets/omix.png)
|
||||
|
||||
### 组件生命周期函数
|
||||
## 其他
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------ | ------ |
|
||||
| created | 在组件实例进入页面节点树时执行,注意此时不能调用 setData |
|
||||
| attached | 在组件实例进入页面节点树时执行 |
|
||||
| ready | 在组件布局完成后执行,此时可以获取节点信息(使用 SelectorQuery ) |
|
||||
| moved | 在组件实例被移动到节点树另一个位置时执行 |
|
||||
| detached | 在组件实例被从页面节点树移除时执行 | -->
|
||||
这里需要注意,改变数组的 length 不会触发视图更新,需要使用 size 方法:
|
||||
|
||||
```js
|
||||
this.oData.arr.size(2) //会触发视图更新
|
||||
this.oData.arr.length = 2 //不会触发视图更新
|
||||
|
||||
this.oData.arr.push(111) //会触发视图更新
|
||||
//每个数组的方法都有对应的 pureXXX 方法
|
||||
this.oData.arr.purePush(111) //不会触发视图更新
|
||||
```
|
||||
|
||||
#### 函数属性
|
||||
|
||||
```js
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
其中 reverseMotto 可以直接绑定在 wxml 里,motto 更新会自动更新 reverseMotto 的值。
|
||||
|
||||
|
||||
## MIT Lic
|
||||
## License
|
||||
|
||||
MIT © Tencent
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/logs/logs",
|
||||
"pages/other/other"
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import create from '../../utils/create'
|
||||
|
||||
// components/hello/hello.js
|
||||
create.Component({
|
||||
Component({
|
||||
/**
|
||||
* 组件的属性列表
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<view class="ctn">
|
||||
<view>
|
||||
<text>Log Length: {{store.logs.length}}</text>
|
||||
<text>Log Length: {{logs.length}}</text>
|
||||
</view>
|
||||
</view>
|
|
@ -1,22 +1,11 @@
|
|||
import create from '../../utils/create'
|
||||
import store from '../../store'
|
||||
|
||||
//获取应用实例
|
||||
const app = getApp()
|
||||
|
||||
create.Page({
|
||||
context: {
|
||||
abc: '公共数据从页面注入到页面的所有组件',
|
||||
//事件发送和监听器,或者 create.mitt()
|
||||
emitter: create.emitter
|
||||
},
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: { },
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
create(store, {
|
||||
|
||||
//事件处理函数
|
||||
bindViewTap: function () {
|
||||
wx.navigateTo({
|
||||
|
@ -24,67 +13,40 @@ create.Page({
|
|||
})
|
||||
},
|
||||
onLoad: function () {
|
||||
console.log(this.context)
|
||||
if (app.globalData.userInfo) {
|
||||
this.setData({
|
||||
userInfo: app.globalData.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
this.store.data.userInfo = app.globalData.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
|
||||
} else if (this.data.canIUse) {
|
||||
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
|
||||
// 所以此处加入 callback 以防止这种情况
|
||||
app.userInfoReadyCallback = res => {
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
this.store.data.userInfo = res.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
}
|
||||
} else {
|
||||
// 在没有 open-type=getUserInfo 版本的兼容处理
|
||||
wx.getUserInfo({
|
||||
success: res => {
|
||||
app.globalData.userInfo = res.userInfo
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
this.store.data.userInfo = res.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.context.emitter.on('foo', e => console.log('foo', e) )
|
||||
setTimeout(() => {
|
||||
this.store.data.logs.push('abc')
|
||||
this.store.data.motto = '123456'
|
||||
}, 1000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo = {
|
||||
nickName: 'dnt',
|
||||
avatarUrl: this.data.userInfo.avatarUrl
|
||||
}
|
||||
this.store.data.motto = 'abcdefg'
|
||||
}, 2000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.userInfo.nickName = 'dntzhang'
|
||||
}, 4000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.motto = 'abc'
|
||||
}, 4000)
|
||||
|
||||
|
||||
|
||||
},
|
||||
getUserInfo: function (e) {
|
||||
console.log(e)
|
||||
app.globalData.userInfo = e.detail.userInfo
|
||||
this.setData({
|
||||
userInfo: e.detail.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
},
|
||||
|
||||
onTap: function(){
|
||||
wx.navigateTo({
|
||||
url: "/pages/other/other"
|
||||
});
|
||||
this.store.data.userInfo = e.detail.userInfo
|
||||
this.store.data.hasUserInfo = true
|
||||
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"navigationBarTitleText": "首页",
|
||||
"usingComponents": {
|
||||
"hello": "/components/hello/hello"
|
||||
"test-store": "/components/test-store/test-store"
|
||||
}
|
||||
}
|
|
@ -8,9 +8,7 @@
|
|||
</block>
|
||||
</view>
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
<text class="user-motto">-{{reverseMotto}}</text>
|
||||
<text class="user-motto">{{motto}}-{{reverseMotto}}</text>
|
||||
</view>
|
||||
<hello c="{{abc}}" ddd="{{ddd}}"></hello>
|
||||
<button bindtap="onTap">点击我测试全局单一 store</button>
|
||||
<test-store />
|
||||
</view>
|
||||
|
|
|
@ -1,31 +1,13 @@
|
|||
//logs.js
|
||||
import create from '../../utils/create'
|
||||
import util from '../../utils/util'
|
||||
import store from '../../store'
|
||||
|
||||
create.Page({
|
||||
data: {
|
||||
logs: [],
|
||||
motto: 'Hello World',
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
},
|
||||
const util = require('../../utils/util.js')
|
||||
|
||||
create(store, {
|
||||
onLoad: function () {
|
||||
this.oData.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
this.store.data.logs = (wx.getStorageSync('logs') || []).map(log => {
|
||||
return util.formatTime(new Date(log))
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs[0] = 'Changed!'
|
||||
this.oData.motto = Math.random() + ''
|
||||
}, 1000)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs.push(Math.random(), Math.random())
|
||||
}, 2000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.oData.logs.splice(this.oData.logs.length-1,1)
|
||||
}, 4000)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"navigationBarTitleText": "查看启动日志"
|
||||
"navigationBarTitleText": "查看启动日志",
|
||||
"usingComponents": {}
|
||||
}
|
|
@ -1,9 +1,5 @@
|
|||
<!--logs.wxml-->
|
||||
<view class="container log-list">
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
<text class="user-motto">-{{reverseMotto}}</text>
|
||||
</view>
|
||||
<block wx:for="{{logs}}" wx:for-item="log">
|
||||
<text class="log-item">{{index + 1}}. {{log}}</text>
|
||||
</block>
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
"postcss": true,
|
||||
"minified": true,
|
||||
"newFeature": true,
|
||||
"autoAudits": false
|
||||
"autoAudits": false,
|
||||
"coverView": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "2.4.1",
|
||||
"libVersion": "2.8.3",
|
||||
"appid": "wxfaf6dad43f57c6bd",
|
||||
"projectname": "omix",
|
||||
"projectname": "miniprogram-1",
|
||||
"debugOptions": {
|
||||
"hidedInDevtools": []
|
||||
},
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
export default {
|
||||
data: {
|
||||
logs: [],
|
||||
motto: 'Hello World',
|
||||
userInfo: {},
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
logs: [],
|
||||
reverseMotto() {
|
||||
return this.motto.split('').reverse().join('')
|
||||
}
|
||||
}
|
||||
},
|
||||
debug: true
|
||||
}
|
|
@ -1,97 +1,15 @@
|
|||
/*!
|
||||
* omix v1.0.2 by dntzhang
|
||||
* omix v2.0.0 by dntzhang
|
||||
* Github: https://github.com/Tencent/omi
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
import obaa from './obaa'
|
||||
import mitt from './mitt'
|
||||
import config from './config'
|
||||
|
||||
const ARRAYTYPE = '[object Array]'
|
||||
const OBJECTTYPE = '[object Object]'
|
||||
const FUNCTIONTYPE = '[object Function]'
|
||||
|
||||
function _Page(option) {
|
||||
const onLoad = option.onLoad
|
||||
option.onLoad = function (e) {
|
||||
this.context = option.context
|
||||
this.oData = JSON.parse(JSON.stringify(option.data))
|
||||
if (!option.data.___walked) {
|
||||
walk(option.data, true)
|
||||
}
|
||||
//fn prop
|
||||
this.setData(option.data)
|
||||
observe(this, option.data)
|
||||
onLoad && onLoad.call(this, e)
|
||||
}
|
||||
Page(option)
|
||||
}
|
||||
|
||||
function _Component(option) {
|
||||
const ready = (option.lifetimes && option.lifetimes.ready) || option.ready
|
||||
option.lifetimes = option.lifetimes || {}
|
||||
option.ready = option.lifetimes.ready = function () {
|
||||
const page = getCurrentPages()[getCurrentPages().length - 1]
|
||||
this.context = option.context || page.context
|
||||
option.data = option.data || {}
|
||||
this.oData = JSON.parse(JSON.stringify(option.data))
|
||||
if (!option.data.___walked) {
|
||||
walk(option.data, true)
|
||||
}
|
||||
observe(this, option.data)
|
||||
ready && ready.call(this)
|
||||
}
|
||||
Component(option)
|
||||
}
|
||||
|
||||
function fixPath(path) {
|
||||
let mpPath = ''
|
||||
const arr = path.replace('#-', '').split('-')
|
||||
arr.forEach((item, index) => {
|
||||
if (index) {
|
||||
if (isNaN(parseInt(item))) {
|
||||
mpPath += '.' + item
|
||||
} else {
|
||||
mpPath += '[' + item + ']'
|
||||
}
|
||||
} else {
|
||||
mpPath += item
|
||||
}
|
||||
})
|
||||
return mpPath
|
||||
}
|
||||
|
||||
function observe(ele, data) {
|
||||
obaa(ele.oData, (prop, value, old, path) => {
|
||||
let patch = {}
|
||||
if (prop.indexOf('Array-push') === 0) {
|
||||
let dl = value.length - old.length
|
||||
for (let i = 0; i < dl; i++) {
|
||||
patch[fixPath(path + '-' + (old.length + i))] = value[(old.length + i)]
|
||||
}
|
||||
} else if (prop.indexOf('Array-') === 0) {
|
||||
patch[fixPath(path)] = value
|
||||
} else {
|
||||
patch[fixPath(path + '-' + prop)] = value
|
||||
}
|
||||
|
||||
|
||||
ele.setData(patch)
|
||||
//update fn prop
|
||||
updateByFnProp(ele, data)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function updateByFnProp(ele, data) {
|
||||
let patch = {}
|
||||
for (let key in data.__fnMapping) {
|
||||
patch[key] = data.__fnMapping[key].call(ele.oData)
|
||||
}
|
||||
ele.setData(patch)
|
||||
}
|
||||
|
||||
|
||||
function create(store, option) {
|
||||
if (arguments.length === 2) {
|
||||
|
@ -101,27 +19,17 @@ function create(store, option) {
|
|||
|
||||
getApp().globalData && (getApp().globalData.store = store)
|
||||
|
||||
option.data = option.data || {}
|
||||
option.data.store = store.data
|
||||
option.data = store.data
|
||||
observeStore(store)
|
||||
const onLoad = option.onLoad
|
||||
|
||||
option.onLoad = function (e) {
|
||||
this.store = store
|
||||
|
||||
this.context = option.context
|
||||
const temp = option.data.store
|
||||
delete option.data.store
|
||||
this.oData = JSON.parse(JSON.stringify(option.data))
|
||||
if (!option.data.___walked) {
|
||||
walk(option.data, true)
|
||||
}
|
||||
observe(this, option.data)
|
||||
option.data.store = temp
|
||||
|
||||
store.instances[this.route] = []
|
||||
store.instances[this.route].push(this)
|
||||
if (!option.data.store.___walked) {
|
||||
if (!option.data.___walked) {
|
||||
walk(this.store.data)
|
||||
}
|
||||
this.setData.call(this, option.data)
|
||||
|
@ -133,11 +41,10 @@ function create(store, option) {
|
|||
store.lifetimes = store.lifetimes || {}
|
||||
store.ready = store.lifetimes.ready = function () {
|
||||
const page = getCurrentPages()[getCurrentPages().length - 1]
|
||||
this.context = store.context || page.context
|
||||
|
||||
this.store = page.store
|
||||
|
||||
store.data = store.data || {}
|
||||
store.data.store = this.store.data
|
||||
store.data = this.store.data
|
||||
this.setData.call(this, store.data)
|
||||
|
||||
this.store.instances[page.route].push(this)
|
||||
|
@ -154,12 +61,12 @@ function observeStore(store) {
|
|||
if (prop.indexOf('Array-push') === 0) {
|
||||
let dl = value.length - old.length
|
||||
for (let i = 0; i < dl; i++) {
|
||||
patch['store.' + fixPath(path + '-' + (old.length + i))] = value[(old.length + i)]
|
||||
patch[ fixPath(path + '-' + (old.length + i))] = value[(old.length + i)]
|
||||
}
|
||||
} else if (prop.indexOf('Array-') === 0) {
|
||||
patch['store.' + fixPath(path)] = value
|
||||
patch[ fixPath(path)] = value
|
||||
} else {
|
||||
patch['store.' + fixPath(path + '-' + prop)] = value
|
||||
patch[ fixPath(path + '-' + prop)] = value
|
||||
}
|
||||
|
||||
_update(patch, store)
|
||||
|
@ -176,7 +83,7 @@ function _update(kv, store) {
|
|||
})
|
||||
}
|
||||
store.onChange && store.onChange(kv)
|
||||
config.logger.isOpen && storeChangeLogger(store)
|
||||
store.debug && storeChangeLogger(store)
|
||||
}
|
||||
|
||||
function storeChangeLogger (store) {
|
||||
|
@ -197,34 +104,16 @@ function storeChangeLogger (store) {
|
|||
}
|
||||
|
||||
function updateStoreByFnProp(ele, data) {
|
||||
if(data.store){
|
||||
if(data){
|
||||
let patch = {}
|
||||
for (let key in data.store.__fnMapping) {
|
||||
patch['store.' + key] = data.store.__fnMapping[key].call(data)
|
||||
for (let key in data.__fnMapping) {
|
||||
patch[key] = data.__fnMapping[key].call(data)
|
||||
}
|
||||
ele.setData(patch)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function extendStoreMethod(data) {
|
||||
data.method = function (path, fn) {
|
||||
//fnMapping[path] = fn
|
||||
//data??
|
||||
data.__fnMapping = data.__fnMapping || {}
|
||||
data.__fnMapping[path] = fn
|
||||
let ok = getObjByPath(path, data)
|
||||
Object.defineProperty(ok.obj, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(data)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getObjByPath(path, data) {
|
||||
const arr = path.replace(/]/g, '').replace(/\[/g, '.').split('.')
|
||||
|
@ -240,73 +129,60 @@ function getObjByPath(path, data) {
|
|||
}
|
||||
}
|
||||
|
||||
function walk(data, tag) {
|
||||
function walk(data) {
|
||||
//___walked 用于标记是否已经观察遍历了
|
||||
data.___walked = true
|
||||
Object.keys(data).forEach(key => {
|
||||
const obj = data[key]
|
||||
const tp = type(obj)
|
||||
if (tp == FUNCTIONTYPE) {
|
||||
setProp(key, obj, data, tag)
|
||||
setProp(key, obj, data)
|
||||
} else if (tp == OBJECTTYPE) {
|
||||
Object.keys(obj).forEach(subKey => {
|
||||
_walk(obj[subKey], key + '.' + subKey, data, tag)
|
||||
_walk(obj[subKey], key + '.' + subKey, data)
|
||||
})
|
||||
|
||||
} else if (tp == ARRAYTYPE) {
|
||||
obj.forEach((item, index) => {
|
||||
_walk(item, key + '[' + index + ']', data, tag)
|
||||
_walk(item, key + '[' + index + ']', data)
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _walk(obj, path, data, tag) {
|
||||
function _walk(obj, path, data) {
|
||||
const tp = type(obj)
|
||||
if (tp == FUNCTIONTYPE) {
|
||||
setProp(path, obj, data, tag)
|
||||
setProp(path, obj, data)
|
||||
} else if (tp == OBJECTTYPE) {
|
||||
Object.keys(obj).forEach(subKey => {
|
||||
_walk(obj[subKey], path + '.' + subKey, data, tag)
|
||||
_walk(obj[subKey], path + '.' + subKey, data)
|
||||
})
|
||||
|
||||
} else if (tp == ARRAYTYPE) {
|
||||
obj.forEach((item, index) => {
|
||||
_walk(item, path + '[' + index + ']', data, tag)
|
||||
_walk(item, path + '[' + index + ']', data)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function setProp(path, fn, data, tag) {
|
||||
function setProp(path, fn, data) {
|
||||
const ok = getObjByPath(path, data)
|
||||
|
||||
if (tag) {
|
||||
data.__fnMapping = data.__fnMapping || {}
|
||||
data.__fnMapping[path] = fn
|
||||
Object.defineProperty(ok.obj, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(ok.obj)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
data.store = data.store || {}
|
||||
data.store.__fnMapping = data.store.__fnMapping || {}
|
||||
data.store.__fnMapping[path] = fn
|
||||
Object.defineProperty(ok.obj.store, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(ok.obj)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
}
|
||||
data.__fnMapping = data.__fnMapping || {}
|
||||
data.__fnMapping[path] = fn
|
||||
Object.defineProperty(ok.obj, ok.key, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
return fn.call(ok.obj)
|
||||
},
|
||||
set: () => {
|
||||
console.warn('Please using this.data.method to set method prop of data!')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -314,11 +190,37 @@ function type(obj) {
|
|||
return Object.prototype.toString.call(obj)
|
||||
}
|
||||
|
||||
create.Page = _Page
|
||||
create.Component = _Component
|
||||
|
||||
|
||||
function fixPath(path) {
|
||||
let mpPath = ''
|
||||
const arr = path.replace('#-', '').split('-')
|
||||
arr.forEach((item, index) => {
|
||||
if (index) {
|
||||
if (isNaN(parseInt(item))) {
|
||||
mpPath += '.' + item
|
||||
} else {
|
||||
mpPath += '[' + item + ']'
|
||||
}
|
||||
} else {
|
||||
mpPath += item
|
||||
}
|
||||
})
|
||||
return mpPath
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateByFnProp(ele, data) {
|
||||
let patch = {}
|
||||
for (let key in data.__fnMapping) {
|
||||
patch[key] = data.__fnMapping[key].call(ele.oData)
|
||||
}
|
||||
ele.setData(patch)
|
||||
}
|
||||
|
||||
|
||||
create.obaa = obaa
|
||||
create.mitt = mitt
|
||||
create.emitter = mitt()
|
||||
|
||||
|
||||
export default create
|
||||
|
|
Loading…
Reference in New Issue