Frontend/React

[React] onKeyDown, onKeyUp ์ด๋ฒคํŠธ, ํ•œ๊ธ€ ์ž…๋ ฅ ์‹œ ํ•จ์ˆ˜ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ

_์„ฑํ˜ธ_ 2023. 6. 21. 16:13
728x90
๋ฐ˜์‘ํ˜•

๐Ÿ”ฅ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ

ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์—”ํ„ฐ(Enter) ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ชฉ๋ก์— ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋˜๊ณ  input ์ฐฝ์€ ๋น„์›Œ์ง€๋„๋ก ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ์™ ์ง€ ๋ชจ๋ฅด๊ฒŒ ํ•จ์ˆ˜๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋ฉด์„œ ๊ธฐ์กด ํ…์ŠคํŠธ์˜ ๋งˆ์ง€๋ง‰ ๊ธ€์ž๊ฐ€ ๋ชฉ๋ก์— ์ถ”๊ฐ€๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹คโ—๏ธ

 

์›์ธ ๋ถ„์„

  • ๊ฒ€์ƒ‰ํ•ด๋ณธ ๊ฒฐ๊ณผ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ•œ๊ธ€์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. (์˜์–ด๋กœ ์ž…๋ ฅํ•˜๋ฉด ํ‚ค ์ด๋ฒคํŠธ๊ฐ€ ์ค‘๋ณต์œผ๋กœ ๋ฐœ์ƒํ•˜์ง€ โŒ)
  • ๊ตฌ์ฒด์ ์œผ๋กœ ์œ„ GIF๋ฅผ ๋ณด๋ฉด ํ•œ๊ธ€ ์ž…๋ ฅ ์‹œ ์ž…๋ ฅ ์ค‘์ธ ๊ธ€์ž ์•„๋ž˜ ๊ฒ€์€ ๋ฐ‘์ค„์ด ์ƒ๊ธฐ๋Š”๋ฐ ํ•ด๋‹น ๋ฐ‘์ค„์ด ์žˆ๋Š” ์ƒํ™ฉ์—์„œ ํ‚ค ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. 
  • ํ•œ๊ธ€์˜ ๊ฒฝ์šฐ ์ž์Œ๊ณผ ๋ชจ์Œ์˜ ์กฐํ•ฉ์œผ๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” ์กฐํ•ฉ ๋ฌธ์ž์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ธ€์ž๊ฐ€ ์กฐํ•ฉ ์ค‘์ธ์ง€ ์กฐํ•ฉ์ด ๋๋‚œ ์ƒํƒœ์ธ์ง€๋ฅผ ์•Œ ์ˆ˜ ์—†์–ด ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ์ด๋‹ค.

 

๐Ÿงฏ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

nativeEvent ๋ฐ React์—์„œ ์ œ๊ณตํ•˜๋Š” CompositionEvent๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฌธ์ œ ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

1๏ธโƒฃ nativeEvent ์‚ฌ์šฉ

import React, { useRef, useState } from 'react';

export default function ToDoList() {
  const [todos, setTodos] = useState(initialValue);

  const textRef = useRef('');

  const handleClick = () => {
    handleAdd();
  };

  // ์—”ํ„ฐ(Enter) ํ‚ค๋ฅผ ๋ˆ„๋ฆ„๊ณผ ๋™์‹œ์— isComposing ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์ด false์ธ ๊ฒฝ์šฐ์—๋งŒ ํ•จ์ˆ˜ ์‹คํ–‰
  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && e.nativeEvent.isComposing === false) handleAdd();
  };

  const handleAdd = () => {
    const text = textRef.current.value.trim();

    if (text === '') {
      alert('๊ณต๋ฐฑ์„ ์ œ์™ธํ•˜๊ณ  ํ•œ ๊ธ€์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.');
      textRef.current.value = '';
      return;
    }

    setTodos([...todos, { id: new Date(), text, status: 'active' }]);
    textRef.current.value = '';
  };

  return (
    <section>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      <input
        type='text'
        ref={textRef}
        placeholder='Add Todo'
        onKeyDown={handleKeyDown}
      />
      <button onClick={handleClick}>Add</button>
    </section>
  );
}

