برنتی / می‌نویسم که فکر کنم

← آزمایش‌ها

useEffectEvent و closure کهنه

§ useEffectEvent و closure کهنه

دو handler به یک connection ساختگی وصل شده‌اند و effect هر دو فقط یک بار اجرا می‌شود: یکی تابع معمولی است که از deps بیرون مانده و دیگری useEffectEvent. «اتصال دوباره» را بزنید، تم را عوض کنید و دوباره وصل شوید؛ تابع معمولی هنوز تم اول را می‌خواند و useEffectEvent تم فعلی را.

پیش‌نمایش

تم فعلی: light

خروجی

هنوز چیزی ثبت نشده.

کد

import { useEffect, useEffectEvent, useState } from "react";
import { Button } from "@/components/ui/button.tsx";
import { Typography } from "@/components/ui/typography.tsx";
import { useLogger } from "@/components/pages/tests/experiment.tsx";

type Theme = "light" | "dark";

// A fake chat connection. reconnect() fires every "connected" handler again,
// the way a chat library does after a network blip, without re-running the
// effect that registered it.
const connection = {
  handlers: new Set<() => void>(),
  on(_event: "connected", handler: () => void) {
    this.handlers.add(handler);
    return () => {
      this.handlers.delete(handler);
    };
  },
  reconnect() {
    this.handlers.forEach((handler) => handler());
  },
};

// Which theme each handler showed its notification in, on the last reconnect.
const shown = new Map<string, Theme>();

function showNotification(handler: string, theme: Theme) {
  shown.set(handler, theme);
}

// Plain function, left out of the deps: the effect runs once, so the
// connection keeps the onConnected from the first render, and its theme.
function PlainFunction({ theme }: { theme: Theme }) {
  const onConnected = () => {
    showNotification("() => {}", theme);
  };

  useEffect(() => {
    return connection.on("connected", () => onConnected());
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return null;
}

// useEffectEvent: the effect still runs once, but onConnected reads the
// latest theme every time it's called.
function WithEffectEvent({ theme }: { theme: Theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification("useEffectEvent", theme);
  });

  useEffect(() => {
    return connection.on("connected", () => onConnected());
  }, []);

  return null;
}

export function UseEffectEventDemo() {
  const [theme, setTheme] = useState<Theme>("light");
  const { log, logTable } = useLogger();

  function toggleTheme() {
    const next = theme === "light" ? "dark" : "light";
    setTheme(next);
    log("تم عوض شد:", next);
  }

  function reconnect() {
    shown.clear();
    connection.reconnect();
    logTable(
      [...shown].map(([handler, read]) => ({
        handler,
        "theme read": read,
        "theme now": theme,
        stale: read !== theme,
      })),
    );
  }

  return (
    <div className="flex flex-col gap-4">
      <PlainFunction theme={theme} />
      <WithEffectEvent theme={theme} />

      <div className="flex flex-wrap gap-2">
        <Button variant="secondary" onClick={toggleTheme}>
          تغییر تم
        </Button>
        <Button onClick={reconnect}>اتصال دوباره</Button>
      </div>
      <Typography variant="caption">
        تم فعلی:{" "}
        <Typography as="span" variant="mono" className="text-text">
          {theme}
        </Typography>
      </Typography>
    </div>
  );
}