Search code examples
javascriptvue.jsvuejs3vue-composition-apivue-script-setup

How does Computed work in Vue 3 script setup?


I'm trying to get computed to work in <script setup>:

<template>
  <div>
    <p>{{ whatever.get() }}</p>
  </div>
</template>


<script setup>
import {computed} from "vue";

const whatever = computed({
    get() {
        console.log('check')
        return 'check?';
    }
})
</script>

The console.log() comes through but the return statement seems to throw an error:

check
Vue warn]: Unhandled error during execution of render function 
  at <Cms>
Uncaught TypeError: $setup.whatever.get is not a function
at Proxy.render (app.js?id=d9e007128724c77a8d69ec76c6c081a0:39550:266)
at renderComponentRoot (app.js?id=d9e007128724c77a8d69ec76c6c081a0:25902:44)
at ReactiveEffect.componentUpdateFn [as fn] (app.js?id=d9e007128724c77a8d69ec76c6c081a0:30019:57)
at ReactiveEffect.run (app.js?id=d9e007128724c77a8d69ec76c6c081a0:23830:29)
at setupRenderEffect (app.js?id=d9e007128724c77a8d69ec76c6c081a0:30145:9)
at mountComponent (app.js?id=d9e007128724c77a8d69ec76c6c081a0:29928:9)
at processComponent (app.js?id=d9e007128724c77a8d69ec76c6c081a0:29886:17)
at patch (app.js?id=d9e007128724c77a8d69ec76c6c081a0:29487:21)
at render (app.js?id=d9e007128724c77a8d69ec76c6c081a0:30630:13)
at mount (app.js?id=d9e007128724c77a8d69ec76c6c081a0:28882:25)

What am I doing wrong?


Solution

  • In <template>:

    Getter only:

    • {{ myVal }}
    • <p v-text="myVal" />

    Getter and setter:

    • <input v-model="myVal">

    Important: Do not use myVal.get() or myVal.set(value)! Use:

    • getting: myVal
    • setting (assignment): myVal = value

    In <script setup>:

    Getter only:

    const someReactiveRef = ref(null)
    const myVal = computed(() => someReactiveRef.value)
    

    Getter & setter:

    const someReactiveRef = ref(null)
    
    const myVal = computed({
      get() {
        return someReactiveRef.value
      },
      set(val) {
        someReactiveRef.value = val
      }
    })
    // myVal can now be used in `v-model`
    

    When the reactive ref is coming from a reactive() object's property, you don't need the .value in either the setter or the getter. Example:

    const store = reactive({
      someProp: null
    })
    
    const myVal = computed({
      get() {
        return store.someProp
      },
      set(val) {
        store.someProp = val
      }
    })
    

    Remember you can always use computed inside reactive. Example:

    const { createApp, reactive, computed, toRefs } = Vue
    createApp({
      setup() {
        const state = reactive({
          firstName: 'John W.',
          lastName: 'Doe',
    
          // getter + setter
          fullName: computed({
            get() {
              return [state.firstName, state.lastName].join(' ')
            },
            set(val) {
              const [last, ...rest] = val.split(' ').reverse()
              state.firstName = rest.reverse().join(' ')
              state.lastName = last
            }
          }),
    
          // getter only:
          name: computed(() => state.fullName)
        })
        return toRefs(state)
      }
    }).mount('#app')
    <script src="https://unpkg.com/vue@3.2.40/dist/vue.global.prod.js"></script>
    <div id="app">
      <input v-model="firstName" />
      <input v-model="lastName" />
      <input v-model="fullName" />
      <pre v-text="{ firstName, lastName, fullName, name }"></pre>
    </div>

    Important notes:

    • when passing a non-reactive reference to a computed, Vue will warn you (the computed wrapper is unnecessary).
    • when passing it a ref which contains a cyclic dependency (e.g: a component instance) Vue will again warn you and advise on using either shallowRef or markRaw.