vue.jscomputed-properties

Why Vue Can't Find Variable When Render Computed Property In Setup Script To Template


I am migrating to Vue SFC and have problem using computed property.

With the following code, I want to create two rows, and the visibility of the bottom row (the content row) can be toggled by clicking the top row (the title row).

<!-- components/Note.vue -->
<script setup>
import { marked } from "marked";
import { ref, computed } from "vue";

const props = defineProps({
  id: Number,
  title: String,
  create: String,
  content: String
});

const showContent = ref(false);
const elContent = ref(null);

const scrollHeight = computed(() => {
  return elContent.value.scrollHeight + "px";
});

const markedContent = computed(() => {
  return marked.parse(content.value);
})
</script>

<template>
  <div class="row" @click="showContent=!showContent">
    <div class="col-9">{{ title }}</div>
    <div class="col-3">{{ create }}</div>
  </div>
  <div class="row" :style="{height:showContent?scrollHeight:0}" ref="elContent">
    <div class="col">{{ markedContent }}</div>
  </div>
</template>

Here is the error message

[Vue warn]: Unhandled error during execution of render function (9)
"
"
" at <Note"
"id=27"
"title=\"1Lorem Ipsum\""
" ..."
">"
"
"
" at <App>"

[Vue warn]: Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core (9)
"
"
" at <Note"
"id=27"
"title=\"1Lorem Ipsum\""
" ..."
">"
"
"
" at <App>"

Unhandled Promise Rejection: ReferenceError: Can't find variable: content

However, if I don't use computed property, but the marked.parse method directly in the template, then everything works smoothly.

<!-- This is fine -->
<div class="row" :style="{height:showContent?scrollHeight:0}" ref="elContent">
  <div class="col">{{ marked.parse(content) }}</div>
</div>

I hope someone can tell me why this happens and how should I use computed property in script setup.


Solution

  • Use the props.probName pattern to access props in script-setup files. However, in the template section, you must use probName directly.

    So in your case, use props.content instead of content to address the issue.

    const markedContent = computed(() => {
      return marked.parse(props.content);
    //return marked.parse(props.content.value);
    })
    

    The props name comes from the definition of your props, where you choose a variable name to hold all properties.

    // Here you've defined a variable name (props) that represents all properties
    const props = defineProps({
      id: Number,
      title: String,
      create: String,
      content: String
    });
    

    Access props directly in the setup

    If you wish to access props directly (as in the template), you can destruct the props at the place:

    const {content, id, title, create} = defineProps({
      id: Number,
      title: String,
      create: String,
      content: String
    });
    
    const markedContent = computed(() => {
      return marked.parse(content);
    })
    

    UPDATE: A note from the comments

    Keep in mind that destructing props is not straightforward. It brings more coding and complexity, therefore I avoid it.

    You need to always access props as props.x in order to retain reactivity. This means you cannot destructure defineProps because the resulting destructured variables are not reactive and will not update. (link)

    To tackle the losing reactivity problem, you need to either use toRef manually or leverage the Vue Macros plugin.

    Reactivity Transform was an experimental feature, and has been deprecated