From 89363ead86ac5ac44fdaf0ba678beac6f3510df4 Mon Sep 17 00:00:00 2001 From: Steve Young <2747745470@qq.com> Date: Fri, 31 Aug 2018 17:54:56 +0800 Subject: [PATCH] Feature/set data (#55) * chore: add npmignore and ignore pattern * feat(tua-mp): hack native setData * chore: move some devDependencies into packages * refactor(tua-mp): index -> TuaPage/TuaComp, add utils/hackSetData * docs: update basic example, add setData example * perf: add error message when call setData with uninitialized properties * docs: close #41, update mini code url, add hack setData part * perf: modify vm.data when call setData --- README.md | 4 +- docs/README.md | 2 +- docs/quick-start/README.md | 30 ++- lerna.json | 4 +- package.json | 6 - packages/tua-mp-cli/.npmignore | 5 + packages/tua-mp-cli/package.json | 8 +- packages/tua-mp-service/.npmignore | 4 + packages/tua-mp-service/package.json | 5 + packages/tua-mp/__tests__/TuaComp.test.js | 203 ++++++++++++++++++ .../{index.test.js => TuaPage.test.js} | 196 +---------------- packages/tua-mp/__tests__/setData.test.js | 52 +++++ packages/tua-mp/__tests__/utils/basic.test.js | 2 +- .../basic/comps/view-button/view-button.js | 10 +- .../tua-mp/examples/basic/pages/test/test.js | 32 ++- .../examples/basic/pages/test/test.wxml | 20 +- .../tua-mp/examples/basic/project.config.json | 10 +- .../tua-mp/examples/basic/utils/tua-mp.js | 129 +++++++---- packages/tua-mp/package.json | 22 +- packages/tua-mp/src/TuaComp.js | 89 ++++++++ packages/tua-mp/src/TuaPage.js | 77 +++++++ packages/tua-mp/src/VmStatus.js | 8 +- packages/tua-mp/src/index.js | 145 +------------ packages/tua-mp/src/utils/basic.js | 36 +++- packages/tua-mp/src/utils/hackSetData.js | 26 +++ packages/tua-mp/src/utils/index.js | 1 + 26 files changed, 688 insertions(+), 438 deletions(-) create mode 100644 packages/tua-mp-cli/.npmignore create mode 100644 packages/tua-mp-service/.npmignore create mode 100644 packages/tua-mp/__tests__/TuaComp.test.js rename packages/tua-mp/__tests__/{index.test.js => TuaPage.test.js} (66%) create mode 100644 packages/tua-mp/__tests__/setData.test.js create mode 100644 packages/tua-mp/src/TuaComp.js create mode 100644 packages/tua-mp/src/TuaPage.js create mode 100644 packages/tua-mp/src/utils/hackSetData.js diff --git a/README.md b/README.md index 030d2b7..32b1eca 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- Build Status + Build Status Coverage Status Version License @@ -28,7 +28,7 @@ ## 1.1.最基础的使用方式 -- [examples/basic/](https://github.com/tuateam/tua-mp/tree/master/packages/tua-mp/examples/basic) 下载 [https://github.com/tuateam/tua-mp/blob/master/packages/tua-mp/examples/basic/utils/tua-mp.js](https://github.com/tuateam/tua-mp/blob/master/packages/tua-mp/examples/basic/utils/tua-mp.js) 文件到你的小程序项目中,例如保存为 `utils/tua-mp.js`。 -代码片段地址为:**wechatide://minicode/JzXSn8mb78n8** +代码片段地址为:**wechatide://minicode/2n17t5mU752Z** > 可以尝试复制以上片段地址到浏览器地址栏中打开 diff --git a/docs/README.md b/docs/README.md index 45d46be..e4a87ca 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ footer: MIT Licensed | Copyright © 2018-present StEve Young

- Build Status + Build Status Coverage Status Version License diff --git a/docs/quick-start/README.md b/docs/quick-start/README.md index 2171e54..6c77004 100644 --- a/docs/quick-start/README.md +++ b/docs/quick-start/README.md @@ -7,7 +7,7 @@ 官方指南假设你已了解关于 [微信小程序开发](https://developers.weixin.qq.com/miniprogram/dev/index.html) 和 [Vue.js](https://cn.vuejs.org/v2/guide/index.html) 的基础知识。 ::: -尝试 `tua-mp` 最简单的方法是 [👉点击这里打开代码片段👈](wechatide://minicode/JzXSn8mb78n8),这个操作会打开你的**微信开发者工具**,并导入代码片段。(详情可参阅 [代码片段文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/minicode.html)) +尝试 `tua-mp` 最简单的方法是 [👉点击这里打开代码片段👈](wechatide://minicode/2n17t5mU752Z),这个操作会打开你的**微信开发者工具**,并导入代码片段。(详情可参阅 [代码片段文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/minicode.html)) 如果还没有安装 【微信开发者工具】 [👉点击这里下载👈](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) @@ -38,9 +38,35 @@ TuaPage({ 我们已经成功创建了第一个 `tua-mp` 应用!看起来这跟渲染一个字符串模板非常类似,但是 `tua-mp` 在背后做了一点微小的工作。 -现在改变数据已经不需要调用 `setData`,所有数据都是响应式的。我们要怎么确认呢?[👉点击这里打开代码片段👈](wechatide://minicode/JzXSn8mb78n8),在控制台里修改 `global.msg` 的值,你将看到上例相应地更新。 +现在改变数据已经不需要调用 `setData`,所有数据都是响应式的。我们要怎么确认呢?[👉点击这里打开代码片段👈](wechatide://minicode/2n17t5mU752Z),在控制台里修改 `global.msg` 的值,你将看到上例相应地更新。 ```js // 在开发者工具的控制台中 global.msg = 'young' ``` + +## hack 原生 setData +在 `v0.8.0` 中对于原生的 `setData` 方法进行了 hack。例如: + +```js {6} +TuaPage({ + data: { msg: 'steve' }, + computed: { msgPlus: vm => vm.msg + '+' }, + created () { + // 使用 setData 也会触发 computed + this.setData({ msg: 'young' }) + }, +}) +``` + +::: tip +之所以选择 hack `setData` 方法,是因为在改造旧项目时,可能已经有了大量的 `setData` 代码。这样在将其向 [过渡版本](./simple-app.md) 改造的过程中,使用 `setData` 更新的数据不是响应式(Reactive)的,因此重构过程可能十分痛苦... + +因此 hack 了 `setData` 之后,只需替换 Page、Component 为 TuaPage、TuaComp 后即可马上使用 computed、watch 等特性,页面依然能够跑起来。这样就可以渐进式地将小程序风格的旧代码改为 Vue 风格代码。 +::: + +::: warning +虽然小程序原生支持对于未在 data 中定义的数据进行 `setData`,但是在 `tua-mp` 中如果该属性未定义则会报错! + +建议在 `data` 选项中先定义该数据![点我查看更多](https://cn.vuejs.org/v2/guide/reactivity.html#%E5%A3%B0%E6%98%8E%E5%93%8D%E5%BA%94%E5%BC%8F%E5%B1%9E%E6%80%A7) +::: diff --git a/lerna.json b/lerna.json index 4bc0b7a..8589102 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,9 @@ "command": { "publish": { "ignoreChanges": [ - "*.md" + "**/__mocks__/**", + "**/__tests__/**", + "**/*.md" ] } }, diff --git a/package.json b/package.json index 2e1aefe..0870a3d 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,6 @@ "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.7.0", - "codecov": "^3.0.2", - "cross-env": "^5.2.0", - "eslint": "^4.19.1", "eslint-config-standard": "^11.0.0", "eslint-plugin-import": "^2.12.0", "eslint-plugin-node": "^6.0.1", @@ -37,12 +34,9 @@ "eslint-plugin-standard": "^3.1.0", "gh-pages": "^1.2.0", "husky": "^1.0.0-rc.13", - "jest": "^23.3.0", "lerna": "^3.0.3", "lint-staged": "^7.2.2", "metro-memory-fs": "^0.43.5", - "rimraf": "^2.6.2", - "rollup": "^0.59.4", "rollup-plugin-babel": "^3.0.4", "rollup-plugin-eslint": "^4.0.0", "rollup-plugin-json": "^3.0.0", diff --git a/packages/tua-mp-cli/.npmignore b/packages/tua-mp-cli/.npmignore new file mode 100644 index 0000000..42c0857 --- /dev/null +++ b/packages/tua-mp-cli/.npmignore @@ -0,0 +1,5 @@ +__mocks__/ +__tests__/ + +src/ +coverage/ diff --git a/packages/tua-mp-cli/package.json b/packages/tua-mp-cli/package.json index 7e3a9e8..be30644 100644 --- a/packages/tua-mp-cli/package.json +++ b/packages/tua-mp-cli/package.json @@ -12,7 +12,7 @@ "lint": "eslint --fix lib/**/*.js bin/*", "clean": "rimraf src && mkdir src/ src/app/ src/apis/ src/pages/ src/comps/ && echo {} > src/app/app.json", "precommit": "lint-staged", - "pub": "yarn test && npm publish" + "pub": "npm test && npm publish" }, "lint-staged": { "bin/*": [ @@ -69,5 +69,11 @@ }, "engines": { "node": ">=8" + }, + "devDependencies": { + "codecov": "^3.0.4", + "cross-env": "^5.2.0", + "jest": "^23.5.0", + "rimraf": "^2.6.2" } } diff --git a/packages/tua-mp-service/.npmignore b/packages/tua-mp-service/.npmignore new file mode 100644 index 0000000..8d86ff1 --- /dev/null +++ b/packages/tua-mp-service/.npmignore @@ -0,0 +1,4 @@ +__mocks__/ +__tests__/ + +coverage/ diff --git a/packages/tua-mp-service/package.json b/packages/tua-mp-service/package.json index 6647f92..f9ab148 100644 --- a/packages/tua-mp-service/package.json +++ b/packages/tua-mp-service/package.json @@ -81,5 +81,10 @@ }, "engines": { "node": ">=8" + }, + "devDependencies": { + "codecov": "^3.0.4", + "cross-env": "^5.2.0", + "jest": "^23.5.0" } } diff --git a/packages/tua-mp/__tests__/TuaComp.test.js b/packages/tua-mp/__tests__/TuaComp.test.js new file mode 100644 index 0000000..573b601 --- /dev/null +++ b/packages/tua-mp/__tests__/TuaComp.test.js @@ -0,0 +1,203 @@ +import { afterSetData } from './utils' +import { TuaComp } from '../src' + +const event = { + "currentTarget": { + "id": "", + "offsetLeft": 0, + "offsetTop": 0, + "dataset": { "index": 0 } + }, + "detail": { "value": [] } +} + +const eventVal = { "value": [], "index": 0 } + +const getTestForReservedKeys = (type) => { + expect(() => type({ data: { $data: 'young' } })).toThrow() + expect(() => type({ data: { __TUA_PATH__: 'path' } })).toThrow() + expect(() => type({ computed: { $computed () { return '!' } } })).toThrow() + expect(() => type({ methods: { $data () {} } })).toThrow() +} + +describe('TuaComp', () => { + test('component lifecycle', () => { + let lifecycleArr = [] + + const vm = TuaComp({ + data: { lc: 'a' }, + beforeCreate () { + lifecycleArr.push('beforeCreate') + }, + created () { + lifecycleArr.push('created') + }, + beforeMount () { + lifecycleArr.push('beforeMount') + }, + ready () { + lifecycleArr.push('ready') + }, + mounted () { + lifecycleArr.push('mounted') + }, + beforeUpdate () { + lifecycleArr.push('beforeUpdate') + }, + updated () { + lifecycleArr.push('updated') + }, + beforeDestroy () { + lifecycleArr.push('beforeDestroy') + }, + destroyed () { + lifecycleArr.push('destroyed') + }, + }) + + expect(lifecycleArr).toEqual(['beforeCreate', 'created', 'beforeMount', 'ready', 'mounted']) + + lifecycleArr = [] + vm.lc = 'b' + + afterSetData(() => { + expect(lifecycleArr).toEqual(['beforeUpdate', 'updated']) + + lifecycleArr = [] + vm.detached() + expect(lifecycleArr).toEqual(['beforeDestroy', 'destroyed']) + }) + }) + + test('throw error when using reserved keys', () => getTestForReservedKeys(TuaComp)) + + test('deep and immediate watch', (done) => { + const vm = TuaComp({ + data () { + return { + steve: 'young', + a: { b: { c: 'd' } }, + } + }, + computed: { + e () { return this.steve + this.a.b.c }, + }, + watch: { + 'a.b': { + deep: true, + immediate: true, + handler (newVal, oldVal) { + this.newAB = newVal + this.oldAB = oldVal + }, + }, + e: { immediate: true, handler: 'eFn' }, + }, + methods: { + eFn (val) { this.newE = val }, + }, + }) + + expect(vm.newE).toBe(vm.e) + expect(vm.e).toBe(vm.steve + 'd') + expect(vm.newAB).toEqual({ c: 'd' }) + expect(vm.oldAB).toEqual(undefined) + vm.a.b.c = 'e' + + afterSetData(() => { + expect(vm.e).toBe(vm.steve + 'e') + expect(vm.newAB).toEqual({ c: 'e' }) + expect(vm.oldAB).toEqual({ c: 'e' }) + done() + }) + }) + + test('use it just like MINA', (done) => { + const vm = TuaComp({ + properties: { + propA: String, + propB: { + type: Number, + }, + propC: { + type: String, + value: 'steve', + }, + }, + data: { compData: 'compData' }, + detached () {}, + methods: { + onChangeVal (e) { + this.$emit('onChangeVal', e) + }, + onEmitVal () { + this.$emit('onEmitVal') + }, + triggerEvent: jest.fn(), + }, + }) + + vm.compData = 'steve' + expect(vm.propA).toBe('') + expect(vm.propB).toBe(0) + expect(vm.propC).toBe('steve') + expect(vm.compData).toBe('steve') + + vm.onChangeVal(event) + expect(vm.triggerEvent).toBeCalledWith('onChangeVal', eventVal, undefined) + + vm.onEmitVal() + expect(vm.triggerEvent).toBeCalledWith('onEmitVal', {}, undefined) + + afterSetData(() => { + vm.detached() + expect(vm.data.compData).toBe('steve') + done() + }) + }) + + test('use it just like Vue', () => { + const vm = TuaComp({ + props: { + propA: Number, + propB: [String, Number], + propC: { type: String, required: true }, + propD: { type: Number, default: 100 }, + propE: { type: Object, default: () => ({ message: 'hello' }) }, + propF: { validator: val => ['success', 'warning', 'danger'].indexOf(val) !== -1 }, + }, + computed: { + dAndE () { return this.propD + this.propE.message }, + }, + }) + expect(vm.propA).toBe(0) + expect(vm.propB).toBe('') + expect(vm.propC).toBe('') + expect(vm.propD).toBe(100) + expect(vm.propE.message).toBe('hello') + expect(vm.propF).toBe('') + expect(vm.dAndE).toBe('100hello') + }) + + test('edge case', () => { + const data = jest.fn(() => ({})) + const attached = jest.fn() + TuaComp({ data, attached }) + + expect(data).toBeCalled() + expect(attached).toBeCalled() + }) + + // close #37 + test('not plain object data', () => { + const now = new Date() + const hour = now.getHours() + const vm = TuaComp({ + data () { return { now } }, + computed: { + hour () { return this.now.getHours() }, + }, + }) + expect(hour).toBe(vm.hour) + }) +}) diff --git a/packages/tua-mp/__tests__/index.test.js b/packages/tua-mp/__tests__/TuaPage.test.js similarity index 66% rename from packages/tua-mp/__tests__/index.test.js rename to packages/tua-mp/__tests__/TuaPage.test.js index 784c65f..a3aaea6 100644 --- a/packages/tua-mp/__tests__/index.test.js +++ b/packages/tua-mp/__tests__/TuaPage.test.js @@ -1,17 +1,5 @@ +import { TuaPage } from '../src' import { afterSetData } from './utils' -import { TuaComp, TuaPage } from '../src' - -const event = { - "currentTarget": { - "id": "", - "offsetLeft": 0, - "offsetTop": 0, - "dataset": { "index": 0 } - }, - "detail": { "value": [] } -} - -const eventVal = { "value": [], "index": 0 } const getTestForReservedKeys = (type) => { expect(() => type({ data: { $data: 'young' } })).toThrow() @@ -20,188 +8,6 @@ const getTestForReservedKeys = (type) => { expect(() => type({ methods: { $data () {} } })).toThrow() } -describe('TuaComp', () => { - test('component lifecycle', () => { - let lifecycleArr = [] - - const vm = TuaComp({ - data: { lc: 'a' }, - beforeCreate () { - lifecycleArr.push('beforeCreate') - }, - created () { - lifecycleArr.push('created') - }, - beforeMount () { - lifecycleArr.push('beforeMount') - }, - ready () { - lifecycleArr.push('ready') - }, - mounted () { - lifecycleArr.push('mounted') - }, - beforeUpdate () { - lifecycleArr.push('beforeUpdate') - }, - updated () { - lifecycleArr.push('updated') - }, - beforeDestroy () { - lifecycleArr.push('beforeDestroy') - }, - destroyed () { - lifecycleArr.push('destroyed') - }, - }) - - expect(lifecycleArr).toEqual(['beforeCreate', 'created', 'beforeMount', 'ready', 'mounted']) - - lifecycleArr = [] - vm.lc = 'b' - - afterSetData(() => { - expect(lifecycleArr).toEqual(['beforeUpdate', 'updated']) - - lifecycleArr = [] - vm.detached() - expect(lifecycleArr).toEqual(['beforeDestroy', 'destroyed']) - }) - }) - - test('throw error when using reserved keys', () => getTestForReservedKeys(TuaComp)) - - test('deep and immediate watch', (done) => { - const vm = TuaComp({ - data () { - return { - steve: 'young', - a: { b: { c: 'd' } }, - } - }, - computed: { - e () { return this.steve + this.a.b.c }, - }, - watch: { - 'a.b': { - deep: true, - immediate: true, - handler (newVal, oldVal) { - this.newAB = newVal - this.oldAB = oldVal - }, - }, - e: { immediate: true, handler: 'eFn' }, - }, - methods: { - eFn (val) { this.newE = val }, - }, - }) - - expect(vm.newE).toBe(vm.e) - expect(vm.e).toBe(vm.steve + 'd') - expect(vm.newAB).toEqual({ c: 'd' }) - expect(vm.oldAB).toEqual(undefined) - vm.a.b.c = 'e' - - afterSetData(() => { - expect(vm.e).toBe(vm.steve + 'e') - expect(vm.newAB).toEqual({ c: 'e' }) - expect(vm.oldAB).toEqual({ c: 'e' }) - done() - }) - }) - - test('use it just like MINA', (done) => { - const vm = TuaComp({ - properties: { - propA: String, - propB: { - type: Number, - }, - propC: { - type: String, - value: 'steve', - }, - }, - data: { compData: 'compData' }, - detached () {}, - methods: { - onChangeVal (e) { - this.$emit('onChangeVal', e) - }, - onEmitVal () { - this.$emit('onEmitVal') - }, - triggerEvent: jest.fn(), - }, - }) - - vm.compData = 'steve' - expect(vm.propA).toBe('') - expect(vm.propB).toBe(0) - expect(vm.propC).toBe('steve') - expect(vm.compData).toBe('steve') - - vm.onChangeVal(event) - expect(vm.triggerEvent).toBeCalledWith('onChangeVal', eventVal, undefined) - - vm.onEmitVal() - expect(vm.triggerEvent).toBeCalledWith('onEmitVal', {}, undefined) - - afterSetData(() => { - vm.detached() - expect(vm.data.compData).toBe('steve') - done() - }) - }) - - test('use it just like Vue', () => { - const vm = TuaComp({ - props: { - propA: Number, - propB: [String, Number], - propC: { type: String, required: true }, - propD: { type: Number, default: 100 }, - propE: { type: Object, default: () => ({ message: 'hello' }) }, - propF: { validator: val => ['success', 'warning', 'danger'].indexOf(val) !== -1 }, - }, - computed: { - dAndE () { return this.propD + this.propE.message }, - }, - }) - expect(vm.propA).toBe(0) - expect(vm.propB).toBe('') - expect(vm.propC).toBe('') - expect(vm.propD).toBe(100) - expect(vm.propE.message).toBe('hello') - expect(vm.propF).toBe('') - expect(vm.dAndE).toBe('100hello') - }) - - test('edge case', () => { - const data = jest.fn(() => ({})) - const attached = jest.fn() - TuaComp({ data, attached }) - - expect(data).toBeCalled() - expect(attached).toBeCalled() - }) - - // close #37 - test('not plain object data', () => { - const now = new Date() - const hour = now.getHours() - const vm = TuaComp({ - data () { return { now } }, - computed: { - hour () { return this.now.getHours() }, - }, - }) - expect(hour).toBe(vm.hour) - }) -}) - describe('TuaPage', () => { test('page lifecycle', () => { let lifecycleArr = [] diff --git a/packages/tua-mp/__tests__/setData.test.js b/packages/tua-mp/__tests__/setData.test.js new file mode 100644 index 0000000..4adcb9b --- /dev/null +++ b/packages/tua-mp/__tests__/setData.test.js @@ -0,0 +1,52 @@ +import { afterSetData } from './utils' +import { TuaComp, TuaPage } from '../src' + +describe('TuaComp', () => { + afterEach(() => {}) + + test('nested setData with callback', (done) => { + const vm = TuaComp({ + data: { + a: { b: 'c' }, + young: 'young', + }, + computed: { + sAndY: vm => vm.a.b + vm.young, + }, + }) + + vm.setData({ 'a.b': 'steve' }, () => { + expect(vm.a.b).toBe('steve') + }) + + afterSetData(() => { + expect(vm.sAndY).toBe('steveyoung') + done() + }) + }) +}) + +describe('TuaPage', () => { + afterEach(() => {}) + + test('nested setData with callback', (done) => { + const vm = TuaPage({ + data: { + a: { b: 'c' }, + young: 'young', + }, + computed: { + sAndY: vm => vm.a.b + vm.young, + }, + }) + + vm.setData({ 'a.b': 'steve' }, () => { + expect(vm.a.b).toBe('steve') + }) + + afterSetData(() => { + expect(vm.sAndY).toBe('steveyoung') + done() + }) + }) +}) diff --git a/packages/tua-mp/__tests__/utils/basic.test.js b/packages/tua-mp/__tests__/utils/basic.test.js index ea21fb4..266d0d9 100644 --- a/packages/tua-mp/__tests__/utils/basic.test.js +++ b/packages/tua-mp/__tests__/utils/basic.test.js @@ -85,7 +85,7 @@ test('setObjByPath', () => { const obj = {} setObjByPath({ obj, path: 'a.b.c', val: 1 }) setObjByPath({ obj, path: 'a.b.d', val: 2 }) - setObjByPath({ obj, path: 'arr[0].b.c', val: 3 }) + setObjByPath({ obj, path: 'arr[0].b.c', val: 3, isCheckDef: true }) expect(obj.a.b.c).toEqual(1) expect(obj.a.b.d).toEqual(2) diff --git a/packages/tua-mp/examples/basic/comps/view-button/view-button.js b/packages/tua-mp/examples/basic/comps/view-button/view-button.js index 0e38b29..85c71cc 100644 --- a/packages/tua-mp/examples/basic/comps/view-button/view-button.js +++ b/packages/tua-mp/examples/basic/comps/view-button/view-button.js @@ -11,7 +11,7 @@ TuaComp({ type: String, // 并没有什么用,因为小程序组件没这概念 required: true, - // 强行让含 msg 的内容告警 + // 强行让不含 msg 的内容告警 validator: val => val.indexOf('msg') !== -1, } }, @@ -26,19 +26,19 @@ TuaComp({ }, created () { - console.log('created') + console.log('[view-button]: created') }, mounted () { - console.log('mounted') + console.log('[view-button]: mounted') }, beforeUpdate () { - console.log('beforeUpdate') + console.log('[view-button]: beforeUpdate') }, updated () { - console.log('updated') + console.log('[view-button]: updated') }, /** diff --git a/packages/tua-mp/examples/basic/pages/test/test.js b/packages/tua-mp/examples/basic/pages/test/test.js index a2b3857..29b4cbc 100644 --- a/packages/tua-mp/examples/basic/pages/test/test.js +++ b/packages/tua-mp/examples/basic/pages/test/test.js @@ -9,7 +9,7 @@ import { let n = 0 -const watchLog = (prefix) => (newVal, oldVal) => console.log(`${prefix}: ${oldVal} -> ${newVal}`) +const watchLog = (prefix) => (newVal, oldVal) => console.log(`[WATCH]: ${prefix}: ${oldVal} -> ${newVal}`) TuaPage({ data () { @@ -72,13 +72,13 @@ TuaPage({ // 在 setData 前调用 beforeUpdate () { console.log('beforeUpdate') - this.stringifyLog(this.data) + // this.stringifyLog(this.data) }, // 在 setData 第二个参数中调用(渲染完毕的回调) updated () { console.log('updated') - this.stringifyLog(this.data) + // this.stringifyLog(this.data) }, computed: { @@ -92,21 +92,17 @@ TuaPage({ return this.g + ' + ' + this.reversedG }, countPlus: { - get: function () { - return this.count + 1 - }, - set: function (v) { + get: vm => vm.count + 1, + set (v) { this.count = v - 1 }, }, }, watch: { - msg (newVal, oldVal) { - console.log(`msg: ${oldVal} -> ${newVal}`) - }, + msg: watchLog('msg'), 'a.b' (newVal, oldVal) { - console.log(`a.b: ${oldVal} -> ${newVal}`) + console.log(`[WATCH]: a.b: ${oldVal} -> ${newVal}`) setTimeout(() => { this.msg = this.reverseStr(this.msg) }, 1000) @@ -121,9 +117,7 @@ TuaPage({ // 直接填写 methods 名称 handler: 'logFromMethods', }, - 'gAndAB' (newVal, oldVal) { - console.log(`gAndAB: ${oldVal} -> ${newVal}`) - }, + 'gAndAB': watchLog('gAndAB'), // deep arr: [ watchLog('arr'), @@ -148,6 +142,9 @@ TuaPage({ tapAB () { this.a.b += n++ }, + tapSetDataAB () { + this.setData({ 'a.b': this.a.b + n++ }) + }, tapArr () { this.arr.push(n++) }, @@ -169,14 +166,11 @@ TuaPage({ unshiftNested () { this.arr.unshift({ c: { d: 'hey' } }) }, - gotoLogs () { - wx.navigateTo({ url: '/pages/logs/logs' }) - }, tapSetCountPlus () { - this.countPlus = 101 + this.countPlus += 1 }, tapSetReversedG () { - this.reversedG = 229 + this.reversedG = 'whatever value' }, }, }) diff --git a/packages/tua-mp/examples/basic/pages/test/test.wxml b/packages/tua-mp/examples/basic/pages/test/test.wxml index 3fa7d21..599cb77 100644 --- a/packages/tua-mp/examples/basic/pages/test/test.wxml +++ b/packages/tua-mp/examples/basic/pages/test/test.wxml @@ -1,5 +1,5 @@ - + + + + + arr: {{ arr }} - + count: {{ count }} + countPlus: {{ countPlus }} + + a.b: {{ a.b }} gAndAB: {{ gAndAB }} dataAndComputed: {{ dataAndComputed }} diff --git a/packages/tua-mp/examples/basic/project.config.json b/packages/tua-mp/examples/basic/project.config.json index 44fc83e..990455b 100644 --- a/packages/tua-mp/examples/basic/project.config.json +++ b/packages/tua-mp/examples/basic/project.config.json @@ -29,8 +29,14 @@ "list": [] }, "miniprogram": { - "current": -1, - "list": [] + "current": 0, + "list": [ + { + "id": -1, + "name": "test", + "pathName": "pages/test/test" + } + ] } } } \ No newline at end of file diff --git a/packages/tua-mp/examples/basic/utils/tua-mp.js b/packages/tua-mp/examples/basic/utils/tua-mp.js index 05fcd67..ef0e524 100644 --- a/packages/tua-mp/examples/basic/utils/tua-mp.js +++ b/packages/tua-mp/examples/basic/utils/tua-mp.js @@ -1,5 +1,3 @@ -var version = "0.7.3"; - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { @@ -189,21 +187,31 @@ var getValByPath = function getValByPath(obj) { * @param {Object} obj 目标对象 * @param {String} path 路径字符串 * @param {any} val 目标值 - * @returns {Object} obj + * @param {Boolean} isCheckDef 是否检查属性已定义 */ var setObjByPath = function setObjByPath(_ref) { var obj = _ref.obj, path = _ref.path, - val = _ref.val; - return pathStr2Arr(path).reduce(function (acc, cur, idx, _ref2) { - var length = _ref2.length; + val = _ref.val, + _ref$isCheckDef = _ref.isCheckDef, + isCheckDef = _ref$isCheckDef === undefined ? false : _ref$isCheckDef; + return pathStr2Arr(path).reduce(function (acc, cur, idx, arr) { + // 在调用 setData 时,有的属性可能没定义 + if (isCheckDef && acc[cur] === undefined) { + var parentStr = arr.slice(0, idx).reduce(function (acc, cur) { + return (/\d/.test(cur) ? acc + '[' + cur + ']' : acc + '.' + cur + ); + }, 'this'); + + error('Property "' + cur + '" is not found in "' + parentStr + '": ' + 'Make sure that this property has initialized in the data option.'); + } - if (idx === length - 1) { + if (idx === arr.length - 1) { acc[cur] = val; return; } - // 当前属性在目标对象上并不存在 + // 当前中间属性在目标对象上并不存在 if (!acc[cur]) { acc[cur] = /\d/.test(cur) ? [] : {}; } @@ -256,15 +264,21 @@ var assertType = function assertType(value, type) { * @param {any} out 输出的内容 */ var logByType = function logByType(type) { - return function (out) { + return function () { + var _console; + + for (var _len = arguments.length, out = Array(_len), _key = 0; _key < _len; _key++) { + out[_key] = arguments[_key]; + } /* istanbul ignore next */ - console[type]('[TUA-MP]:', out); + (_console = console)[type].apply(_console, ['[TUA-MP]:'].concat(out)); }; }; var log = logByType('log'); var warn = logByType('warn'); +var error = logByType('error'); // reserved keys var isReservedKeys = function isReservedKeys(str) { @@ -290,13 +304,13 @@ var checkReservedKeys = function checkReservedKeys(data, computed, methods) { * @param {Object} target 对象 */ var def = function def(key) { - return function (_ref3) { - var value = _ref3.value, - _ref3$enumerable = _ref3.enumerable, - enumerable = _ref3$enumerable === undefined ? false : _ref3$enumerable, - _ref3$configurable = _ref3.configurable, - configurable = _ref3$configurable === undefined ? true : _ref3$configurable, - rest = objectWithoutProperties(_ref3, ['value', 'enumerable', 'configurable']); + return function (_ref2) { + var value = _ref2.value, + _ref2$enumerable = _ref2.enumerable, + enumerable = _ref2$enumerable === undefined ? false : _ref2$enumerable, + _ref2$configurable = _ref2.configurable, + configurable = _ref2$configurable === undefined ? true : _ref2$configurable, + rest = objectWithoutProperties(_ref2, ['value', 'enumerable', 'configurable']); return function (target) { Object.defineProperty(target, key, _extends({ value: value, @@ -452,6 +466,33 @@ var getPropertiesFromProps = function getPropertiesFromProps(props) { }, {}); }; +var hackSetData = function hackSetData(vm) { + var originalSetData = vm.setData; + + Object.defineProperties(vm, { + 'setData': { + get: function get() { + return function (newVal, cb) { + Object.keys(newVal).forEach(function (path) { + // 针对 vm 上的属性赋值 + setObjByPath({ obj: vm, path: path, val: newVal[path], isCheckDef: true }); + + // 针对 vm.data 上的属性赋值 + setObjByPath({ obj: vm.data, path: path, val: newVal[path] }); + }); + + isFn(cb) && Promise.resolve().then(cb); + }; + } + }, + '__setData__': { get: function get() { + return originalSetData; + } } + }); +}; + +var version = "0.8.0"; + /** * 根据 vm 生成 key * @param {String} __wxWebviewId__ webview 的 id @@ -566,10 +607,12 @@ var VmStatus = function () { var oldState = _this.oldStateByVM[vmKey]; var getWatchFnArr = getWatchFnArrByVm(vm); + var setData = vm.__setData__ ? vm.__setData__ : vm.setData; + vm.beforeUpdate && vm.beforeUpdate(); // 更新数据 - vm.updated ? vm.setData(newState, vm.updated) : vm.setData(newState); + vm.updated ? setData.call(vm, newState, vm.updated) : setData.call(vm, newState); // 触发 watch Object.keys(newState).map(function (key) { @@ -1054,8 +1097,6 @@ var triggerImmediateWatch = function triggerImmediateWatch(vm, watch) { }); }; -log('Version ' + version); - /** * 适配 Vue 风格代码,生成小程序原生组件 * @param {Object|Function} data 组件的内部数据 @@ -1116,6 +1157,9 @@ var TuaComp = function TuaComp(_ref) { // 触发 immediate watch triggerImmediateWatch(this, watch); + // hack 原生 setData + hackSetData(this); + rest.attached && rest.attached.apply(this, options); }, ready: function ready() { @@ -1141,20 +1185,28 @@ var TuaComp = function TuaComp(_ref) { } })); }; -var TuaPage = function TuaPage(_ref2) { - var _ref2$data = _ref2.data, - rawData = _ref2$data === undefined ? {} : _ref2$data, - _ref2$watch = _ref2.watch, - watch = _ref2$watch === undefined ? {} : _ref2$watch, - _ref2$methods = _ref2.methods, - methods = _ref2$methods === undefined ? {} : _ref2$methods, - _ref2$computed = _ref2.computed, - computed = _ref2$computed === undefined ? {} : _ref2$computed, - rest = objectWithoutProperties(_ref2, ['data', 'watch', 'methods', 'computed']); + +/** + * 适配 Vue 风格代码,生成小程序页面 + * @param {Object|Function} data 页面组件的内部数据 + * @param {Object} watch 侦听器对象 + * @param {Object} methods 页面组件的方法,包括事件响应函数和任意的自定义方法 + * @param {Object} computed 计算属性 + */ +var TuaPage = function TuaPage(_ref) { + var _ref$data = _ref.data, + rawData = _ref$data === undefined ? {} : _ref$data, + _ref$watch = _ref.watch, + watch = _ref$watch === undefined ? {} : _ref$watch, + _ref$methods = _ref.methods, + methods = _ref$methods === undefined ? {} : _ref$methods, + _ref$computed = _ref.computed, + computed = _ref$computed === undefined ? {} : _ref$computed, + rest = objectWithoutProperties(_ref, ['data', 'watch', 'methods', 'computed']); return Page(_extends({}, rest, methods, { onLoad: function onLoad() { - for (var _len5 = arguments.length, options = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { - options[_key5] = arguments[_key5]; + for (var _len = arguments.length, options = Array(_len), _key = 0; _key < _len; _key++) { + options[_key] = arguments[_key]; } rest.beforeCreate && rest.beforeCreate.apply(this, options); @@ -1178,12 +1230,15 @@ var TuaPage = function TuaPage(_ref2) { // 触发 immediate watch triggerImmediateWatch(this, watch); + // hack 原生 setData + hackSetData(this); + rest.onLoad && rest.onLoad.apply(this, options); rest.created && rest.created.apply(this, options); }, onReady: function onReady() { - for (var _len6 = arguments.length, options = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { - options[_key6] = arguments[_key6]; + for (var _len2 = arguments.length, options = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + options[_key2] = arguments[_key2]; } rest.beforeMount && rest.beforeMount.apply(this, options); @@ -1191,8 +1246,8 @@ var TuaPage = function TuaPage(_ref2) { rest.mounted && rest.mounted.apply(this, options); }, onUnload: function onUnload() { - for (var _len7 = arguments.length, options = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) { - options[_key7] = arguments[_key7]; + for (var _len3 = arguments.length, options = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + options[_key3] = arguments[_key3]; } rest.beforeDestroy && rest.beforeDestroy.apply(this, options); @@ -1206,4 +1261,6 @@ var TuaPage = function TuaPage(_ref2) { })); }; +log('Version ' + version); + export { TuaComp, TuaPage }; diff --git a/packages/tua-mp/package.json b/packages/tua-mp/package.json index 976797b..5930f53 100644 --- a/packages/tua-mp/package.json +++ b/packages/tua-mp/package.json @@ -1,6 +1,6 @@ { "name": "tua-mp", - "version": "0.7.3", + "version": "0.8.0", "description": "A progressive miniprogram framework for coding like Vue", "main": "examples/basic/utils/tua-mp.js", "files": [ @@ -11,16 +11,15 @@ "tdd": "cross-env NODE_ENV=test jest --watch", "test": "cross-env NODE_ENV=test jest && codecov", "lint": "eslint --fix --ext .js src/", - "prebuild": "yarn test", "build": "cross-env NODE_ENV=prod rollup -c", "watch": "cross-env NODE_ENV=prod rollup -c -w", - "precommit": "lint-staged", - "start": "yarn watch", - "pub": "yarn build && npm publish" + "start": "npm run watch", + "pub": "npm run build && npm run publish", + "precommit": "npm run build && lint-staged" }, "lint-staged": { - "src/*.{js,vue}": [ - "yarn lint", + "src/**/*.{js,vue}": [ + "npm run lint", "git add" ] }, @@ -66,5 +65,12 @@ "bugs": { "url": "https://github.com/tuateam/tua-mp/issues" }, - "homepage": "https://github.com/tuateam/tua-mp#readme" + "homepage": "https://github.com/tuateam/tua-mp#readme", + "devDependencies": { + "codecov": "^3.0.4", + "cross-env": "^5.2.0", + "eslint": "^5.4.0", + "jest": "^23.5.0", + "rollup": "^0.64.1" + } } diff --git a/packages/tua-mp/src/TuaComp.js b/packages/tua-mp/src/TuaComp.js new file mode 100644 index 0000000..99e5164 --- /dev/null +++ b/packages/tua-mp/src/TuaComp.js @@ -0,0 +1,89 @@ +import { + isFn, + $emit, + hackSetData, + checkReservedKeys, + getPropertiesFromProps, +} from './utils/index' +import { + deleteVm, + getAsyncSetData, +} from './asyncSetData' +import { + getObserveDeep, +} from './observer/index' +import { + bindData, + bindComputed, + triggerImmediateWatch, +} from './init' + +/** + * 适配 Vue 风格代码,生成小程序原生组件 + * @param {Object|Function} data 组件的内部数据 + * @param {Object|Function|Null} props 组件的对外属性 + * @param {Object} watch 侦听器对象 + * @param {Object} methods 组件的方法,包括事件响应函数和任意的自定义方法 + * @param {Object} computed 计算属性 + * @param {Object|Function|Null} properties 小程序原生的组件的对外属性 + */ +export const TuaComp = ({ + data: rawData = {}, + props = {}, + watch = {}, + methods = {}, + computed = {}, + properties = {}, + ...rest +}) => Component({ + ...rest, + methods: { ...methods, $emit }, + properties: { + ...properties, + ...getPropertiesFromProps(props), + }, + created (...options) { + rest.beforeCreate && rest.beforeCreate.apply(this, options) + rest.created && rest.created.apply(this, options) + }, + attached (...options) { + rest.beforeMount && rest.beforeMount.apply(this, options) + + const data = isFn(rawData) ? rawData() : rawData + const asyncSetData = getAsyncSetData(this, watch) + const observeDeep = getObserveDeep(asyncSetData) + + // 检查是否使用了保留字 + checkReservedKeys(data, computed, methods) + + // 初始化数据 + this.setData(data) + + // 遍历递归观察 data + bindData(this, { ...this.data, ...data }, observeDeep) + + // 遍历观察 computed + bindComputed(this, computed, asyncSetData) + + // 触发 immediate watch + triggerImmediateWatch(this, watch) + + // hack 原生 setData + hackSetData(this) + + rest.attached && rest.attached.apply(this, options) + }, + ready (...options) { + rest.ready && rest.ready.apply(this, options) + rest.mounted && rest.mounted.apply(this, options) + }, + detached (...options) { + rest.beforeDestroy && rest.beforeDestroy.apply(this, options) + + // 从 VM_MAP 中删除自己 + deleteVm(this) + + rest.detached && rest.detached.apply(this, options) + rest.destroyed && rest.destroyed.apply(this, options) + }, +}) diff --git a/packages/tua-mp/src/TuaPage.js b/packages/tua-mp/src/TuaPage.js new file mode 100644 index 0000000..29ddb47 --- /dev/null +++ b/packages/tua-mp/src/TuaPage.js @@ -0,0 +1,77 @@ +import { + isFn, + hackSetData, + checkReservedKeys, +} from './utils/index' +import { + deleteVm, + getAsyncSetData, +} from './asyncSetData' +import { + getObserveDeep, +} from './observer/index' +import { + bindData, + bindComputed, + triggerImmediateWatch, +} from './init' + +/** + * 适配 Vue 风格代码,生成小程序页面 + * @param {Object|Function} data 页面组件的内部数据 + * @param {Object} watch 侦听器对象 + * @param {Object} methods 页面组件的方法,包括事件响应函数和任意的自定义方法 + * @param {Object} computed 计算属性 + */ +export const TuaPage = ({ + data: rawData = {}, + watch = {}, + methods = {}, + computed = {}, + ...rest +}) => Page({ + ...rest, + ...methods, + onLoad (...options) { + rest.beforeCreate && rest.beforeCreate.apply(this, options) + + const data = isFn(rawData) ? rawData() : rawData + const asyncSetData = getAsyncSetData(this, watch) + const observeDeep = getObserveDeep(asyncSetData) + + // 检查是否使用了保留字 + checkReservedKeys(data, computed, methods) + + // 初始化数据 + this.setData(data) + + // 遍历递归观察 data + bindData(this, data, observeDeep) + + // 遍历观察 computed + bindComputed(this, computed, asyncSetData) + + // 触发 immediate watch + triggerImmediateWatch(this, watch) + + // hack 原生 setData + hackSetData(this) + + rest.onLoad && rest.onLoad.apply(this, options) + rest.created && rest.created.apply(this, options) + }, + onReady (...options) { + rest.beforeMount && rest.beforeMount.apply(this, options) + rest.onReady && rest.onReady.apply(this, options) + rest.mounted && rest.mounted.apply(this, options) + }, + onUnload (...options) { + rest.beforeDestroy && rest.beforeDestroy.apply(this, options) + + // 从 VM_MAP 中删除自己 + deleteVm(this) + + rest.onUnload && rest.onUnload.apply(this, options) + rest.destroyed && rest.destroyed.apply(this, options) + }, +}) diff --git a/packages/tua-mp/src/VmStatus.js b/packages/tua-mp/src/VmStatus.js index b8b5af4..1d6b8bd 100644 --- a/packages/tua-mp/src/VmStatus.js +++ b/packages/tua-mp/src/VmStatus.js @@ -92,12 +92,16 @@ export default class VmStatus { const oldState = this.oldStateByVM[vmKey] const getWatchFnArr = getWatchFnArrByVm(vm) + const setData = vm.__setData__ + ? vm.__setData__ + : vm.setData + vm.beforeUpdate && vm.beforeUpdate() // 更新数据 vm.updated - ? vm.setData(newState, vm.updated) - : vm.setData(newState) + ? setData.call(vm, newState, vm.updated) + : setData.call(vm, newState) // 触发 watch Object.keys(newState) diff --git a/packages/tua-mp/src/index.js b/packages/tua-mp/src/index.js index bbc8673..89d4833 100644 --- a/packages/tua-mp/src/index.js +++ b/packages/tua-mp/src/index.js @@ -1,146 +1,7 @@ +import { log } from './utils/index' import { version } from '../package.json' -import { - log, - isFn, - $emit, - checkReservedKeys, - getPropertiesFromProps, -} from './utils/index' -import { - deleteVm, - getAsyncSetData, -} from './asyncSetData' -import { - getObserveDeep, -} from './observer/index' -import { - bindData, - bindComputed, - triggerImmediateWatch, -} from './init' log(`Version ${version}`) -/** - * 适配 Vue 风格代码,生成小程序原生组件 - * @param {Object|Function} data 组件的内部数据 - * @param {Object|Function|Null} props 组件的对外属性 - * @param {Object} watch 侦听器对象 - * @param {Object} methods 组件的方法,包括事件响应函数和任意的自定义方法 - * @param {Object} computed 计算属性 - * @param {Object|Function|Null} properties 小程序原生的组件的对外属性 - */ -export const TuaComp = ({ - data: rawData = {}, - props = {}, - watch = {}, - methods = {}, - computed = {}, - properties = {}, - ...rest -}) => Component({ - ...rest, - methods: { ...methods, $emit }, - properties: { - ...properties, - ...getPropertiesFromProps(props), - }, - created (...options) { - rest.beforeCreate && rest.beforeCreate.apply(this, options) - rest.created && rest.created.apply(this, options) - }, - attached (...options) { - rest.beforeMount && rest.beforeMount.apply(this, options) - - const data = isFn(rawData) ? rawData() : rawData - const asyncSetData = getAsyncSetData(this, watch) - const observeDeep = getObserveDeep(asyncSetData) - - // 检查是否使用了保留字 - checkReservedKeys(data, computed, methods) - - // 初始化数据 - this.setData(data) - - // 遍历递归观察 data - bindData(this, { ...this.data, ...data }, observeDeep) - - // 遍历观察 computed - bindComputed(this, computed, asyncSetData) - - // 触发 immediate watch - triggerImmediateWatch(this, watch) - - rest.attached && rest.attached.apply(this, options) - }, - ready (...options) { - rest.ready && rest.ready.apply(this, options) - rest.mounted && rest.mounted.apply(this, options) - }, - detached (...options) { - rest.beforeDestroy && rest.beforeDestroy.apply(this, options) - - // 从 VM_MAP 中删除自己 - deleteVm(this) - - rest.detached && rest.detached.apply(this, options) - rest.destroyed && rest.destroyed.apply(this, options) - }, -}) - -/** - * 适配 Vue 风格代码,生成小程序页面 - * @param {Object|Function} data 页面组件的内部数据 - * @param {Object} watch 侦听器对象 - * @param {Object} methods 页面组件的方法,包括事件响应函数和任意的自定义方法 - * @param {Object} computed 计算属性 - */ -export const TuaPage = ({ - data: rawData = {}, - watch = {}, - methods = {}, - computed = {}, - ...rest -}) => Page({ - ...rest, - ...methods, - onLoad (...options) { - rest.beforeCreate && rest.beforeCreate.apply(this, options) - - const data = isFn(rawData) ? rawData() : rawData - const asyncSetData = getAsyncSetData(this, watch) - const observeDeep = getObserveDeep(asyncSetData) - - // 检查是否使用了保留字 - checkReservedKeys(data, computed, methods) - - // 初始化数据 - this.setData(data) - - // 遍历递归观察 data - bindData(this, data, observeDeep) - - // 遍历观察 computed - bindComputed(this, computed, asyncSetData) - - // 触发 immediate watch - triggerImmediateWatch(this, watch) - - rest.onLoad && rest.onLoad.apply(this, options) - rest.created && rest.created.apply(this, options) - }, - onReady (...options) { - rest.beforeMount && rest.beforeMount.apply(this, options) - rest.onReady && rest.onReady.apply(this, options) - rest.mounted && rest.mounted.apply(this, options) - }, - onUnload (...options) { - rest.beforeDestroy && rest.beforeDestroy.apply(this, options) - - // 从 VM_MAP 中删除自己 - deleteVm(this) - - rest.onUnload && rest.onUnload.apply(this, options) - rest.destroyed && rest.destroyed.apply(this, options) - }, -}) +export * from './TuaComp' +export * from './TuaPage' diff --git a/packages/tua-mp/src/utils/basic.js b/packages/tua-mp/src/utils/basic.js index 72f202a..1a3f37a 100644 --- a/packages/tua-mp/src/utils/basic.js +++ b/packages/tua-mp/src/utils/basic.js @@ -64,16 +64,33 @@ export const getValByPath = (obj) => (path) => pathStr2Arr(path) * @param {Object} obj 目标对象 * @param {String} path 路径字符串 * @param {any} val 目标值 - * @returns {Object} obj + * @param {Boolean} isCheckDef 是否检查属性已定义 */ -export const setObjByPath = ({ obj, path, val }) => pathStr2Arr(path) - .reduce((acc, cur, idx, { length }) => { - if (idx === length - 1) { +export const setObjByPath = ({ obj, path, val, isCheckDef = false }) => pathStr2Arr(path) + .reduce((acc, cur, idx, arr) => { + // 在调用 setData 时,有的属性可能没定义 + if (isCheckDef && acc[cur] === undefined) { + const parentStr = arr + .slice(0, idx) + .reduce( + (acc, cur) => /\d/.test(cur) + ? `${acc}[${cur}]` + : `${acc}.${cur}`, + 'this' + ) + + error( + `Property "${cur}" is not found in "${parentStr}": ` + + `Make sure that this property has initialized in the data option.` + ) + } + + if (idx === arr.length - 1) { acc[cur] = val return } - // 当前属性在目标对象上并不存在 + // 当前中间属性在目标对象上并不存在 if (!acc[cur]) { acc[cur] = /\d/.test(cur) ? [] : {} } @@ -86,8 +103,9 @@ export const setObjByPath = ({ obj, path, val }) => pathStr2Arr(path) * 因为简单的相等检查,在不同的 vms 或 iframes 中运行时会判断错误 */ export const getType = (fn) => { - const match = fn && - fn.toString().match(/^\s*function (\w+)/) + const match = fn && fn + .toString() + .match(/^\s*function (\w+)/) return match ? match[1] : '' } @@ -125,12 +143,12 @@ export const assertType = (value, type) => { * @param {String} type 输出类型 log|warn|error * @param {any} out 输出的内容 */ -const logByType = (type) => (out) => { +const logByType = (type) => (...out) => { /* istanbul ignore else */ if (process.env.NODE_ENV === 'test') return /* istanbul ignore next */ - console[type](`[TUA-MP]:`, out) + console[type](`[TUA-MP]:`, ...out) } export const log = logByType('log') diff --git a/packages/tua-mp/src/utils/hackSetData.js b/packages/tua-mp/src/utils/hackSetData.js new file mode 100644 index 0000000..8a6f395 --- /dev/null +++ b/packages/tua-mp/src/utils/hackSetData.js @@ -0,0 +1,26 @@ +import { + isFn, + setObjByPath, +} from './basic' + +export const hackSetData = (vm) => { + const originalSetData = vm.setData + + Object.defineProperties(vm, { + 'setData': { + get: () => (newVal, cb) => { + Object.keys(newVal) + .forEach((path) => { + // 针对 vm 上的属性赋值 + setObjByPath({ obj: vm, path, val: newVal[path], isCheckDef: true }) + + // 针对 vm.data 上的属性赋值 + setObjByPath({ obj: vm.data, path, val: newVal[path] }) + }) + + isFn(cb) && Promise.resolve().then(cb) + }, + }, + '__setData__': { get: () => originalSetData }, + }) +} diff --git a/packages/tua-mp/src/utils/index.js b/packages/tua-mp/src/utils/index.js index a466339..c46191a 100644 --- a/packages/tua-mp/src/utils/index.js +++ b/packages/tua-mp/src/utils/index.js @@ -1,3 +1,4 @@ export * from './comp' export * from './basic' export * from './props' +export * from './hackSetData'