Skip to content

Commit

Permalink
feat(weex): WIP implement virtual component (#7165)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hanks10100 authored and yyx990803 committed Dec 4, 2017
1 parent 420e134 commit d8aee55
Show file tree
Hide file tree
Showing 14 changed files with 433 additions and 13 deletions.
2 changes: 1 addition & 1 deletion flow/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ declare interface Component {
$createElement: (tag?: string | Component, data?: Object, children?: VNodeChildren) => VNode;

// private properties
_uid: number;
_uid: number | string;
_name: string; // this only exists in dev mode
_isVue: true;
_self: Component;
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.1",
"weex-js-runtime": "^0.23.3",
"weex-styler": "^0.3.0"
},
"config": {
Expand Down
2 changes: 1 addition & 1 deletion src/core/instance/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function initMixin (Vue: Class<Component>) {
}
}

function initInternalComponent (vm: Component, options: InternalComponentOptions) {
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { warn } from 'core/util/debug'
import { handleError } from 'core/util/error'
import { RECYCLE_LIST_MARKER } from 'weex/util/index'
import { createComponentInstanceForVnode } from 'core/vdom/create-component'
import { resolveVirtualComponent } from './virtual-component'

export function isRecyclableComponent (vnode: VNodeWithData): boolean {
return vnode.data.attrs
Expand All @@ -14,6 +15,7 @@ export function isRecyclableComponent (vnode: VNodeWithData): boolean {
export function renderRecyclableComponentTemplate (vnode: MountedComponentVNode): VNode {
// $flow-disable-line
delete vnode.data.attrs[RECYCLE_LIST_MARKER]
resolveVirtualComponent(vnode)
const vm = createComponentInstanceForVnode(vnode)
const render = (vm.$options: any)['@render']
if (render) {
Expand Down
96 changes: 90 additions & 6 deletions src/platforms/weex/runtime/recycle-list/virtual-component.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,90 @@
// import {
// // id, 'lifecycle', hookname, fn
// // https://github.com/Hanks10100/weex-native-directive/tree/master/component
// registerComponentHook,
// updateComponentData
// } from '../util/index'
/* @flow */

// https://github.com/Hanks10100/weex-native-directive/tree/master/component

import { mergeOptions } from 'core/util/index'
import { initProxy } from 'core/instance/proxy'
import { initState } 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 { initInternalComponent, resolveConstructorOptions } from 'core/instance/init'
import { registerComponentHook, updateComponentData } from '../../util/index'

let uid = 0

// override Vue.prototype._init
function initVirtualComponent (options: Object = {}) {
const vm: Component = this
const componentId = options.componentId

// virtual component uid
vm._uid = `virtual-component-${uid++}`

// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}

/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}

vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

registerComponentHook(componentId, 'lifecycle', 'attach', () => {
mountComponent(vm)
})

registerComponentHook(componentId, 'lifecycle', 'detach', () => {
vm.$destroy()
})
}

// override Vue.prototype._update
function updateVirtualComponent (vnode: VNode, hydrating?: boolean) {
// TODO
updateComponentData(this.$options.componentId, {})
}

// listening on native callback
export function resolveVirtualComponent (vnode: MountedComponentVNode): VNode {
const BaseCtor = vnode.componentOptions.Ctor
const VirtualComponent = BaseCtor.extend({})
VirtualComponent.prototype._init = initVirtualComponent
VirtualComponent.prototype._update = updateVirtualComponent

vnode.componentOptions.Ctor = BaseCtor.extend({
beforeCreate () {
registerComponentHook(VirtualComponent.cid, 'lifecycle', 'create', componentId => {
// create virtual component
const options = { componentId }
return new VirtualComponent(options)
})
}
})
}

67 changes: 66 additions & 1 deletion test/weex/cases/cases.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
compileVue,
compileWithDeps,
createInstance,
addTaskHook,
resetTaskHook,
getRoot,
getEvents,
fireEvent
Expand All @@ -19,6 +21,7 @@ function createRenderTestCase (name) {
const instance = createInstance(id, code)
setTimeout(() => {
expect(getRoot(instance)).toEqual(target)
instance.$destroy()
done()
}, 50)
}).catch(done.fail)
Expand All @@ -40,6 +43,7 @@ function createEventTestCase (name) {
fireEvent(instance, event.ref, event.type, {})
setTimeout(() => {
expect(getRoot(instance)).toEqual(after)
instance.$destroy()
done()
}, 50)
}, 50)
Expand Down Expand Up @@ -79,6 +83,7 @@ describe('Usage', () => {
setTimeout(() => {
const target = readObject('recycle-list/components/stateless.vdom.js')
expect(getRoot(instance)).toEqual(target)
instance.$destroy()
done()
}, 50)
}).catch(done.fail)
Expand All @@ -94,29 +99,89 @@ describe('Usage', () => {
setTimeout(() => {
const target = readObject('recycle-list/components/stateless-with-props.vdom.js')
expect(getRoot(instance)).toEqual(target)
instance.$destroy()
done()
}, 50)
}).catch(done.fail)
})

it('multi stateless components', done => {
compileWithDeps('recycle-list/components/stateless-multi-components.vue', [{
name: 'banner',
path: 'recycle-list/components/banner.vue'
}, {
name: 'poster',
path: 'recycle-list/components/poster.vue'
}, {
name: 'footer',
path: 'recycle-list/components/footer.vue'
}]).then(code => {
const id = String(Date.now() * Math.random())
const instance = createInstance(id, code)
setTimeout(() => {
const target = readObject('recycle-list/components/stateless-multi-components.vdom.js')
expect(getRoot(instance)).toEqual(target)
instance.$destroy()
done()
}, 50)
}).catch(done.fail)
})

it('stateful component', done => {
const tasks = []
addTaskHook((_, task) => tasks.push(task))
compileWithDeps('recycle-list/components/stateful.vue', [{
name: 'counter',
path: 'recycle-list/components/counter.vue'
}]).then(code => {
const id = String(Date.now() * Math.random())
const instance = createInstance(id, code)
expect(tasks.length).toEqual(7)
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(() => {
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, {})
setTimeout(() => {
expect(getRoot(instance)).toEqual(target)
// expect(tasks.length).toEqual(1)
// expect(tasks[0]).toEqual({
// module: 'dom',
// method: 'updateComponentData',
// args: [{ count: 43 }]
// })
instance.$destroy()
resetTaskHook()
done()
})
}, 50)
}).catch(done.fail)
})

