Skip to content

hammoooo/ge-fe

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

195 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Menual โ€” ๋ทฐํ‹ฐ ์ „๋ฌธ๊ฐ€ ๋งค์นญ ํ”Œ๋žซํผ

ํ—ค์–ด, ํŒจ์…˜, ๋ฉ”์ดํฌ์—…, ํ”ผ๋ถ€ ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€์™€ ๊ณ ๊ฐ์„ ์—ฐ๊ฒฐํ•˜๋Š” ๋ทฐํ‹ฐ ์ปจ์„คํŒ… ์„œ๋น„์Šค


๐Ÿ“– ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

Menual์€ ๊ณ ๊ฐ์ด ๋‹จ๊ณ„๋ณ„ ๊ณ ๋ฏผ์ง€๋ฅผ ์ž‘์„ฑํ•ด ๋ทฐํ‹ฐ ์ „๋ฌธ๊ฐ€์—๊ฒŒ ์ „๋‹ฌํ•˜๋ฉด, ์ „๋ฌธ๊ฐ€๊ฐ€ ์†”๋ฃจ์…˜์ง€๋กœ ๋‹ตํ•˜๊ณ  ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…์œผ๋กœ ์ถ”๊ฐ€ ์ƒ๋‹ด๊นŒ์ง€ ์ด์–ด์ง€๋Š” ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค.


๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

Development

Deployment


๐Ÿ“† ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„

  • ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„: 2025.09 - 2026.02

๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•œ ๊ธฐ๋Šฅ

ํŒ€ ํ”„๋กœ์ ํŠธ Menual ๋‚ด์—์„œ ์ง์ ‘ ๊ตฌํ˜„ํ•œ ํŒŒํŠธ์˜ ํฌํŠธํด๋ฆฌ์˜ค ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.


1. ๋ฐฐํฌ ์ž๋™ํ™” (CI/CD)

GitHub Actions + Vercel ๊ธฐ๋ฐ˜ ์ž๋™ ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ

develop ๋ธŒ๋žœ์น˜์— Push๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ณ„๋„์˜ ์กฐ์ž‘ ์—†์ด ํ”„๋กœ๋•์…˜๊นŒ์ง€ ์ž๋™์œผ๋กœ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค.

  • GitHub Actions์—์„œ ๋นŒ๋“œ ํ›„ cpina/github-action-push-to-another-repository๋กœ ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ์„ ๋ฐฐํฌ ์ „์šฉ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ์ž๋™ Push
  • Vercel์ด ๋ฐฐํฌ ๋ ˆํฌ๋ฅผ ๊ฐ์ง€ํ•ด ์ฆ‰์‹œ ํ”„๋กœ๋•์…˜ ๋ฐ˜์˜
  • ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐฐํฌ ๋ ˆํฌ์˜ ์ปค๋ฐ‹์— ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•ด ๋ฐฐํฌ ์ด๋ ฅ ์ถ”์  ๊ฐ€๋Šฅ
# .github/workflows/deploy.yml
- name: Pushes to another repository
  uses: cpina/github-action-push-to-another-repository@main
  env:
    API_TOKEN_GITHUB: ${{ secrets.AUTO_ACTIONS }}
  with:
    source-directory: "output"
    destination-github-username: dragunshin
    destination-repository-name: ge-fe
    commit-message: ${{ github.event.commits[0].message }}
    target-branch: develop

2. ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… (Chat)

STOMP over SockJS ๊ธฐ๋ฐ˜ WebSocket ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์‹œ์Šคํ…œ

useStompClient โ€” ๋ชจ๋“ˆ ์‹ฑ๊ธ€ํ†ค ์—ฐ๊ฒฐ ๊ด€๋ฆฌ

