Skip to content

๐Ÿชต 7. ํŽธ๋ฆฌํ•œ ๊ฒŒ์ž„ ํ™˜๊ฒฝ์„ ์œ„ํ•œ ๋‹จ์ถ•ํ‚ค ์ ์šฉ๊ธฐ: custom hook์„ ํ†ตํ•œ ์žฌ์‚ฌ์šฉ

choiseona edited this page Dec 5, 2024 · 1 revision

๋‹จ์ถ•ํ‚ค ์‹œ์Šคํ…œ ์„ค๊ณ„ ๋ฐ ๊ตฌํ˜„

๊ฒŒ์ž„์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋‹จ์ถ•ํ‚ค ์‹œ์Šคํ…œ์„ ๋„์ž…ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๋‹ค ํšจ์œจ์ ์œผ๋กœ ๊ฒŒ์ž„์„ ํ”Œ๋ ˆ์ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ถ•ํ‚ค ์ƒ์ˆ˜ํ™”

๋‹จ์ถ•ํ‚ค ์‹œ์Šคํ…œ์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€ ๋ถ€๋ถ„์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒซ์งธ, ๋ชจ๋“  ๋‹จ์ถ•ํ‚ค๋Š” SHORTCUT_KEYS ์ƒ์ˆ˜์—์„œ ์ค‘์•™ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ๋‹จ์ถ•ํ‚ค๋Š” ์ฃผ ํ‚ค(key), ๋Œ€์ฒด ํ‚ค(alternativeKeys), ๊ทธ๋ฆฌ๊ณ  ์„ค๋ช…(description)์„ ํฌํ•จํ•˜๋ฉฐ, ๊ฒŒ์ž„ ์„ค์ •๊ณผ ๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ๊ด€๋ จ ๊ธฐ๋Šฅ๋“ค์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

export const SHORTCUT_KEYS = {
  // ์„ค์ • ๊ด€๋ จ
  DROPDOWN_TOTAL_ROUNDS: {
    key: '1',
    alternativeKeys: ['1', '!'],
    description: '๋ผ์šด๋“œ ์ˆ˜ ์„ค์ •',
  },
  DROPDOWN_MAX_PLAYERS: {
    key: '2',
    alternativeKeys: ['2', '@'],
    description: 'ํ”Œ๋ ˆ์ด์–ด ์ˆ˜ ์„ค์ •',
  },
  DROPDOWN_DRAW_TIME: {
    key: '3',
    alternativeKeys: ['3', '#'],
    description: '์ œํ•œ์‹œ๊ฐ„ ์„ค์ •',
  },
  // ๊ฒŒ์ž„ ๊ด€๋ จ
  CHAT: {
    key: 'Enter',
    alternativeKeys: null,
    description: '์ฑ„ํŒ…',
  },
  GAME_START: {
    key: 's',
    alternativeKeys: ['s', 'S', 'ใ„ด'],
    description: '๊ฒŒ์ž„ ์‹œ์ž‘',
  },
  GAME_INVITE: {
    key: 'i',
    alternativeKeys: ['i', 'I', 'ใ…‘'],
    description: '์ดˆ๋Œ€ํ•˜๊ธฐ',
  },
} as const;

๋‹จ์ถ•ํ‚ค custom hook

๋‘˜์งธ, useShortcuts๋ผ๋Š” ์ปค์Šคํ…€ ํ›…์„ ํ†ตํ•ด ๋‹จ์ถ•ํ‚ค ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด ํ›…์€ ์—ฌ๋Ÿฌ ๋‹จ์ถ•ํ‚ค๋ฅผ ๋ฐฐ์—ด ํ˜•ํƒœ๋กœ ๊ด€๋ฆฌํ•˜๋ฉฐ, ๊ฐ ๋‹จ์ถ•ํ‚ค ์„ค์ •์—๋Š” ํ‚ค ์‹๋ณ„์ž, ์‹คํ–‰ํ•  ๋™์ž‘, ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ, ๊ทธ๋ฆฌ๊ณ  ์ž…๋ ฅ ์š”์†Œ ํฌ์ปค์Šค ์ƒํƒœ์—์„œ์˜ ๋™์ž‘ ์—ฌ๋ถ€๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ž…๋ ฅ ์š”์†Œ(input, textarea, select)์—์„œ์˜ ํŠน๋ณ„ ์ฒ˜๋ฆฌ๋ฅผ ํฌํ•จํ•˜๋ฉฐ, canAllowInInput ์˜ต์…˜์„ ํ†ตํ•ด ์ž…๋ ฅ ์ค‘์—๋„ ํŠน์ • ๋‹จ์ถ•ํ‚ค๊ฐ€ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

