-
Notifications
You must be signed in to change notification settings - Fork 7
๐ชต 7. ํธ๋ฆฌํ ๊ฒ์ ํ๊ฒฝ์ ์ํ ๋จ์ถํค ์ ์ฉ๊ธฐ: custom hook์ ํตํ ์ฌ์ฌ์ฉ
๊ฒ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ธฐ ์ํด ๋จ์ถํค ์์คํ ์ ๋์ ํ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๊ฐ ๋ณด๋ค ํจ์จ์ ์ผ๋ก ๊ฒ์์ ํ๋ ์ดํ ์ ์๋๋ก ํ์ต๋๋ค.
๋จ์ถํค ์์คํ ์ ํฌ๊ฒ ์ธ ๊ฐ์ง ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ์ฒซ์งธ, ๋ชจ๋ ๋จ์ถํค๋ 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;
๋์งธ, 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,
},
]);
์ ์ฒด์ ์ผ๋ก ์ด ๋จ์ถํค ์์คํ ์ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๊ณ ๋ คํ์ฌ ์ค๊ณ๋์์ผ๋ฉฐ, ์ฌ์ฉ์ ๊ฒฝํ์ ์ต์ ํํ๋ ๋ฐ ์ค์ ์ ๋์์ต๋๋ค.
- 1. ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ๋ฐ ํ๋ก์ ํธ ๋ฌธ์ํ
- 2. ์ค์๊ฐ ํต์
- 3. ์ธํ๋ผ ๋ฐ CI/CD
- 4. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Canvas ๊ตฌํํ๊ธฐ
- 5. ์บ๋ฒ์ค ๋๊ธฐํ๋ฅผ ์ํ ์์ CRDT ๊ตฌํ๊ธฐ
-
6. ์ปดํฌ๋ํธ ํจํด๋ถํฐ ์น์์ผ๊น์ง, ํจ์จ์ ์ธ FE ์ค๊ณ
- ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? + Headless Pattern
- ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง: Tailwind CSS + cn.ts
- Tailwind CSS๋ก ๋์์ธ ์์คํ ๋ฐ UI ์ปดํฌ๋ํธ ์ธํ
- ์น์์ผ ํด๋ผ์ด์ธํธ ๊ตฌํ๊ธฐ: React ํ๊ฒฝ์์ ํจ์จ์ ์ธ ์น์์ผ ์ํคํ ์ฒ
- ์น์์ผ ํด๋ผ์ด์ธํธ ์ฝ๋ ๋ถ์ ๋ฐ ๊ณต์
- 7. ํธ๋ฌ๋ธ ์ํ ๋ฐ ์ฑ๋ฅ/UX ๊ฐ์
- 1์ฃผ์ฐจ ๊ธฐ์ ๊ณต์
- 2์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 3์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 4์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 5์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- WEEK 06 ์ฃผ๊ฐ ๊ณํ
- WEEK 06 ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- WEEK 06 ์ฃผ๊ฐ ํ๊ณ