์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋™์‹œ์— ๋งˆ์šดํŠธ๋˜์–ด๋„ WebSocket ์—ฐ๊ฒฐ์ด ๋‹จ ํ•˜๋‚˜๋งŒ ์œ ์ง€๋˜๋„๋ก ๋ชจ๋“ˆ ๋ ˆ๋ฒจ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๋งˆ์šดํŠธ ์นด์šดํ„ฐ(mounts)๋กœ ์—ฐ๊ฒฐ ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ โ†’ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์—ฐ๊ฒฐ ํ•ด์ œ
  • onConnect ํ”„๋ ˆ์ž„์˜ user-name ํ—ค๋”๋ฅผ ํŒŒ์‹ฑํ•ด ๋ณธ์ธ userId๋ฅผ ์ถ”์ถœ โ†’ ๋ฉ”์‹œ์ง€ ๋งํ’์„  ์ขŒ์šฐ ๊ตฌ๋ถ„์— ํ™œ์šฉ
  • ์žฌ์—ฐ๊ฒฐ ์‹œ onConnect์—์„œ ๋ฐ€๋ฆฐ ๊ตฌ๋…์„ ์ž๋™ flush โ†’ ๋„คํŠธ์›Œํฌ ๋ณต๊ตฌ ํ›„ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ฆ‰์‹œ ์žฌ๊ฐœ
  • ws:// / wss:// ์Šคํ‚ด์„ http:// / https://๋กœ ์ž๋™ ๋ณ€ํ™˜ (SockJS ์š”๊ตฌ์‚ฌํ•ญ ์ฒ˜๋ฆฌ)
// onConnect์—์„œ ๋ณธ์ธ userId ํŒŒ์‹ฑ
onConnect: (frame: IFrame) => {
  emitReady(true);
  const myId = parseMyUserIdFromHeaders(frame.headers as Record<string, string>);
  if (myId != null) emitMyUserId(myId);

  // ์žฌ์—ฐ๊ฒฐ ์‹œ ๋ฐ€๋ฆฐ ๊ตฌ๋… flush
  for (const r of subs) {
    if (!r.active) continue;
    if (!r.sub) r.sub = client.subscribe(r.destination, r.handler);
  }
},

useChatRoom โ€” ์ฑ„ํŒ…๋ฐฉ ๋ฉ”์‹œ์ง€ ์ƒํƒœ ๊ด€๋ฆฌ

  • ํžˆ์Šคํ† ๋ฆฌ ๋กœ๋“œ ์‹คํŒจ(403)๊ฐ€ ์‹ค์‹œ๊ฐ„ ์—ฐ๊ฒฐ์„ ๋ง‰์ง€ ์•Š๋„๋ก ๋…๋ฆฝ์ ์ธ useEffect๋กœ ๋ถ„๋ฆฌ
  • roomIdRef๋กœ ์ฝœ๋ฐฑ ๋‚ด stale ํด๋กœ์ € ๋ฐฉ์ง€ โ†’ ๋ฃธ ID ๋ณ€๊ฒฝ ์‹œ์—๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ตฌ๋…
  • functional update + messageId ์ค‘๋ณต ์ฒดํฌ๋กœ ์„œ๋ฒ„ ์—์ฝ” ๋ฉ”์‹œ์ง€ ์ค‘๋ณต ๋ Œ๋”๋ง ๋ฐฉ์ง€
// functional update๋กœ stale ํด๋กœ์ € ์—†์ด ์ค‘๋ณต ๋ฉ”์‹œ์ง€ ์ œ๊ฑฐ
setMessages((prev) => {
  if (prev.some((m) => m.messageId === incoming.messageId)) return prev;
  return [...prev, incoming];
});

3. ๊ณ ๋ฏผ์ง€ ํ”Œ๋กœ์šฐ โ€” ํ—ค์–ด (Hair Flow)

URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ธฐ๋ฐ˜ 7๋‹จ๊ณ„ ๋ฉ€ํ‹ฐ ์Šคํ… ํผ

ํ—ค์–ด ์ƒ๋‹ด์— ํ•„์š”ํ•œ ์ •๋ณด(์‚ฌ์ง„ 4์ข… + ์–ผ๊ตดํ˜• + ์›ํ•˜๋Š” ์ด๋ฏธ์ง€ + ์งˆ๋ฌธ)๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค.