const initialValue = [
  { id: '123', text: '์žฅ๋ณด๊ธฐ', status: 'active' },
  { id: '124', text: '๊ณต๋ถ€ํ•˜๊ธฐ', status: 'active' },
];

 

2๏ธโƒฃ CompositionEvent ์‚ฌ์šฉ

CompositionEvent ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜(onCompositionStart, onCompositionEnd)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ isComposing ์ƒํƒœ๋ฅผ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•จ์œผ๋กœ์จ ์ปดํฌ์ง€์…˜ ์„ธ์…˜ ๋‚ด์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค. 

import React, { useRef, useState } from 'react';

export default function ToDoList() {
  const [todos, setTodos] = useState(initialValue);
  // isComposing ์ƒํƒœ๋ฅผ ๋ณ„๋„๋กœ ๊ด€๋ฆฌ
  const [isComposing, setIsComposing] = useState(false);

  const textRef = useRef('');

  const handleClick = () => {
    handleAdd();
  };

  // ์—”ํ„ฐ(Enter) ํ‚ค๋ฅผ ๋ˆ„๋ฆ„๊ณผ ๋™์‹œ์— isComposing ์ƒํƒœ๊ฐ€ false์ธ ๊ฒฝ์šฐ์—๋งŒ ํ•จ์ˆ˜ ์‹คํ–‰
  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && !isComposing) handleAdd();
  };

  const handleAdd = () => {
    const text = textRef.current.value.trim();

    if (text === '') {
      alert('๊ณต๋ฐฑ์„ ์ œ์™ธํ•˜๊ณ  ํ•œ ๊ธ€์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.');
      textRef.current.value = '';
      return;
    }

    setTodos([...todos, { id: new Date(), text, status: 'active' }]);
    textRef.current.value = '';
  };

  return (
    <section>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      <input
        type='text'
        ref={textRef}
        placeholder='Add Todo'
        onKeyDown={handleKeyDown}
        onCompositionStart={() => setIsComposing(true)}
        onCompositionEnd={() => setIsComposing(false)}
      />
      <button onClick={handleClick}>Add</button>
    </section>
  );
}

const initialValue = [
  { id: '123', text: '์žฅ๋ณด๊ธฐ', status: 'active' },
  { id: '124', text: '๊ณต๋ถ€ํ•˜๊ธฐ', status: 'active' },
];

 

๐Ÿ“š ์ฐธ๊ณ ํ•œ ์‚ฌ์ดํŠธ

 

Common components (e.g. <div>) – React

The library for web and native user interfaces

react.dev

 

KeyboardEvent: isComposing property - Web APIs | MDN

The KeyboardEvent.isComposing read-only property returns a boolean value indicating if the event is fired within a composition session, i.e. after compositionstart and before compositionend.

developer.mozilla.org

 

[React] onKeyDown ์ด๋ฒคํŠธ ์ค‘๋ณต์œผ๋กœ 2๋ฒˆ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

# 1. ๋‚ด๊ฐ€ ๊ฒช์€ ๋ฌธ์ œ ์•„๋ž˜ ์˜์ƒ์—์„œ ์ฒ˜๋Ÿผ ์•„๋ž˜ ๋ฐฉํ–ฅํ‚ค๋ฅผ ํ•œ ๋ฒˆ ๋ˆŒ๋ €์„ ๋•Œ ๋‡Œ์•”-> ๋‡Œ๊ฒฝ์ƒ‰ ์ˆœ์„œ๋Œ€๋กœ๊ฐ€ ์•„๋‹Œ, ๋‡Œ์•” -> ๋‡Œ์ถœํ˜ˆ๋กœ ์ด๋™๋˜๋Š” ํ˜„์ƒ์ด๋‹ค. ๋งˆ์น˜ 2๋ฒˆ ๋ˆ„๋ฅธ ๊ฒƒ ์ฒ˜๋Ÿผ ์ค‘๋ณต๋œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ๋˜๋Š”

always-hyeppy.tistory.com