Skip to content

Commit

Permalink
feat(weex): partially support lifecycles of virtual component (#7242)
Browse files Browse the repository at this point in the history
Update the `_init` and `_update` logic to partially support lifecycles.
Add test cases for testing the lifecycle hooks and data update.
  • Loading branch information
Hanks10100 authored and yyx990803 committed Dec 19, 2017
1 parent d544d05 commit 661bfe5
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 37 deletions.
1 change: 1 addition & 0 deletions flow/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions flow/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion src/core/instance/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
68 changes: 57 additions & 11 deletions src/platforms/weex/runtime/recycle-list/virtual-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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', () => {
Expand All @@ -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
}
})
}
Expand Down
8 changes: 6 additions & 2 deletions src/platforms/weex/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}).`)
}
83 changes: 66 additions & 17 deletions test/weex/cases/cases.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion test/weex/cases/recycle-list/components/counter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
props: ['start'],
data () {
return {
count: parseInt(this.start, 10) || 42
count: parseInt(this.start, 10) * 2 || 42
}
},
methods: {
Expand Down
39 changes: 39 additions & 0 deletions test/weex/cases/recycle-list/components/lifecycle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template recyclable="true">
<div>
<text>{{number}}</text>
</div>
</template>

<script>
module.exports = {
data () {
return { number: 0 }
},
beforeCreate () {
try { __lifecycles.push('beforeCreate ' + this.number) } catch (e) {}
},
created () {
try { __lifecycles.push('created ' + this.number) } catch (e) {}
this.number++
},
beforeMount () {
try { __lifecycles.push('beforeMount ' + this.number) } catch (e) {}
},
mounted () {
try { __lifecycles.push('mounted ' + this.number) } catch (e) {}
this.number++
},
beforeUpdate () {
try { __lifecycles.push('beforeUpdate ' + this.number) } catch (e) {}
},
updated () {
try { __lifecycles.push('updated ' + this.number) } catch (e) {}
},
beforeDestroy () {
try { __lifecycles.push('beforeDestroy ' + this.number) } catch (e) {}
},
destroyed () {
try { __lifecycles.push('destroyed ' + this.number) } catch (e) {}
}
}
</script>
29 changes: 29 additions & 0 deletions test/weex/cases/recycle-list/components/stateful-lifecycle.vdom.js
Original file line number Diff line number Diff line change
@@ -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' }
}
}]
}]
}]
})
21 changes: 21 additions & 0 deletions test/weex/cases/recycle-list/components/stateful-lifecycle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="X">
<lifecycle></lifecycle>
</cell-slot>
</recycle-list>
</template>

<script>
// require('./lifecycle.vue')
module.exports = {
data () {
return {
longList: [
{ type: 'X' },
{ type: 'X' }
]
}
}
}
</script>
3 changes: 2 additions & 1 deletion test/weex/runtime/framework.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"}' }
}]
})
})
Expand Down Expand Up @@ -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'
Expand Down

0 comments on commit 661bfe5

Please sign in to comment.