๋‹จ๊ณ„ ๋‚ด์šฉ
1/7 ํ‰์ƒ์‹œ ํ—ค์–ด์Šคํƒ€์ผ ์‚ฌ์ง„ ์—…๋กœ๋“œ
2/7 ๋จธ๋ฆฌ๋ฅผ ์˜ฌ๋ฆฐ ์ •๋ฉด ์‚ฌ์ง„ ์—…๋กœ๋“œ
3/7 ์ธก๋ฉด ์‚ฌ์ง„ ์—…๋กœ๋“œ
4/7 ์›ํ•˜๋Š” ์ด๋ฏธ์ง€ ์‚ฌ์ง„ ์—…๋กœ๋“œ
5/7 ์–ผ๊ตดํ˜• ์„ ํƒ
6/7 ์›ํ•˜๋Š” ํ—ค์–ด ์Šคํƒ€์ผ ์„ ํƒ
7/7 ์ถ”๊ฐ€ ์งˆ๋ฌธ ์ž‘์„ฑ
  • ?step=N&reservationId=X ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋‹จ๊ณ„ ๊ด€๋ฆฌ โ†’ ์ƒˆ๋กœ๊ณ ์นจ, ๊ณต์œ  URL์—๋„ ์ƒํƒœ ์œ ์ง€
  • useLayoutEffect + requestAnimationFrame์œผ๋กœ ๋‹จ๊ณ„ ์ „ํ™˜๋งˆ๋‹ค ์Šคํฌ๋กค์ด ์ƒ๋‹จ์œผ๋กœ ์ด๋™
  • useHairSetupStore (Zustand)๋กœ ๋‹จ๊ณ„ ๊ฐ„ ์‚ฌ์ง„ ํ‚ค(S3 ๊ฒฝ๋กœ) ๋ณด์กด
  • S3 Presigned URL๋กœ ์‚ฌ์ง„์„ ์ง์ ‘ ์—…๋กœ๋“œ โ†’ ์„œ๋ฒ„ ๋ถ€ํ•˜ ์—†์ด ๋Œ€์šฉ๋Ÿ‰ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
// ๋‹จ๊ณ„ ์ „ํ™˜ ์‹œ ์Šคํฌ๋กค ํƒ‘ ๋ณด์žฅ
useLayoutEffect(() => {
  requestAnimationFrame(() => {
    window.scrollTo({ top: 0, left: 0, behavior: "auto" });
    (document.scrollingElement ?? document.documentElement).scrollTop = 0;
  });
}, [loc.key, step]);

4. S3 ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ โ€” AbortController ๊ธฐ๋ฐ˜ ์•ˆ์ „ํ•œ ํ”„๋ฆฌ๋ทฐ ๊ด€๋ฆฌ

๋ฌธ์ œ ์ƒํ™ฉ: ์„œ๋ฒ„ ์˜ค๋ฅ˜ ์‹œ ํ”„๋ฆฌ๋ทฐ๊ฐ€ ๋‚จ์•„ "์—…๋กœ๋“œ๋œ ๊ฒƒ์ฒ˜๋Ÿผ" ๋ณด์ด๋Š” UX ์˜ค๋ฅ˜

์‚ฌ์ง„์„ ์„ ํƒํ•˜๋ฉด ๋กœ์ปฌ Object URL๋กœ ํ”„๋ฆฌ๋ทฐ๋ฅผ ์ฆ‰์‹œ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ดํ›„ S3 ์—…๋กœ๋“œ๊ฐ€ ์‹คํŒจํ•˜๋ฉด, ํ”„๋ฆฌ๋ทฐ๋Š” ๊ทธ๋Œ€๋กœ ๋ณด์ด๋Š” ์ฑ„ S3 ํ‚ค๋งŒ ์—†๋Š” ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์—…๋กœ๋“œ๊ฐ€ ๋œ ์ค„ ์•Œ๊ณ  ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜์ง€ ์•Š์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ: ์—…๋กœ๋“œ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ํ”„๋ฆฌ๋ทฐ ์ƒ๋ช…์ฃผ๊ธฐ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ

