cloning vnodes in Vue
by modernweb
HTML
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/lodash"></script>
<div id="app">
<wrapper ref="wrapper">
<p slot="scopeTest" slot-scope="_">{{_.name}}</p>
<test :value="message" @some-event="lognodes"><span class="chidl-of-test">Child of Test</span></test>
<div class="test2" :class="message ? 'dyn' : false" @click="lognodes">Test2</div>
</wrapper>
</div>
Babel + JSX
console.clear()
const DATA_KEYS = [
'class', 'staticClass', 'style',
'attrs', 'props', 'domProps',
'on', 'nativeOn',
'directives', 'scopesSlots',
'slot', 'ref', 'key'
]
let cid = 0
function mutateKey(key) {
return '' + key + `-cloned-cid`
}
function extractData(vnode, isComp) {
const data = _.pick(vnode.data, DATA_KEYS)
if (isComp) {
const cOpts = vnode.componentOptions
_.assign(data, {
props: cOpts.propsData,
on: cOpts.listeners
})
}
if (data.key) {
data.key = mutateKey(data.key)
}
return data
}
function cloneVNode(vnode, newData = {}) {
// use the context that the original vnode was created in.
const h = vnode.context && vnode.context.$createElement
const isComp = !!vnode.componentOptions
const isText = !vnode.tag // this will also match comments but those will be dropped, essentially
const children = isComp
? vnode.componentOptions.children
: vnode.children
if (isText) return vnode.text
const data = extractData(vnode, isComp)
const tag = isComp
? vnode.componentOptions.Ctor
: vnode.tag
const childNodes = children ? children.map(c => cloneVNode(c)) : undefined
return h(tag, data, childNodes)
}
Vue.component('wrapper', {
template: '<div class="wrapper"><slot/></div>',
})
Vue.component('test', {
props: ['value'],
template: `<div class="test">{{value}}</div>`,
})
const app = new Vue({
el: '#app',
data: {
message: 'message'
},
mounted() { this.lognodes() },
updated() { this.lognodes() },
methods: {
lognodes() {
const nodes = this.$refs.wrapper.$slots.default
console.log('originals: ', nodes)
console.log('cloned: ', cloneVNode(nodes[2]))
}
}
})