Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(weex): partially support lifecycles of virtual component #7242

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions flow/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ declare interface Component {
_staticTrees: ?Array<VNode>; // v-once cached trees
_hasHookEvent: boolean;
_provided: ?Object;
// _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": "^2.6.1",
"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 @@ -149,7 +149,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