Skip to content

Commit ea0d227

Browse files
committedOct 11, 2017
feat: functional component support for compiled templates
1 parent 68bdbf5 commit ea0d227

File tree

5 files changed

+162
-53
lines changed

5 files changed

+162
-53
lines changed
 
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* @flow */
2+
3+
import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'
4+
import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'
5+
import { renderList } from './render-list'
6+
import { renderSlot } from './render-slot'
7+
import { resolveFilter } from './resolve-filter'
8+
import { checkKeyCodes } from './check-keycodes'
9+
import { bindObjectProps } from './bind-object-props'
10+
import { renderStatic, markOnce } from './render-static'
11+
import { bindObjectListeners } from './bind-object-listeners'
12+
import { resolveScopedSlots } from './resolve-slots'
13+
14+
export function installRenderHelpers (target: any) {
15+
target._o = markOnce
16+
target._n = toNumber
17+
target._s = toString
18+
target._l = renderList
19+
target._t = renderSlot
20+
target._q = looseEqual
21+
target._i = looseIndexOf
22+
target._m = renderStatic
23+
target._f = resolveFilter
24+
target._k = checkKeyCodes
25+
target._b = bindObjectProps
26+
target._v = createTextVNode
27+
target._e = createEmptyVNode
28+
target._u = resolveScopedSlots
29+
target._g = bindObjectListeners
30+
}