it('stateful component with v-model', done => {
compileWithDeps('recycle-list/components/stateful-v-model.vue', [{
name: 'editor',
path: 'recycle-list/components/editor.vue'
}]).then(code => {
const id = String(Date.now() * Math.random())
const instance = createInstance(id, code)
setTimeout(() => {
const target = readObject('recycle-list/components/stateful-v-model.vdom.js')
expect(getRoot(instance)).toEqual(target)
instance.$destroy()
done()
}, 50)
}).catch(done.fail)
})
})
})
31 changes: 31 additions & 0 deletions test/weex/cases/recycle-list/components/editor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template recyclable="true">
<div>
<text class="output">{{output}}</text>
<input class="input" type="text" v-model="output" />
</div>
</template>

<script>
module.exports = {
props: ['message'],
data () {
return {
output: this.message | ''
}
}
}
</script>

<style scoped>
.output {
height: 80px;
font-size: 60px;
color: #41B883;
}
.input {
font-size: 50px;
color: #666666;
border-width: 2px;
border-color: #41B883;
}
</style>
18 changes: 18 additions & 0 deletions test/weex/cases/recycle-list/components/footer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template recyclable="true">
<div class="footer">
<text class="copyright">All rights reserved.</text>
</div>
</template>

<style scoped>
.footer {
height: 80px;
justify-content: center;
background-color: #EEEEEE;
}
.copyright {
color: #AAAAAA;
font-size: 32px;
text-align: center;
}
</style>
48 changes: 48 additions & 0 deletions test/weex/cases/recycle-list/components/stateful-v-model.vdom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'div',
attr: {
'@isComponentRoot': true,
'@componentProps': {
message: 'No binding'
}
},
children: [{
type: 'text',
style: {
height: '80px',
fontSize: '60px',
color: '#41B883'
},
attr: {
value: { '@binding': 'output' }
}
}, {
type: 'input',
event: ['input'],
style: {
fontSize: '50px',
color: '#666666',
borderWidth: '2px',
borderColor: '#41B883'
},
attr: {
type: 'text',
value: 0
}
}]
}]
}]
})
21 changes: 21 additions & 0 deletions test/weex/cases/recycle-list/components/stateful-v-model.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="A">
<editor message="No binding"></editor>
</cell-slot>
</recycle-list>
</template>

<script>
// require('./editor.vue')
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
}
}
</script>
Loading

0 comments on commit d8aee55

Please sign in to comment.