Vue 组合 API 中的只读目标

Posted

技术标签:

【中文标题】Vue 组合 API 中的只读目标【英文标题】:Readonly target in Vue composition API 【发布时间】:2021-04-06 21:16:34 【问题描述】:

我的“Generateur”组件正在向我的“Visionneuse”组件发送道具。在浏览器中一切正常,但我在控制台中有此消息:

Set operation on key "texteEnvoye" failed: target is readonly.

我真的不知道为什么会收到此消息,因为我将 prop 传递给了 ref。 这是我的组件: “发电机”

<template>
  <div>
    <h1>Génération d'une carte de voeux</h1>
    <div class="board">
      <Visionneuse :texteEnvoye="texte" :taille="0.5"/>
    </div>
    <textarea
      :style="'width: 60%; resize:none;height: 100px;'"
      v-model="texte"
      placeholder="Écrivez votre texte ici">
    </textarea>
  </div>
  <div>
    <button
      v-if="lien.length == 0"
      id="boutonObtenirLien"
      v-bind:class=" enCours: lienEnCours "
      class="btn first"
      @click="obtenirLien">Obtenir le lien</button>
    <p
      v-if="lien.length > 0">
      Votre carte de voeux est accessible au lien suivant:<br/>
      <a :href="lien"> lien </a>
    </p>
  </div>
</template>

<script>
import Visionneuse from '@/components/Visionneuse.vue';

import axios from 'axios';

import 
  defineComponent, ref,
 from 'vue';

export default defineComponent(
  name: 'Générateur',
  components: 
    Visionneuse,
  ,
  setup() 
    const texte = ref('');
    const lienEnCours = ref(false);
    const lien = ref('');

    function obtenirLien() 
      if (lienEnCours.value) 
        console.log('Je suis déjà en train de chercher!');
        return false;
      
      lienEnCours.value = true;

      axios.post(`$process.env.VUE_APP_API_URL/textes/creer/`, 
        texte: texte.value,
      ,
      
        headers: 
          'Content-Type': 'application/json',
        ,
      )
        .then((response) => 
          console.log(response.data);
          lien.value = `$process.env.VUE_APP_URL/carte/$response.data`;
        )
        .catch((error) => 
          console.log(error);
        )
        .then(() => 
          lienEnCours.value = false;
        );
      return true;
    

    return 
      texte,
      obtenirLien,
      lienEnCours,
      lien,
    ;
  ,
);

</script>

还有“幻视者”

<template>
  <div class="board">
    <canvas
      ref='carte'
      :
      :
      tabindex='0'
      style="border:1px solid #000000;"
    ></canvas>
  </div>
  <div id="texteRemplacement" v-if="petit">
    <p v-for="p in texte.split('\n')" v-bind:key="p">
       p 
    </p>
  </div>
</template>

<script>

import 
  defineComponent, onMounted, ref, reactive, nextTick, toRefs, watch,
 from 'vue';

export default defineComponent(
  name: 'Visionneuse',
  props: ['texteEnvoye', 'taille'],
  setup(props) 
    const myCanvas = ref(null);
    const carte = ref(null);
    const  texteEnvoye: texte, taille  = toRefs(props);

    const rapport = ref(0);
    const petit = ref((window.innerWidth < 750));

    const size = reactive(
      w: window.innerWidth * taille.value,
      h: window.innerWidth * taille.value,
    );

    function drawText() 
      const fontSize = 0.05 * size.w - 10;
      myCanvas.value.font = `$fontSizepx Adrip`;
      myCanvas.value.textAlign = 'center';
      myCanvas.value.fillStyle = 'lightgrey';
      myCanvas.value.strokeStyle = 'black';
      myCanvas.value.lineWidth = 0.006 * size.w - 10;
      const x = size.w / 2;
      const lineHeight = fontSize;
      const lines = texte.value.split('\n');
      for (let i = 0; i < lines.length; i += 1) 
        myCanvas.value.fillText(
          lines[lines.length - i - 1],
          x,
          (size.h * 0.98) - (i * lineHeight),
        );
        myCanvas.value.strokeText(
          lines[lines.length - i - 1],
          x,
          (size.h * 0.98) - (i * lineHeight),
        );
      
    

    function initCarte() 
      const background = new Image();
      background.src = '/img/fond.jpeg';
      background.onload = function () 
        rapport.value = background.naturalWidth / background.naturalHeight;
        size.h = size.w / rapport.value;
        nextTick(() => 
          try 
            myCanvas.value.drawImage(background, 0, 0, size.w, size.h);
           catch (e) 
            console.log(`ERREUR DE CHARGEMENT D'IMAGE: $e`);
          
          if (!petit.value) 
            drawText();
          
        );
      ;
    

    function handleResize() 
      size.w = window.innerWidth * taille.value;
      size.h = size.w / rapport.value;
      petit.value = window.innerWidth < 750;
      initCarte();
    

    window.addEventListener('resize', handleResize);

    watch(texte, (_, y) => 
      texte.value = y;
      initCarte();
    );

    onMounted(() => 
      const c = carte.value;
      const ctx = c.getContext('2d');
      myCanvas.value = ctx;
      initCarte();
    );

    return 
      myCanvas,
      size,
      texte,
      petit,
      carte,
    ;
  ,
);