๋‹จ์ผ ์‚ฌ์ง„ ์—…๋กœ๋“œ (SinglePhotoField)

  • ํŒŒ์ผ ์„ ํƒ ์ฆ‰์‹œ URL.createObjectURL()๋กœ ๋กœ์ปฌ ํ”„๋ฆฌ๋ทฐ๋ฅผ ํ‘œ์‹œํ•˜๊ณ  S3 ์—…๋กœ๋“œ ์‹œ์ž‘
  • AbortController๋ฅผ abortRef๋กœ ๋ณด๊ด€ โ†’ ์œ ์ €๊ฐ€ X ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜, ์ƒˆ ํŒŒ์ผ์„ ์„ ํƒํ•˜๊ฑฐ๋‚˜, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋  ๋•Œ abort() ํ˜ธ์ถœ๋กœ ์ง„ํ–‰ ์ค‘์ธ presign-PUT ์š”์ฒญ์„ ์ฆ‰์‹œ ์ทจ์†Œ
  • seqRef (์—…๋กœ๋“œ ์ˆœ์„œ ์นด์šดํ„ฐ)๋กœ ์ƒˆ ์—…๋กœ๋“œ๊ฐ€ ์‹œ์ž‘๋œ ๋’ค ๋„์ฐฉํ•œ ์ด์ „ ์‘๋‹ต์„ ๋ฌด์‹œ โ†’ ๋น ๋ฅด๊ฒŒ ์‚ฌ์ง„์„ ๋ฐ”๊ฟ”๋„ stale ๊ฒฐ๊ณผ๊ฐ€ Zustand์— ์ €์žฅ๋˜์ง€ ์•Š์Œ
  • S3 ์—…๋กœ๋“œ ์‹คํŒจ ์‹œ: URL.revokeObjectURL()๋กœ ํ”„๋ฆฌ๋ทฐ๋ฅผ ์ฆ‰์‹œ ์ œ๊ฑฐ โ†’ ์‚ฌ์šฉ์ž๊ฐ€ ์—…๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋œ ๊ฒƒ์œผ๋กœ ์˜ค์ธํ•˜๋Š” ์ƒํ™ฉ ๋ฐฉ์ง€
  • presign ์š”์ฒญ๊ณผ S3 PUT ์š”์ฒญ ๋ชจ๋‘ ๋™์ผํ•œ signal์„ ์ „๋‹ฌํ•ด ์ค‘๋‹จ ์‹œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋‘ ๋‹จ๊ณ„ ๋ชจ๋‘ ์ทจ์†Œ๋จ
const onChange = async (file: File) => {
  handleRemove(); // ์ด์ „ ์—…๋กœ๋“œ abort + ํ”„๋ฆฌ๋ทฐ ์ดˆ๊ธฐํ™”

  const localUrl = URL.createObjectURL(file);
  setPreviewUrl(localUrl); // ๋กœ์ปฌ ํ”„๋ฆฌ๋ทฐ ์ฆ‰์‹œ ํ‘œ์‹œ

  const controller = new AbortController();
  abortRef.current = controller;
  const mySeq = ++seqRef.current;

  try {
    const { key } = await uploadImageViaPresign({
      file,
      resourceType,
      resourceId,
      imageType,
      signal: controller.signal, // presign + PUT ๋ชจ๋‘ ๋™์ผ signal ์ „๋‹ฌ
    });

    if (seqRef.current !== mySeq) return; // ์ƒˆ ์—…๋กœ๋“œ๊ฐ€ ์‹œ์ž‘๋์œผ๋ฉด ๊ฒฐ๊ณผ ๋ฌด์‹œ
    onUploadedKey(key); // ์„ฑ๊ณต ์‹œ์—๋งŒ Zustand์— S3 ํ‚ค ์ €์žฅ
  } catch (e) {
    if (controller.signal.aborted) return; // abort๋กœ ์ธํ•œ ์—๋Ÿฌ๋Š” ๋ฌด์‹œ
    clearLocalPreview(); // ์‹คํŒจ ์‹œ ํ”„๋ฆฌ๋ทฐ ์ œ๊ฑฐ โ†’ ์˜คํ•ด ๋ฐฉ์ง€
  } finally {
    if (seqRef.current === mySeq) setIsUploading(false);
  }
};

๋‹ค์ค‘ ์‚ฌ์ง„ ์—…๋กœ๋“œ (MultiPhotoPicker)

  • ์ƒ์„ฑํ•œ ๋ชจ๋“  Object URL์„ objectUrlsRef(Set)์— ๋“ฑ๋กํ•ด ๋ˆ„๋ฝ ์—†์ด ์ถ”์ 
  • ์—…๋กœ๋“œ ์‹คํŒจ ์‹œ ํ•ด๋‹น Object URL๋งŒ ์ฆ‰์‹œ revoke + Set์—์„œ ์ œ๊ฑฐ โ†’ ํ”„๋ฆฌ๋ทฐ ์‚ฌ๋ผ์ง
  • ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ Set์— ๋‚จ์€ ๋ชจ๋“  URL์„ ์ผ๊ด„ revoke โ†’ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€
useEffect(() => {
  return () => {
    objectUrlsRef.current.forEach((url) => URL.revokeObjectURL(url));
    objectUrlsRef.current.clear();
  };
}, []);