import { useCallback, useEffect } from 'react';
import { SHORTCUT_KEYS } from '@/constants/shortcutKeys';

interface ShortcutConfig {
  key: keyof typeof SHORTCUT_KEYS | null;
  action: () => void;
  disabled?: boolean;
  canAllowInInput?: boolean;
}

export const useShortcuts = (configs: ShortcutConfig[]) => {
  const handleKeyDown = useCallback(
    (e: KeyboardEvent) => {
      const isInputElement =
        e.target instanceof HTMLInputElement ||
        e.target instanceof HTMLTextAreaElement ||
        e.target instanceof HTMLSelectElement;

      configs.forEach(({ key, action, disabled, canAllowInInput }) => {
        if (!key || disabled || (isInputElement && !canAllowInInput)) return;

        const shortcut = SHORTCUT_KEYS[key];
        const pressedKey = e.key.toLowerCase();
        const isMainKey = pressedKey === shortcut.key.toLowerCase();
        const isAlternativeKey = shortcut.alternativeKeys?.some((key) => key.toLowerCase() === pressedKey);

        if (!isMainKey && !isAlternativeKey) return;

        if (pressedKey !== 'enter' || !isInputElement) {
          e.preventDefault();
        }

        action();
      });
    },
    [configs],
  );

  useEffect(() => {
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [handleKeyDown]);
};

์‚ฌ์šฉ ์˜ˆ์‹œ

์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ์ฑ„ํŒ… ๊ธฐ๋Šฅ์„ ์˜ˆ์‹œ๋กœ ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Enter ํ‚ค๋ฅผ ํ†ตํ•ด ์ฑ„ํŒ…์ฐฝ์˜ ํฌ์ปค์Šค๋ฅผ ์ œ์–ดํ•˜๋Š”๋ฐ, ํฌ์ปค์Šค๋œ ์š”์†Œ๊ฐ€ ์—†์„ ๋•Œ๋Š” ์ž…๋ ฅ์ฐฝ์— ํฌ์ปค์Šค๋ฅผ, ์ž…๋ ฅ์ฐฝ์ด ๋น„์–ด์žˆ๋Š” ์ƒํƒœ์—์„œ Enter๋ฅผ ๋ˆ„๋ฅด๋ฉด ํฌ์ปค์Šค๋ฅผ ํ•ด์ œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ๊ฒŒ์ž„ ์ง„ํ–‰ ์ค‘ ์ฑ„ํŒ… ๊ธฐ๋Šฅ์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค.

  useShortcuts([
    {
      key: 'CHAT',
      action: () => {
        // ํ˜„์žฌ ํฌ์ปค์Šค๋œ ์š”์†Œ๊ฐ€ ์—†๊ฑฐ๋‚˜, ํฌ์ปค์Šค๋œ ์š”์†Œ๊ฐ€ body๋ผ๋ฉด input์„ ํฌ์ปค์‹ฑ
        const isNoFocusedElement = !document.activeElement || document.activeElement === document.body;

        if (isNoFocusedElement) {
          inputRef.current?.focus();
        } else if (inputMessage.trim() === '') {
          inputRef.current?.blur();
        }
      },
      disabled: !inputRef.current,
      canAllowInInput: true,
    },
  ]);

์ „์ฒด์ ์œผ๋กœ ์ด ๋‹จ์ถ•ํ‚ค ์‹œ์Šคํ…œ์€ ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐ ์ค‘์ ์„ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ˜Ž ์›จ๋ฒ ๋ฒ ๋ฒ ๋ฒฑ

๐Ÿ‘ฎ๐Ÿป ํŒ€ ๊ทœ์น™

๐Ÿ’ป ํ”„๋กœ์ ํŠธ

๐Ÿชต ์›จ๋ฒ ๋ฒฑ ๊ธฐ์ˆ ๋กœ๊ทธ

๐Ÿช„ ๋ฐ๋ชจ ๊ณต์œ 

๐Ÿ”„ ์Šคํ”„๋ฆฐํŠธ ๊ธฐ๋ก

๐Ÿ“— ํšŒ์˜๋ก

Clone this wiki locally