Subscribe on changes!

Behavior inconsistent in SFC

avatar
Jan 14th 2022

Version

3.2.26

Reproduction link

sfc.vuejs.org/

Steps to reproduce

Consider the two pieces of code as below:

<script>
  export default {
    data(){
       return {
        msg:"data"
      };
    },
    setup(){
      var obj = {
        msg:"setup"
      };
      return obj
    }
  }
</script>

<template>
  <h1>{{ msg }}</h1>
</template>

and

<script>
  export default {
    data(){
       return {
        msg:"data"
      };
    },
    setup(){
      // The only difference from the first piece of code.
      return {
        msg:"setup"
      }
    }
  }
</script>

<template>
  <h1>{{ msg }}</h1>
</template>

What is expected?

The two pieces of code should behave exactly the same. Both render the msg from setup, which would be "setup".

What is actually happening?

The first code would render "data". The second code would render "setup". It's wired.


Is it a bug or did I miss something? This inconsistent behavior only happens in SFC.

avatar
Jan 14th 2022

That is indeed very weird, but - you should never have keys with the same name between data/setup/computed etc.

so this is a curious side effect that is not relevant when writing correct code.

Edit: the reason for the behaviour lies in the bindings analysis that the compiler does for optimization - you can see this at the top of the compiler output in the JS tab of the playground:

/* Analyzed bindings: {
  "msg": "setup-maybe-ref"
} */

This is an optimization that tries to understand where bindings in the template come from. For bindings it can detect, it creates a "shortcut", which means we don't have to go through the instance proxy's more expensive lookup:

// $setup, $data, .... etc are shortcut to the objects returned by these options.
function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("h1", null, _toDisplayString($setup.msg), 1 /* TEXT */))
}

now, if you do this in setup, you can see that the binding analytics detect "msg" for data, not setup-maybe-ref, because the compiler can't interpret the return value of setup clearly:

/* Analyzed bindings: {
  "msg": "data"
} */

 setup(){
      var obj = {
        msg:"setup"
      };
      return obj
    }

// consequently, now message is read from `$data.msg:

function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("h1", null, _toDisplayString($data.msg), 1 /* TEXT */))
}

I'm inclined to close this as a side-effect of a compiler optimization, a side-effect which only manifests if you have colliding property names, which is not allowed anyway.