• בלוג
  • יומי
  • ניסוי ריאקט: משתנה סטייט לקריאה וכתיבה

ניסוי ריאקט: משתנה סטייט לקריאה וכתיבה

08/01/2025

ב Vue אנחנו יוצרים משתנה סטייט עם הפקודה ref ומקבלים משהו שאפשר לכתוב אליו וגם לקרוא ממנו לדוגמה הקוד הבא מציג מונה לחיצות עם משתנה סטייט ששומר את מספר הלחיצות:

<script setup>
import { ref } from 'vue'

const counter = ref(0)
function inc() { counter.value++ }
</script>

<template>
  <p>{{counter}}</p>
  <button @click="inc">+1</button>
</template>

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

תוכן עניינים

  1. פיתרון: useRefState

1. פיתרון: useRefState

בשביל לקבל רק אוביקט אחד שמאפשר כתיבה וקריאה אני משתמש בפרוקסי. הפרוקסי יעטוף את האוביקט ויתפוס כתיבות לשדה value שלו, ואז יחליף כל כתיבה לשדה value בהפעלת ה setter. כך נראה הקוד:

function toRef(value, setter) {
  return new Proxy(
    { value },
    {
      get(target, prop, receiver) {
        return value;
      },
      set(obj, prop, value) {
        setter(value);
      },
    }
  );
}

function useRefState(initialValue) {
  const [value, setter] = useState(initialValue);
  return toRef(value, setter);
}

בתוך קומפוננטה אפשר להשתמש במנגנון בדיוק כמו ב Vue:

export default function App() {
  const counter = useRefState(0);
  function inc() {
    counter.value++;
  }

  return (
    <div className="App">
      <p>{counter.value}</p>
      <button onClick={inc}>+1</button>
      <button onClick={() => counter.value--}>-1</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

הפונקציה inc מעלה ב-1 את ה value של counter, וזה מספיק בשביל לגרום לרינדור מחדש של הקומפוננטה. הצגת ה value מתוך התבנית מראה את הערך שלו.

עדיין יש מספר פערים בפיתרון:

  1. ב Vue אפשר להשתמש ב counter ישירות בתוך התבנית. ריאקט לא מרשה לי לכתוב אובייקטים בתוך התבנית ולכן חייבים להשתמש ב .value גם שם.

  2. ומה שיותר משמעותי, ב Vue רק קריאה ממשתנה ריאקטיבי יוצרת מנוי לערך של אותו משתנה. קומפוננטה שלא קוראת ממשתנה ריאקטיבי לא תתרנדר מחדש כשאותו משתנה יתעדכן. בריאקט השינויים קורים ברזולוציה של קומפוננטה ולכן מספיק שיש עדכון למשתנה סטייט שהוגדר בקומפוננטה מסוימת כדי שקומפוננטה זו תתרנדר מחדש.

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

function useRefState(initialValue) {
  const [value, setter] = useState(initialValue);
  return function (...args) {
    if (args.length > 0) {
      setter(...args);
    } else {
      return value;
    }
  };
}

export default function App() {
  const counter = useRefState(0);
  function inc() {
    counter((c) => c + 1);
  }

  return (
    <div className="App">
      <p>{counter()}</p>
      <button onClick={inc}>+1</button>
      <button onClick={() => counter((c) => c - 1)}>-1</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}