全文為AI參考實際開發狀況進行記錄並撰寫。
問題現象
最近在開發一個 Next.js 聊天應用時,遇到了一個惱人的 bug:使用注音輸入法打字時,按下 Enter 鍵選字會同時觸發訊息送出。這對台灣、中國等使用 IME(輸入法編輯器)的用戶來說是災難性的體驗。
想像一下:你正在打「你好」,輸入 ㄋㄧˇ 後按 Enter 選字,結果一個「ㄋㄧˇ」就被送出去了。
問題根源
問題出在 handleKeyDown 事件處理器:
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSubmit(e);
}
};
這段程式碼只檢查了 Enter 鍵和 Shift 鍵,卻忽略了 IME 正在組字的狀態。當用戶使用注音輸入法時,按下 Enter 是為了確認候選字,而不是送出訊息。
解決方案
1. 認識 Composition Events
瀏覽器提供了 Composition Events 來處理 IME 輸入:
compositionstart:IME 開始組字時觸發compositionupdate:組字內容更新時觸發compositionend:組字結束時觸發
2. 使用 isComposing 屬性
KeyboardEvent 有一個 isComposing 屬性,當 IME 正在組字時會是 true。
3. 完整解決方案
export function ChatInput({ onSend, disabled = false }) {
const [input, setInput] = useState("");
const [isComposing, setIsComposing] = useState(false);
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
// 檢查 IME 組字狀態,避免選字時誤送出
if (
e.key === "Enter" &&
!e.shiftKey &&
!isComposing &&
!e.nativeEvent.isComposing
) {
e.preventDefault();
handleSubmit(e);
}
};
const handleCompositionStart = () => setIsComposing(true);
const handleCompositionEnd = () => setIsComposing(false);
return (
<Textarea
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
/>
);
}
為什麼需要雙重檢查?
你可能注意到我同時檢查了 isComposing 狀態和 e.nativeEvent.isComposing:
!isComposing && !e.nativeEvent.isComposing
這是因為:
-
Safari 的事件順序不同:Safari 會在
keydown事件之後才觸發compositionend,導致用 React 狀態追蹤時可能出現時序問題。 -
跨瀏覽器相容性:
e.nativeEvent.isComposing是原生屬性,部分舊瀏覽器可能不支援,用 state 追蹤作為備援。 -
React 的合成事件:React 的
KeyboardEvent是合成事件,需要透過nativeEvent存取原生屬性。
適用場景
這個問題不只出現在注音輸入法,所有 IME 都會受影響:
- 注音輸入法(台灣)
- 拼音輸入法(中國)
- 日文輸入法(日本)
- 韓文輸入法(韓國)
結語
這是一個容易被忽略但影響廣泛的問題。如果你的應用有亞洲用戶,務必在所有鍵盤事件處理中加入 IME 狀態檢查。