</script>

【问题讨论】:

【参考方案1】:

好的,我找到了解决方案。我将“texteEnvoye”和“texte”分开,一切正常。我不知道这是否是在组合 API 中编写代码的好方法,但它成功了:

<template>
  <div class="board">
    <canvas
      ref='carte'
      :
      :
      tabindex='0'
      style="border:1px solid #000000;"
    ></canvas>
  </div>
  <div id="texteRemplacement" v-if="petit">
    <p v-for="p in texte.split('\n')" v-bind:key="p">
       p 
    </p>
  </div>
</template>

<script>

import 
  defineComponent, onMounted, ref, reactive, nextTick, toRefs, watch,
 from 'vue';

export default defineComponent(
  name: 'Visionneuse',
  props: ['texteEnvoye', 'taille'],
  setup(props) 
    const myCanvas = ref(null);
    const carte = ref(null);
    const  texteEnvoye, taille  = toRefs(props);
    const texte = ref('');

    const rapport = ref(0);
    const petit = ref((window.innerWidth < 750));

    const size = reactive(
      w: window.innerWidth * taille.value,
      h: window.innerWidth * taille.value,
    );

    function drawText() 
      const fontSize = 0.05 * size.w - 10;
      myCanvas.value.font = `$fontSizepx Adrip`;
      myCanvas.value.textAlign = 'center';
      myCanvas.value.fillStyle = 'lightgrey';
      myCanvas.value.strokeStyle = 'black';
      myCanvas.value.lineWidth = 0.006 * size.w - 10;
      const x = size.w / 2;
      const lineHeight = fontSize;
      const lines = texte.value.split('\n');
      for (let i = 0; i < lines.length; i += 1) 
        myCanvas.value.fillText(
          lines[lines.length - i - 1],
          x,
          (size.h * 0.98) - (i * lineHeight),
        );
        myCanvas.value.strokeText(
          lines[lines.length - i - 1],
          x,
          (size.h * 0.98) - (i * lineHeight),
        );
      
    

    function initCarte() 
      const background = new Image();
      background.src = '/img/fond.jpeg';
      background.onload = function () 
        rapport.value = background.naturalWidth / background.naturalHeight;
        size.h = size.w / rapport.value;
        nextTick(() => 
          try 
            myCanvas.value.drawImage(background, 0, 0, size.w, size.h);
           catch (e) 
            console.log(`ERREUR DE CHARGEMENT D'IMAGE: $e`);
          
          if (!petit.value) 
            drawText();
          
        );
      ;
    

    function handleResize() 
      size.w = window.innerWidth * taille.value;
      size.h = size.w / rapport.value;
      petit.value = window.innerWidth < 750;
      initCarte();
    

    window.addEventListener('resize', handleResize);

    watch(texteEnvoye, (x) => 
      texte.value = x;
      initCarte();
    );

    onMounted(() => 
      const c = carte.value;
      const ctx = c.getContext('2d');
      myCanvas.value = ctx;
      initCarte();
    );

    return 
      myCanvas,
      size,
      texte,
      petit,
      carte,
    ;
  ,
);

</script>

【讨论】:

以上是关于Vue 组合 API 中的只读目标的主要内容,如果未能解决你的问题,请参考以下文章

Vue3中 响应式 API ( readonlyshallowReadonlytoRawmarkRaw ) 详解

Vue导入导致无法分配给对象错误的只读属性'exports'

删除文件夹中的只读权限,以便在目标文件夹中上载文件(php)

Vue3中 响应式 API ( readonlyshallowReadonlytoRawmarkRaw ) 详解

Vue3中 响应式 API ( readonlyshallowReadonlytoRawmarkRaw ) 详解

在python中将`QTableWidget`中的一整列设置为只读