diff --git a/flow/component.js b/flow/component.js index bf984cd6dc..b873bc2450 100644 --- a/flow/component.js +++ b/flow/component.js @@ -70,6 +70,7 @@ declare interface Component { _hasHookEvent: boolean; _provided: ?Object; _inlineComputed: ?{ [key: string]: Watcher }; // inline computed watchers for literal props + // _virtualComponents?: { [key: string]: Component }; // private methods diff --git a/flow/options.js b/flow/options.js index 3ae6509df6..e4c6ed1f96 100644 --- a/flow/options.js +++ b/flow/options.js @@ -11,6 +11,8 @@ declare type InternalComponentOptions = { type InjectKey = string | Symbol; declare type ComponentOptions = { + componentId?: string; + // data data: Object | Function | void; props?: { [key: string]: PropOptions }; diff --git a/package-lock.json b/package-lock.json index 1135b8d669..0f3b963dd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9796,9 +9796,9 @@ } }, "weex-js-runtime": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/weex-js-runtime/-/weex-js-runtime-0.23.4.tgz", - "integrity": "sha512-leBzBrpvbrKHvmwd00YjzxZAQrp6NWEbEt3jAtk5EINccxZzFhwZ8SpUmn0d5bspemFisT18eJDAavv4LgRxpw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/weex-js-runtime/-/weex-js-runtime-0.23.5.tgz", + "integrity": "sha512-94/bMUpCyZMsrq2codDPFatr5Ec8yKYKYNsfoshQOiQKTZY3pwqlfedtOKQNf6k7o4npEhdxDnHJwEVORtNylg==", "dev": true }, "weex-styler": { diff --git a/package.json b/package.json index fe58ff2634..ecbb0fd9d3 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "typescript": "^2.6.1", "uglify-js": "^3.0.15", "webpack": "^3.10.0", - "weex-js-runtime": "^0.23.3", + "weex-js-runtime": "^0.23.5", "weex-styler": "^0.3.0" }, "config": { diff --git a/src/core/instance/state.js b/src/core/instance/state.js index e39fb2e0a5..01e1e8720d 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -150,7 +150,7 @@ function initData (vm: Component) { observe(data, true /* asRootData */) } -function getData (data: Function, vm: Component): any { +export function getData (data: Function, vm: Component): any { try { return data.call(vm, vm) } catch (e) { diff --git a/src/platforms/weex/runtime/recycle-list/virtual-component.js b/src/platforms/weex/runtime/recycle-list/virtual-component.js index ed370d8c9f..e5498d2689 100644 --- a/src/platforms/weex/runtime/recycle-list/virtual-component.js +++ b/src/platforms/weex/runtime/recycle-list/virtual-component.js @@ -2,13 +2,14 @@ // https://github.com/Hanks10100/weex-native-directive/tree/master/component -import { mergeOptions } from 'core/util/index' +import { mergeOptions, isPlainObject, noop } from 'core/util/index' +import Watcher from 'core/observer/watcher' import { initProxy } from 'core/instance/proxy' -import { initState } from 'core/instance/state' +import { initState, getData } from 'core/instance/state' import { initRender } from 'core/instance/render' import { initEvents } from 'core/instance/events' import { initProvide, initInjections } from 'core/instance/inject' -import { initLifecycle, mountComponent, callHook } from 'core/instance/lifecycle' +import { initLifecycle, callHook } from 'core/instance/lifecycle' import { initInternalComponent, resolveConstructorOptions } from 'core/instance/init' import { registerComponentHook, updateComponentData } from '../../util/index' @@ -55,8 +56,25 @@ function initVirtualComponent (options: Object = {}) { initProvide(vm) // resolve provide after data/props callHook(vm, 'created') + // send initial data to native + const data = vm.$options.data + const params = typeof data === 'function' + ? getData(data, vm) + : data || {} + if (isPlainObject(params)) { + updateComponentData(componentId, params) + } + registerComponentHook(componentId, 'lifecycle', 'attach', () => { - mountComponent(vm) + callHook(vm, 'beforeMount') + + const updateComponent = () => { + vm._update(vm._vnode, false) + } + new Watcher(vm, updateComponent, noop, null, true) + + vm._isMounted = true + callHook(vm, 'mounted') }) registerComponentHook(componentId, 'lifecycle', 'detach', () => { @@ -65,25 +83,53 @@ function initVirtualComponent (options: Object = {}) { } // override Vue.prototype._update -function updateVirtualComponent (vnode: VNode, hydrating?: boolean) { - // TODO - updateComponentData(this.$options.componentId, {}) +function updateVirtualComponent (vnode?: VNode) { + const vm: Component = this + const componentId = vm.$options.componentId + if (vm._isMounted) { + callHook(vm, 'beforeUpdate') + } + vm._vnode = vnode + if (vm._isMounted && componentId) { + // TODO: data should be filtered and without bindings + const data = Object.assign({}, vm._data) + updateComponentData(componentId, data, () => { + callHook(vm, 'updated') + }) + } } // listening on native callback export function resolveVirtualComponent (vnode: MountedComponentVNode): VNode { const BaseCtor = vnode.componentOptions.Ctor const VirtualComponent = BaseCtor.extend({}) + const cid = VirtualComponent.cid VirtualComponent.prototype._init = initVirtualComponent VirtualComponent.prototype._update = updateVirtualComponent vnode.componentOptions.Ctor = BaseCtor.extend({ beforeCreate () { - registerComponentHook(VirtualComponent.cid, 'lifecycle', 'create', componentId => { + // const vm: Component = this + + // TODO: listen on all events and dispatch them to the + // corresponding virtual components according to the componentId. + // vm._virtualComponents = {} + const createVirtualComponent = (componentId, propsData) => { // create virtual component - const options = { componentId } - return new VirtualComponent(options) - }) + // const subVm = + new VirtualComponent({ + componentId, + propsData + }) + // if (vm._virtualComponents) { + // vm._virtualComponents[componentId] = subVm + // } + } + + registerComponentHook(cid, 'lifecycle', 'create', createVirtualComponent) + }, + beforeDestroy () { + delete this._virtualComponents } }) } diff --git a/src/platforms/weex/util/index.js b/src/platforms/weex/util/index.js index cffc2ede79..77bc318c55 100755 --- a/src/platforms/weex/util/index.js +++ b/src/platforms/weex/util/index.js @@ -70,13 +70,17 @@ export function registerComponentHook ( } // Updates the state of the component to weex native render engine. -export function updateComponentData (componentId: string, newData: Object) { +export function updateComponentData ( + componentId: string, + newData: Object | void, + callback?: Function +) { if (!document || !document.taskCenter) { warn(`Can't find available "document" or "taskCenter".`) return } if (typeof document.taskCenter.updateData === 'function') { - return document.taskCenter.updateData(componentId, newData) + return document.taskCenter.updateData(componentId, newData, callback) } warn(`Failed to update component data (${componentId}).`) } diff --git a/test/weex/cases/cases.spec.js b/test/weex/cases/cases.spec.js index 532544cdd1..e5825ec743 100644 --- a/test/weex/cases/cases.spec.js +++ b/test/weex/cases/cases.spec.js @@ -137,29 +137,45 @@ describe('Usage', () => { const id = String(Date.now() * Math.random()) const instance = createInstance(id, code) expect(tasks.length).toEqual(3) - tasks.length = 0 - instance.$triggerHook(2, 'create', ['component-1']) - instance.$triggerHook(2, 'create', ['component-2']) - instance.$triggerHook('component-1', 'attach') - instance.$triggerHook('component-2', 'attach') - expect(tasks.length).toEqual(2) - expect(tasks[0].method).toEqual('updateComponentData') - // expect(tasks[0].args).toEqual([{ count: 42 }]) - expect(tasks[1].method).toEqual('updateComponentData') - // expect(tasks[1].args).toEqual([{ count: 42 }]) setTimeout(() => { + // check the render results const target = readObject('recycle-list/components/stateful.vdom.js') expect(getRoot(instance)).toEqual(target) - const event = getEvents(instance)[0] tasks.length = 0 - fireEvent(instance, event.ref, event.type, {}) + + // trigger component hooks + instance.$triggerHook( + 2, // cid of the virtual component template + 'create', // lifecycle hook name + + // arguments for the callback + [ + 'x-1', // componentId of the virtual component + { start: 3 } // propsData of the virtual component + ] + ) + instance.$triggerHook(2, 'create', ['x-2', { start: 11 }]) + + // the state (_data) of the virtual component should be sent to native + expect(tasks.length).toEqual(2) + expect(tasks[0].method).toEqual('updateComponentData') + expect(tasks[0].args).toEqual(['x-1', { count: 6 }, '']) + expect(tasks[1].method).toEqual('updateComponentData') + expect(tasks[1].args).toEqual(['x-2', { count: 22 }, '']) + + instance.$triggerHook('x-1', 'attach') + instance.$triggerHook('x-2', 'attach') + tasks.length = 0 + + // simulate a click event + // the event will be caught by the virtual component template and + // should be dispatched to virtual component according to the componentId + const event = getEvents(instance)[0] + fireEvent(instance, event.ref, 'click', { componentId: 'x-1' }) setTimeout(() => { // expect(tasks.length).toEqual(1) - // expect(tasks[0]).toEqual({ - // module: 'dom', - // method: 'updateComponentData', - // args: [{ count: 43 }] - // }) + // expect(tasks[0].method).toEqual('updateComponentData') + // expect(tasks[0].args).toEqual([{ count: 7 }]) instance.$destroy() resetTaskHook() done() @@ -168,6 +184,39 @@ describe('Usage', () => { }).catch(done.fail) }) + // it('component lifecycle', done => { + // global.__lifecycles = [] + // compileWithDeps('recycle-list/components/stateful-lifecycle.vue', [{ + // name: 'lifecycle', + // path: 'recycle-list/components/lifecycle.vue' + // }]).then(code => { + // const id = String(Date.now() * Math.random()) + // const instance = createInstance(id, code) + // setTimeout(() => { + // const target = readObject('recycle-list/components/stateful-lifecycle.vdom.js') + // expect(getRoot(instance)).toEqual(target) + + // instance.$triggerHook(2, 'create', ['y-1']) + // instance.$triggerHook('y-1', 'attach') + // instance.$triggerHook('y-1', 'detach') + // expect(global.__lifecycles).toEqual([ + // 'beforeCreate undefined', + // 'created 0', + // 'beforeMount 1', + // 'mounted 1', + // 'beforeUpdate 2', + // 'updated 2', + // 'beforeDestroy 2', + // 'destroyed 2' + // ]) + + // delete global.__lifecycles + // instance.$destroy() + // done() + // }, 50) + // }).catch(done.fail) + // }) + it('stateful component with v-model', done => { compileWithDeps('recycle-list/components/stateful-v-model.vue', [{ name: 'editor', diff --git a/test/weex/cases/recycle-list/components/counter.vue b/test/weex/cases/recycle-list/components/counter.vue index c0298dee6e..79b8189b03 100644 --- a/test/weex/cases/recycle-list/components/counter.vue +++ b/test/weex/cases/recycle-list/components/counter.vue @@ -10,7 +10,7 @@ props: ['start'], data () { return { - count: parseInt(this.start, 10) || 42 + count: parseInt(this.start, 10) * 2 || 42 } }, methods: { diff --git a/test/weex/cases/recycle-list/components/lifecycle.vue b/test/weex/cases/recycle-list/components/lifecycle.vue new file mode 100644 index 0000000000..03ee65ada5 --- /dev/null +++ b/test/weex/cases/recycle-list/components/lifecycle.vue @@ -0,0 +1,39 @@ + + + diff --git a/test/weex/cases/recycle-list/components/stateful-lifecycle.vdom.js b/test/weex/cases/recycle-list/components/stateful-lifecycle.vdom.js new file mode 100644 index 0000000000..5264259f14 --- /dev/null +++ b/test/weex/cases/recycle-list/components/stateful-lifecycle.vdom.js @@ -0,0 +1,29 @@ +({ + type: 'recycle-list', + attr: { + append: 'tree', + listData: [ + { type: 'X' }, + { type: 'X' } + ], + templateKey: 'type', + alias: 'item' + }, + children: [{ + type: 'cell-slot', + attr: { append: 'tree', templateType: 'X' }, + children: [{ + type: 'div', + attr: { + '@isComponentRoot': true, + '@componentProps': {} + }, + children: [{ + type: 'text', + attr: { + value: { '@binding': 'number' } + } + }] + }] + }] +}) diff --git a/test/weex/cases/recycle-list/components/stateful-lifecycle.vue b/test/weex/cases/recycle-list/components/stateful-lifecycle.vue new file mode 100644 index 0000000000..c01a96b6d1 --- /dev/null +++ b/test/weex/cases/recycle-list/components/stateful-lifecycle.vue @@ -0,0 +1,21 @@ + + + diff --git a/test/weex/runtime/framework.spec.js b/test/weex/runtime/framework.spec.js index 449d877260..5a8daed461 100644 --- a/test/weex/runtime/framework.spec.js +++ b/test/weex/runtime/framework.spec.js @@ -36,7 +36,7 @@ describe('framework APIs', () => { type: 'div', children: [{ type: 'text', - attr: { value: '{"bundleUrl":"http://example.com/","a":1,"b":2,"env":{}}' } + attr: { value: '{"bundleUrl":"http://example.com/","a":1,"b":2,"env":{},"bundleType":"Vue"}' } }] }) }) @@ -170,6 +170,7 @@ describe('framework APIs', () => { `, { bundleUrl: 'http://whatever.com/x.js' }) expect(JSON.parse(getRoot(instance).children[0].attr.value)).toEqual({ bundleUrl: 'http://whatever.com/x.js', + bundleType: 'Vue', env: { weexVersion: '0.10.0', platform: 'Node.js'