Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is Vue.js component's vm.$el constant or can it be reassigned?

Vue.js component instances have a vm.$em property that gives access to a component's root DOM element. Does the value of this property ever change during the lifecycle of a component or is it constant after the component is mounted? In other words, once a component's root DOM element is created, can it be replaced by Vue under some condition?

I know the contents of the DOM element can of course change.

According to a component's lifecycle diagram below, when data changes there is a "Virtual DOM re-render and patch." I read the doc and tried to find the answer in Vue's source, but I haven't found any conclusive answer so far.

The closest I've been with the source code is this in src/core/instance/lifecycle.js:

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  const restoreActiveInstance = setActiveInstance(vm)
  vm._vnode = vnode
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  restoreActiveInstance()
  // update __vue__ reference
  if (prevEl) {
    prevEl.__vue__ = null
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm
  }
  // etc.
}

This suggests that vm.$el could be reassigned on every component render, but I haven't deciphered how __patch__() works so far to be sure and when _update() is called exactly.

Why bother?

I have some setup task to do on the root DOM element directly (e.g. setup a jQuery plugin). Is it enough to do this only in the mounted hook (yes if vm.$el is constant) or must I also do it in the updated hook when a change of vm.$el is detected?

Vue.js lifecycle diagram

like image 337
bernie Avatar asked Nov 06 '22 20:11

bernie


1 Answers

If you are in complete control of the component content then you are probably save to assume that vm.$el won't change. If you are making (for example) a mixin or directive then you can't - it is perfectly possible to make a component where the top level element can change by using the is directive.

For example, this defines a component which can change it's top level element type:

Vue.component('comp',{
  props:{type:String},
  template:`
    <component :is="type">
       I am a {{type}}
    </component>
  `
})

This is a fiddle which demonstrates it: https://jsfiddle.net/tazfLqbh/1/

As good practice I would suggest assuming that it could change, as the behaviour does not seem to be defined formally in the documentation so behaviour can change with future versions.

Analysis of the fiddle

The question remains whether the dynamic component in the fiddle triggers the creation of a new Vue component when its type is toggled. In the dev console:

// "Inspect" the span
> a = $0.__vue__
VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
// Toggle the type to div and "inspect" the div
> b = $0.__vue__
VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
> a === b
true

So no new Vue component instance is created on toggle of the root element type.

As expected, this fiddle shows that a dynamic component type does however trigger the creation of a new Vue component instance. In that case, it thus appears the mounted hook is enough. Something like this:

<div id="app">
  <button @click="toggle">
    Toggle div or span element
  </button>
  <component :is="span?'comp-span':'comp-div'"></component>
</div>

Vue.component('comp-span',{
  template:`
    <span>
       I am a span
    </span>
  `
})

Vue.component('comp-div',{
  template:`
    <div>
       I am a div
    </div>
  `
})
like image 170
Euan Smith Avatar answered Nov 14 '22 23:11

Euan Smith