Search code examples
javascripttypescriptvue.jsvue-devtools

Accessing top-level Vue data in nested getter/setter


<template>
  <div id="app">
    {{ foo.bar }}
    <button @click="meaning++">click</button> <!--not reactive-->
    <button @click="foo.bar++">click2</button>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class App extends Vue {
  private meaning = 42;

  private foo = {
    that:this,
    get bar() {
      return this.that.meaning;
    },
    set bar(value) {
      this.that.meaning = value;
    },
  };

  created() {
    console.log(this); // VueComponent {}
    console.log(this.foo.that); // App {}
    console.log(this === this.foo.that); // false
  }
}
</script>

I want foo.bar to be backed by meaning which is a top level data field. I start with the hack above to save this (the Vue option instance) in that, but I'm also aware that at run time, this become the VueComponent instance in component methods, thus this.meaning is not this.foo.that.meaning.

An extra problem is that the code snippet above will break vue-devtools inside Chrome. vue-devtools will try invoke Object.keys() on instance._data which is null.

What's the correct way to make foo.bar be backed by meaning? I might be having arbitrarily complicated logic in those getter and setter.


Solution

  • Edit:

    Since this to the VueComponent can only be resolved at runtime, I came to this kinda hackish workaround:

    <template>
      <div id="app">
        {{ foo.bar }} 
        <button @click="meaning++">click</button>  <!-- reactive now! -->
        <button @click="foo.bar++">click2</button>
      </div>
    </template>
    
    <script lang="ts">
    import { Component, Vue } from "vue-property-decorator";
    
    @Component
    export default class App extends Vue {
      private meaning = 42;
    
      private foo = {
        that: {meaning:42} as any, // put exactly the same literal initail value here
        get bar() {
            return this.that.meaning;
        },
        set bar(value) {
          this.that.meaning = value;
        },
      };
    
      created() {
        this.foo.that = this;
        console.log(this); // VueComponent
        console.log(this.foo.that); // VueComponent
        console.log(this === this.foo.that); // true
      }
    }
    </script>
    

    Original answer:

    For anyone face the same problem, I came to the conclusion that

    it's impossible to refer to outer-level data in inner-level object data in the Vue option instance.

    Because the VueComponent instance can only be grabbed at run time, but you have to bind that:this in the constructor of the Vue config class, which is prior to the instantiation of VueComponent. Thus the reactive system of Vue won't have any chance to correct the reference in that.

    So the answer (for me) is simply don't do this. Dependent must always be higher than (or on the equal level of) the dependency, and you can't have foo as bar's namespace.

    Elevate bar to the top level.