‎src/core/instance/render-helpers/render-static.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ export function renderStatic (
99
index: number,
1010
isInFor?: boolean
1111
): VNode | Array<VNode> {
12-
let tree = this._staticTrees[index]
12+
// static trees can be rendered once and cached on the contructor options
13+
// so every instance shares the same trees
14+
let options = this.constructor.options
15+
if (this.$options.staticRenderFns !== options.staticRenderFns) {
16+
options = this.$options
17+
}
18+
const trees = options._staticTrees || (options._staticTrees = [])
19+
let tree = trees[index]
1320
// if has already-rendered static tree and not inside v-for,
1421
// we can reuse the same tree by doing a shallow clone.
1522
if (tree && !isInFor) {
@@ -18,8 +25,8 @@ export function renderStatic (
1825
: cloneVNode(tree)
1926
}
2027
// otherwise, render a fresh tree.
21-
tree = this._staticTrees[index] =
22-
this.$options.staticRenderFns[index].call(this._renderProxy)
28+
tree = trees[index] =
29+
options.staticRenderFns[index].call(this._renderProxy, null, this)
2330
markStatic(tree, `__static__${index}`, false)
2431
return tree
2532
}

‎src/core/instance/render.js

+7-38
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,18 @@
33
import {
44
warn,
55
nextTick,
6-
toNumber,
7-
toString,
8-
looseEqual,
96
emptyObject,
107
handleError,
11-
looseIndexOf,
128
defineReactive
139
} from '../util/index'
1410

15-
import VNode, {
16-
cloneVNodes,
17-
createTextVNode,
18-
createEmptyVNode
19-
} from '../vdom/vnode'
11+
import { createElement } from '../vdom/create-element'
12+
import { installRenderHelpers } from './render-helpers/index'
13+
import { resolveSlots } from './render-helpers/resolve-slots'
14+
import VNode, { cloneVNodes, createEmptyVNode } from '../vdom/vnode'
2015

2116
import { isUpdatingChildComponent } from './lifecycle'
2217

23-
import { createElement } from '../vdom/create-element'
24-
import { renderList } from './render-helpers/render-list'
25-
import { renderSlot } from './render-helpers/render-slot'
26-
import { resolveFilter } from './render-helpers/resolve-filter'
27-
import { checkKeyCodes } from './render-helpers/check-keycodes'
28-
import { bindObjectProps } from './render-helpers/bind-object-props'
29-
import { renderStatic, markOnce } from './render-helpers/render-static'
30-
import { bindObjectListeners } from './render-helpers/bind-object-listeners'
31-
import { resolveSlots, resolveScopedSlots } from './render-helpers/resolve-slots'
32-
3318
export function initRender (vm: Component) {
3419
vm._vnode = null // the root of the child tree
3520
vm._staticTrees = null
@@ -65,6 +50,9 @@ export function initRender (vm: Component) {
6550
}
6651

6752
export function renderMixin (Vue: Class<Component>) {
53+
// install runtime convenience helpers
54+
installRenderHelpers(Vue.prototype)
55+
6856
Vue.prototype.$nextTick = function (fn: Function) {
6957
return nextTick(fn, this)
7058
}
@@ -135,23 +123,4 @@ export function renderMixin (Vue: Class<Component>) {
135123
vnode.parent = _parentVnode
136124
return vnode
137125
}
138-
139-
// internal render helpers.
140-
// these are exposed on the instance prototype to reduce generated render
141-
// code size.
142-
Vue.prototype._o = markOnce
143-
Vue.prototype._n = toNumber
144-
Vue.prototype._s = toString
145-
Vue.prototype._l = renderList
146-
Vue.prototype._t = renderSlot
147-
Vue.prototype._q = looseEqual
148-
Vue.prototype._i = looseIndexOf
149-
Vue.prototype._m = renderStatic
150-
Vue.prototype._f = resolveFilter
151-
Vue.prototype._k = checkKeyCodes
152-
Vue.prototype._b = bindObjectProps
153-
Vue.prototype._v = createTextVNode
154-
Vue.prototype._e = createEmptyVNode
155-
Vue.prototype._u = resolveScopedSlots
156-
Vue.prototype._g = bindObjectListeners
157126
}

‎src/core/vdom/create-functional-component.js

+40-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import VNode from './vnode'
44
import { createElement } from './create-element'
55
import { resolveInject } from '../instance/inject'
66
import { resolveSlots } from '../instance/render-helpers/resolve-slots'
7+
import { installRenderHelpers } from '../instance/render-helpers/index'
78

89
import {
910
isDef,
@@ -12,15 +13,43 @@ import {
1213
validateProp
1314
} from '../util/index'
1415

16+
function FunctionalRenderContext (
17+
data,
18+
props,
19+
children,
20+
parent,
21+
Ctor
22+
) {
23+
const options = Ctor.options
24+
this.data = data
25+
this.props = props
26+
this.children = children
27+
this.parent = parent
28+
this.listeners = data.on || emptyObject
29+
this.injections = resolveInject(options.inject, parent)
30+
this.slots = () => resolveSlots(children, parent)
31+
// support for compiled functional template
32+
if (options._compiled) {
33+
this.constructor = Ctor
34+
this.$options = options
35+
this._c = parent._c
36+
this.$slots = this.slots()
37+
this.$scopedSlots = data.scopedSlots || emptyObject
38+
}
39+
}
40+
41+
installRenderHelpers(FunctionalRenderContext.prototype)
42+
1543
export function createFunctionalComponent (
1644
Ctor: Class<Component>,
1745
propsData: ?Object,
1846
data: VNodeData,
19-
context: Component,
47+
contextVm: Component,
2048
children: ?Array<VNode>
2149
): VNode | void {
50+
const options = Ctor.options
2251
const props = {}
23-
const propOptions = Ctor.options.props
52+
const propOptions = options.props
2453
if (isDef(propOptions)) {
2554
for (const key in propOptions) {
2655
props[key] = validateProp(key, propOptions, propsData || emptyObject)
@@ -31,20 +60,19 @@ export function createFunctionalComponent (
3160
}
3261
// ensure the createElement function in functional components
3362
// gets a unique context - this is necessary for correct named slot check
34-
const _context = Object.create(context)
35-
const h = (a, b, c, d) => createElement(_context, a, b, c, d, true)
36-
const vnode = Ctor.options.render.call(null, h, {
63+
const _contextVm = Object.create(contextVm)
64+
const h = (a, b, c, d) => createElement(_contextVm, a, b, c, d, true)
65+
const renderContext = new FunctionalRenderContext(
3766
data,
3867
props,
3968
children,
40-
parent: context,
41-
listeners: data.on || emptyObject,
42-
injections: resolveInject(Ctor.options.inject, context),
43-
slots: () => resolveSlots(children, context)
44-
})
69+
contextVm,
70+
Ctor
71+
)
72+
const vnode = options.render.call(null, h, renderContext)
4573
if (vnode instanceof VNode) {
46-
vnode.functionalContext = context
47-
vnode.functionalOptions = Ctor.options
74+
vnode.functionalContext = contextVm
75+
vnode.functionalOptions = options
4876
if (data.slot) {
4977
(vnode.data || (vnode.data = {})).slot = data.slot
5078
}

‎test/unit/features/options/functional.spec.js

+75
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,79 @@ describe('Options functional', () => {
185185
const vnode = h('child')
186186
expect(vnode).toEqual(createEmptyVNode())
187187
})
188+
189+
it('should work with render fns compiled from template', done => {
190+
// code generated via vue-template-es2015-compiler
191+
var render = function (_h, _vm) {
192+
var _c = _vm._c
193+
return _c(
194+
'div',
195+
[
196+
_c('h2', { staticClass: 'red' }, [_vm._v(_vm._s(_vm.props.msg))]),
197+
_vm._t('default'),
198+
_vm._t('slot2'),
199+
_vm._t('scoped', null, { msg: _vm.props.msg }),
200+
_vm._m(0),
201+
_c('div', { staticClass: 'clickable', on: { click: _vm.parent.fn }}, [
202+
_vm._v('click me')
203+
])
204+
],
205+
2
206+
)
207+
}
208+
var staticRenderFns = [
209+
function (_h, _vm) {
210+
var _c = _vm._c
211+
return _c('div', [_vm._v('Some '), _c('span', [_vm._v('text')])])
212+
}
213+
]
214+
215+
const child = {
216+
functional: true,
217+
_compiled: true,
218+
render,
219+
staticRenderFns
220+
}
221+
222+
const parent = new Vue({
223+
components: {
224+
child
225+
},
226+
data: {
227+
msg: 'hello'
228+
},
229+
template: `
230+
<div>
231+
<child :msg="msg">
232+
<span>{{ msg }}</span>
233+
<div slot="slot2">Second slot</div>
234+
<template slot="scoped" slot-scope="scope">{{ scope.msg }}</template>
235+
</child>
236+
</div>
237+
`,
238+
methods: {
239+
fn () {
240+
this.msg = 'bye'
241+
}
242+
}
243+
}).$mount()
244+
245+
function assertMarkup () {
246+
expect(parent.$el.innerHTML).toBe(
247+
`<div>` +
248+
`<h2 class="red">${parent.msg}</h2>` +
249+
`<span>${parent.msg}</span> ` +
250+
`<div>Second slot</div>` +
251+
parent.msg +
252+
// static
253+
`<div>Some <span>text</span></div>` +
254+
`<div class="clickable">click me</div>` +
255+
`</div>`
256+
)
257+
}
258+
259+
assertMarkup()
260+
triggerEvent(parent.$el.querySelector('.clickable'), 'click')
261+
waitForUpdate(assertMarkup).then(done)
262+
})
188263
})

1 commit comments

Comments
 (1)

eshell commented on Oct 11, 2017

@eshell

Lol. The second you posted this, I just converted my dumb components to jsx based functional components ;)

Please sign in to comment.