const addFile = async (file: File) => {
  const localUrl = URL.createObjectURL(file);
  objectUrlsRef.current.add(localUrl);

  try {
    const { key } = await uploadImageViaPresign({ file, ... });
    setPreviews((p) => [...p, { key, previewUrl: localUrl }]); // ์„ฑ๊ณต ์‹œ์—๋งŒ ํ”„๋ฆฌ๋ทฐ ๋“ฑ๋ก
  } catch {
    URL.revokeObjectURL(localUrl);
    objectUrlsRef.current.delete(localUrl); // ์‹คํŒจ ์‹œ ํ”„๋ฆฌ๋ทฐ ์ฆ‰์‹œ ์ œ๊ฑฐ
  }
};

5. ์†”๋ฃจ์…˜์ง€ (Solution)

React Quill ๊ธฐ๋ฐ˜ ์ „๋ฌธ๊ฐ€ ์†”๋ฃจ์…˜ ์ž‘์„ฑ ์—๋””ํ„ฐ

  • ํ—ค์–ด / ํŒจ์…˜ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ HTML์„ Quill value๋กœ ์ฃผ์ž…ํ•ด ์ „๋ฌธ๊ฐ€๊ฐ€ ํ•ญ๋ชฉ๋ณ„๋กœ ๋ฐ”๋กœ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ์ด๋ฏธ์ง€ ์‚ฝ์ž… ์‹œ S3 Presigned URL๋กœ ์ง์ ‘ ์—…๋กœ๋“œ ํ›„ ์ด๋ฏธ์ง€ URL์„ Quill์— ์‚ฝ์ž…
  • ๊ณ ๊ฐ์˜ ๊ณ ๋ฏผ์ง€(์„ค๋ฌธ ์‘๋‹ต)๋ฅผ ์ƒ๋‹จ์— ํ•จ๊ป˜ ๋ Œ๋”๋งํ•ด ์ „๋ฌธ๊ฐ€๊ฐ€ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ณด๋ฉฐ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ๊ณ ๊ฐ์€ /consultations/:consultationId/solution์—์„œ ์†”๋ฃจ์…˜์ง€ ์—ด๋žŒ ๋ฐ ๋‚ด ์†”๋ฃจ์…˜ ๋ชฉ๋ก ์กฐํšŒ

6. ๋งˆ์ดํŽ˜์ด์ง€ (MyPage)

์ผ๋ฐ˜ ์‚ฌ์šฉ์ž / ์ „๋ฌธ๊ฐ€ ์—ญํ• ๋ณ„๋กœ ๋ถ„๊ธฐ๋˜๋Š” ๋งˆ์ดํŽ˜์ด์ง€

ํŽ˜์ด์ง€ ๋‚ด์šฉ
๋งˆ์ดํŽ˜์ด์ง€ ๋ฉ”์ธ ์œ ์ € ํƒ€์ž… ๊ฐ์ง€ ํ›„ ์ผ๋ฐ˜ / ์ „๋ฌธ๊ฐ€ ๋ฉ”๋‰ด ๋ถ„๊ธฐ ๋ Œ๋”๋ง
์ฐœ ๋ชฉ๋ก ์ข‹์•„์š”ํ•œ ์ „๋ฌธ๊ฐ€ ๋ชฉ๋ก ์กฐํšŒ
ํฌ์ธํŠธ ๋‚ด์—ญ ํฌ์ธํŠธ ์ ๋ฆฝ / ์‚ฌ์šฉ ๋‚ด์—ญ
๊ฒฐ์ œ ๋‚ด์—ญ ๊ฒฐ์ œ ๋‚ด์—ญ ๋ชฉ๋ก
์˜ˆ์•ฝ ๋‚ด์—ญ ์˜ˆ์•ฝ ์ƒํƒœ๋ณ„ ๋ชฉ๋ก ์กฐํšŒ
๋‚ด ๋ฆฌ๋ทฐ ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ
๋ฆฌ๋ทฐ ์ž‘์„ฑ ์ƒ๋‹ด ์™„๋ฃŒ ๊ฑด์— ๋Œ€ํ•œ ๋ณ„์  + ํ…์ŠคํŠธ ๋ฆฌ๋ทฐ ์ž‘์„ฑ
์ „๋ฌธ๊ฐ€ ์ž๊ธฐ์†Œ๊ฐœ React Quill๋กœ ์ž๊ธฐ์†Œ๊ฐœ ํŽธ์ง‘
์ „๋ฌธ๊ฐ€ ํฌํŠธํด๋ฆฌ์˜ค ํฌํŠธํด๋ฆฌ์˜ค ๋ชฉ๋ก ๊ด€๋ฆฌ (์œ„ 4๋ฒˆ ์ฐธ๊ณ )
์ „๋ฌธ๊ฐ€ ์ผ์ • ์„ค์ • ์ƒ๋‹ด ์œ ํ˜• / ๊ฐ€๊ฒฉ ์„ค์ • (์œ„ 4๋ฒˆ ์ฐธ๊ณ )
์ „๋ฌธ๊ฐ€ ์ƒ๋‹ด ๋‚ด์—ญ ์ง„ํ–‰ํ•œ ์ƒ๋‹ด ์ด๋ ฅ ์กฐํšŒ

