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): WIP implement virtual component #7165

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
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 | ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.message || ''?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my fault! I will fix it later. Maybe you can also send a PR to fix it. 😄

}
}
}
</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