example.mp4

<aside> πŸ™

https://github.com/djk01281/YJS-SocketIO

  1. Socket.io둜 톡신, Y.doc에 μΌμ–΄λ‚œ λ³€κ²½ 값을 μ„œλ²„μ—μ„œ 확인
  2. ν΄λΌμ΄μ–ΈνŠΈλ“€κ³Ό μ„œλ²„ κ°„ 같은 Y.doc을 곡유 </aside>

YJS

image.png

YJSλŠ” μƒνƒœλ₯Ό μΆ©λŒμ—†μ΄ κ³΅μœ ν•  수 있게 ν•΄μ£ΌλŠ” 라이브러리라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 μƒνƒœλ₯Ό λ‹΄μ•„λ‘” 곡간을 Y.Doc이라고 ν•©λ‹ˆλ‹€.

λ™μ‹œμ„± 해결을 μœ„ν•΄μ„œλŠ” CRDTλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

λ™μ‹œ μ‚¬μš©μ΄ κ°€λŠ₯ν•œ ν† κΈ€ λ²„νŠΌ λ§Œλ“€κΈ°

κ°€μž₯ κ°„λ‹¨ν•œ μ˜ˆμ‹œλ₯Ό 생각해보면, λ‹€μŒκ³Ό 같을 것 κ°™μŠ΅λ‹ˆλ‹€.

const toggleMap = {
    toggle: true
}

이와 같은 object에 μ—¬λŸ¬ μ‚¬λžŒμ΄ λ™μ‹œμ— μ ‘κ·Όν•΄μ•Όν•˜λŠ” μƒν™©μž…λ‹ˆλ‹€. 각 ν΄λΌμ΄μ–ΈνŠΈμ—λŠ” λ²„νŠΌμ΄ 있고, 이λ₯Ό λˆŒλŸ¬μ„œ λ²„νŠΌμ˜ μƒνƒœλ₯Ό 토글해쀄 수 μžˆμŠ΅λ‹ˆλ‹€.

μ‹€μ‹œκ°„μœΌλ‘œ λ™μž‘ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ—, λ‹€λ₯Έ ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ ν΄λ¦­ν–ˆμ„ λ•Œμ˜ 변화도 λ°”λ‘œ μ•Œ 수 있고 λ™μ‹œμ— ν΄λ¦­ν•˜λ”λΌλ„ 이에 λŒ€ν•œ μΆ©λŒμ„ ν•΄κ²°ν•΄μ€˜μ•Ό ν•©λ‹ˆλ‹€.

[ν΄λΌμ΄μ–ΈνŠΈ]

이λ₯Ό μœ„ν•œ ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

import { useEffect, useState, useRef } from "react";
import * as Y from "yjs";
import { SocketIOProvider } from "y-socket.io";

export default function App() {
  const [toggle, setToggle] = useState(false);

  const ydocRef = useRef<Y.Doc>();
  const providerRef = useRef<SocketIOProvider>();

  useEffect(() => {
    ydocRef.current = new Y.Doc();

    const provider = new SocketIOProvider(
      "<http://localhost:3000>",
      "room-name",
      ydocRef.current,
      {
        autoConnect: true,
        disableBc: false,
      },
      {
        reconnectionDelayMax: 10000,
        timeout: 5000,
        transports: ["websocket", "polling"],
      }
    );

    providerRef.current = provider;

    provider.awareness.setLocalState({
      user: {
        name: `User-${Math.floor(Math.random() * 100)}`,
      },
    });

    const toggleMap = ydocRef.current.getMap("toggleMap");

    const updateToggleState = () => {
      const newValue = toggleMap.get("toggle");
      setToggle(Boolean(newValue));
    };
    if (!toggleMap.has("toggle")) {
      toggleMap.set("toggle", false);
    } else {
      updateToggleState();
    }

    toggleMap.observe(() => {
      updateToggleState();
    });

    return () => {
      provider.destroy();
      ydocRef.current?.destroy();
    };
  }, []);

  const handleToggle = () => {
    if (!ydocRef.current) return;

    const toggleMap = ydocRef.current.getMap("toggleMap");
    const newValue = !toggle;

    toggleMap.set("toggle", newValue);
  };

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "20px",
        justifyContent: "center",
        alignItems: "center",
        width: "100vw",
        height: "100vh",
        backgroundColor: "#231f20",
      }}
    >
      <button
        onClick={handleToggle}
        style={{
          width: "96px",
          height: "96px",
          borderRadius: "50%",
          transition: "background-color 0.3s",
          backgroundColor: toggle ? "#f7b528" : "#e9203d",
          border: "none",
          cursor: "pointer",
        }}
      />
    </div>
  );
}

처음 ν•œλ²ˆλ§Œ useEffect λ‚΄λΆ€μ˜ ν•¨μˆ˜κ°€ μ‹€ν–‰λ©λ‹ˆλ‹€.

ydocRef.current = new Y.Doc();

const provider = new SocketIOProvider(
  "<http://localhost:3000>",
  "room-name",
  ydocRef.current,
  {
    autoConnect: true,
    disableBc: false,
  },
  {
    reconnectionDelayMax: 10000,
    timeout: 5000,
    transports: ["websocket", "polling"],
  }
);

providerRef.current = provider;