๊ธฐ์ˆ ์  ๋„์ „ & ํ•ด๊ฒฐ

๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋™์‹œ์— WebSocket์„ ๋งˆ์šดํŠธํ•˜๋ฉด ์ค‘๋ณต ์—ฐ๊ฒฐ ๋ฐœ์ƒ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ ์‹ฑ๊ธ€ํ†ค + ๋งˆ์šดํŠธ ์นด์šดํ„ฐ๋กœ ์—ฐ๊ฒฐ 1๊ฐœ ์œ ์ง€
์žฌ์—ฐ๊ฒฐ ํ›„ ๊ตฌ๋…์ด ์‚ฌ๋ผ์ ธ ๋ฉ”์‹œ์ง€ ๋ฏธ์ˆ˜์‹  onConnect์—์„œ ํ™œ์„ฑ ๊ตฌ๋… ๋ชฉ๋ก์„ flushํ•ด ์ž๋™ ์žฌ๊ตฌ๋…
์†Œ์ผ“ ์ฝœ๋ฐฑ ๋‚ด roomId๊ฐ€ stale ํด๋กœ์ €๋กœ ์ด์ „ ๊ฐ’ ์ฐธ์กฐ useRef๋กœ ์ตœ์‹  ๊ฐ’ ๋™๊ธฐํ™”, functional update๋กœ prev ์‚ฌ์šฉ
์„œ๋ฒ„ ์—์ฝ” ๋ฉ”์‹œ์ง€๋กœ ์ธํ•œ ๋ฉ”์‹œ์ง€ ์ค‘๋ณต ๋ Œ๋”๋ง messageId ๊ธฐ๋ฐ˜ ์ค‘๋ณต ์ฒดํฌ
๋‹จ๊ณ„ ์ „ํ™˜ ์‹œ ์Šคํฌ๋กค ์œ„์น˜๊ฐ€ ์ด์ „ ๋‹จ๊ณ„์˜ ์œ„์น˜์— ๋จธ๋ฌด๋ฆ„ useLayoutEffect + requestAnimationFrame์œผ๋กœ ๋ Œ๋” ํ›„ ์Šคํฌ๋กค ํƒ‘ ๋ณด์žฅ
S3 ์—…๋กœ๋“œ ์‹คํŒจ ์‹œ ํ”„๋ฆฌ๋ทฐ๊ฐ€ ๋‚จ์•„ ์—…๋กœ๋“œ ์™„๋ฃŒ๋กœ ์˜ค์ธ ์—…๋กœ๋“œ ์‹คํŒจ ์ฆ‰์‹œ URL.revokeObjectURL()๋กœ ํ”„๋ฆฌ๋ทฐ ์ œ๊ฑฐ, S3 ํ‚ค๋Š” ์„ฑ๊ณต ์‹œ์—๋งŒ Zustand์— ์ €์žฅ
์‚ฌ์ง„์„ ๋น ๋ฅด๊ฒŒ ๊ต์ฒดํ•˜๋ฉด ์ด์ „ ์—…๋กœ๋“œ ๊ฒฐ๊ณผ๊ฐ€ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•ด ์ž˜๋ชป๋œ ํ‚ค ์ €์žฅ seqRef ์นด์šดํ„ฐ๋กœ stale ์‘๋‹ต ๊ฐ์ง€ ํ›„ ๋ฌด์‹œ, AbortController๋กœ ์ด์ „ ์š”์ฒญ ์ฆ‰์‹œ ์ทจ์†Œ
์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ Object URL์ด ๋ฉ”๋ชจ๋ฆฌ์— ๋ˆ„์  objectUrlsRef(Set)๋กœ ์ƒ์„ฑํ•œ ๋ชจ๋“  URL ์ถ”์  โ†’ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ผ๊ด„ revoke

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 98.9%
  • Other 1.1%