input에서 입력한 문자열 값을 배열로 관리하여 화면에 렌더링하는 기능을 구현 중이었다. input의 onKeyDown 속성을 사용해 엔터 키 입력을 감지하여 처리하고 있다. 코드를 살펴보자.
useKeyword 훅
import { Dispatch, SetStateAction, useEffect, useState } from "react";
interface IProps {
keywordList: string[];
setKeywordList: Dispatch<SetStateAction<string[]>>;
}
function useKeyword({ keywordList, setKeywordList }: IProps) {
const [value, setValue] = useState("");
const [isDisabled, setIsDisabled] = useState(false);
useEffect(() => {
if (keywordList.length === 10 || !value) setIsDisabled(true);
else setIsDisabled(false);
}, [keywordList.length, value]);
const addKeyword = () => {
if (keywordList.length === 10) return;
setKeywordList((prev) => [...prev, value]);
setValue("");
};
// 엔터키 입력시 addKeyword 함수를 호출
const handleKeyPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
addKeyword();
}
};
const deleteKeyword = (index: number) => {
const filteredKeywordList = keywordList.filter((_, i) => index !== i);
setKeywordList(filteredKeywordList);
};
return {
value,
setValue,
addKeyword,
isDisabled,
handleKeyPressEnter,
deleteKeyword,
setIsComposing,
};
}
export default useKeyword;
Keyword 컴포넌트
import styles from "./KeywordSection.module.scss";
import AddProductDetailSection from "../addProductDetailSection/AddProductDetailSection";
import { Dispatch, SetStateAction } from "react";
import useKeyword from "../../../hooks/useKeyword/useKeyword";
interface IProps {
keywordList: string[];
setKeywordList: Dispatch<SetStateAction<string[]>>;
}
function KeywordSection({ keywordList, setKeywordList }: IProps): JSX.Element {
const {
value,
setValue,
handleKeyPressEnter,
addKeyword,
deleteKeyword,
isDisabled,
setIsComposing,
} = useKeyword({
keywordList,
setKeywordList,
});
return (
<AddProductDetailSection
label="키워드 입력"
limit={`(${keywordList.length}/10)`}
>
<div className={styles.container}>
<section>
<input
type="text"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
// onKeyDown 부분
onKeyDown={(e) => {
handleKeyPressEnter(e);
}}
placeholder="키워드 입력"
className={styles.keyword}
/>
<button
onClick={addKeyword}
disabled={isDisabled}
className={styles.button}
>
추가
</button>
</section>
<ul className={styles.user_keywords}>
{keywordList.map((keyword, i) => (
<li
key={i}
onClick={() => {
deleteKeyword(i);
}}
className={styles.user_keyword}
>
<img src="/images/x.png" alt="x" className={styles.x_icon} />
{keyword}
</li>
))}
</ul>
</div>
</AddProductDetailSection>
);
}
export default KeywordSection;
영어는 별 문제가 없었는데 한글로 입력한 키워드들은 두번씩 배열에 출력되는 이슈가 발생했다.
ex) 안녕 => 안녕, 녕
원인은 IME 때문이었다.
IME(Input Method Editor)는 키보드 입력을 통해 다양한 문자 체계(예: 한글, 중국어, 일본어 등)를 입력할 수 있게 해주는 소프트웨어 도구이다. 특히 알파벳을 기반으로 하지 않는 언어를 입력할 때 필수적인 도구로, 사용자가 여러 키 입력을 조합하여 복잡한 문자와 기호를 생성할 수 있게 한다.
IME는 사용자가 키보드로 입력하는 단일 문자 또는 문자 시퀀스를 받아서, 그것을 해당 언어의 문자나 단어로 변환한다. 예를 들어, 한글 입력 시에는 자음과 모음을 각각 입력하고, 이를 조합하여 하나의 글자를 만든다. 이 과정에서 사용자는 여러 입력 조합을 시도할 수 있으며, IME는 이를 실시간으로 처리하여 올바른 문자를 생성한다.
사용자가 IME를 사용하여 문자를 입력할 때, 입력이 완료되었는지 아닌지를 정확히 감지하는 것이 중요하다. compositionstart와 compositionend 이벤트를 통해 입력 시작과 완료 시점을 감지할 수 있다.
IME를 사용하는 동안 중간 단계의 입력이 있을 수 있으며, 이러한 입력은 최종 입력 값에 포함되지 않아야 한다. 예를 들어, 'Enter' 키를 눌렀을 때 아직 조합이 완료되지 않은 입력을 처리하려고 시도하면 예상치 못한 결과가 발생할 수 있다.
useKeyword 훅 내에 isComposing 상태를 설정하고, setIsComposing을 return하여 input의 onCompositionStart와 onCompositionEnd 프로퍼티에 할당해주는 것으로 문제를 해결할 수 있었다.
useKeyword 훅 내부
// useKeyword에서 관리중인 isComposing 상태
const [isComposing, setIsComposing] = useState(false);
const handleKeyPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
// isComposing 상태면 return하여 함수 종료
if (isComposing) return;
if (e.key === "Enter") {
addKeyword();
}
};
Keyword 컴포넌트 내부
<input
type="text"
value={value}
// 추가된 프로퍼티
onCompositionStart={() => setIsComposing(true)}
onCompositionEnd={() => setIsComposing(false)}
onChange={(e) => {
setValue(e.target.value);
}}
onKeyDown={(e) => {
handleKeyPressEnter(e);
}}
placeholder="키워드 입력"
className={styles.keyword}
/>
웹 애플리케이션을 개발할 때 IME의 존재와 작동 방식을 이해하는 것은 글로벌 사용자를 위한 접근성과 사용자 경험(UX)을 향상시키는 데 중요하다. 사용자가 자신의 언어로 자연스럽게 입력할 수 있도록 지원함으로써, 애플리케이션의 접근성과 만족도를 높일 수 있다는 것을 깨달았다.