חידת Vue משתנים ריאקטיביים

03/01/2025

נתון קוד Vue הבא:

<script setup>
import { ref } from 'vue'
const init = {count: 0}
const item = ref(init)

function inc() { item.value.count++ }
function reset() { item.value = init }
</script>

<template>
  <p>{{ item.count }}</p>
  <button @click="inc">+1</button>
  <button @click="reset">Reset</button>
</template>

משתמש לוחץ כמה פעמים על כפתור הפלוס ואז על כפתור ה Reset. האם הערך בתיבה מתאפס? למה?

וכן הכנתי לכם את זה כבר בלייב שתוכלו לבדוק כאן: דוגמת לייב

תוכן עניינים

  1. הסבר

1. הסבר

הכפתור לא מאפס את הערך, או יותר נכון הוא מאפס אבל לא לערך שרצינו. זה מה שקורה:

  1. פקודת ה ref יוצרת קישור ל"דבר" שהיא קיבלה בסוגריים. מאחר והדבר הזה הוא אוביקט יהיה לנו פרוקסי שמחזיק אוביקט, אבל ההעתקה היא שטחית.

  2. שינוי הערך של item הוא בעצם שינוי הערך של init, כי item הוא בסך הכל פרוקסי ריאקטיבי ל init.

  3. כשלוחצים איפוס הערך של init מועתק ל item, אבל זה בסך הכל העתקה של משתנה לעצמו ולכן הערך של item לא משתנה.

זה מבלבל כי הרבה פעמים אנחנו מפעילים את ref על מספרים או מחרוזות. שם העתקה שטחית יוצרת עדיין עותק של אותו מספר או מחרוזת ולכן שינוי של item לא ישפיע על init, כלומר קוד כזה יעבוד בלי בעיה:

const init = 0;
const count = ref(init);

function inc() { count.value++ }
function reset() { count.value = init }

מסיבה זו אגב אני מעדיף להשתמש ב reactive על אוביקטים ו ref על משתנים פשוטים. ההבדל בשם גם רומז על ההבדל במשמעות.

בכל מקרה פיתרון קל לסיטואציה אחרי שהבנו מה קורה ואם אי אפשר לעבור למשתנים פשוטים הוא להעתיק את האוביקט לפני שמפעילים ref כלומר (בהנחה שאין פונקציות באוביקט):

import { ref } from 'vue'
const init = {count: 0}
const item = ref(JSON.parse(JSON.stringify(init)));

function inc() { item.value.count++ }
function reset() { item.value = init }

או אם אפשר עדיף להחליף את init בפונקציה שמחזירה את הערך הראשוני וככה אתם מבטיחים שאף פעם לא תשנו את הערך הראשוני:

import { ref } from 'vue'
const init = () => ({count: 0})
const item = ref(init());

function inc() { item.value.count++ }
function reset() { item.value = init() }