NextJS 15中触发API调用后保持虚拟键盘常驻的方法
问题
我正在开发一款类WhatsApp的即时通讯风格Web应用,用户点击SEND按钮发送文本时,虚拟键盘会消失,体验不佳,希望能让键盘保持弹出状态。但SEND按钮触发的API调用业务逻辑会导致输入框失去焦点。
我已编写最简复现代码:当使用await convertText(userText)调用模拟API时,即便尝试立即让输入框重新获取焦点,虚拟键盘仍会消失;而使用硬编码结果时,键盘能正常保持弹出。
请问如何在handleSend()方法执行API调用期间始终保持虚拟键盘常驻?技术栈为NextJS 15。
复现代码:
'use client'; import { useRef, useState } from "react"; // 模拟API调用转换文本 async function convertText(text: string): Promise<string> { return "CONVERTED:" + text; } export default function Test() { const [userText, setUserText] = useState(''); const [convertedText, setConvertedText] = useState(''); const inputRef = useRef<HTMLInputElement | null>(null); /* * 调用convertText()时,用户每次点击SEND虚拟键盘都会消失,这在通讯类App里体验很差。 * 如果去掉await convertText()改用硬编码结果,onMouseDown和inputRef的焦点设置能正常工作,键盘不会消失。 */ const handleSend = async () => { const result = await convertText(userText); // 提交后键盘消失 // const result = "Virtual keyboard stays up"; // 提交后键盘保持弹出 setConvertedText(result); inputRef.current?.focus(); }; return ( <div> {convertedText} <div> <input ref={inputRef} type="text" value={userText} onChange={(e) => setUserText(e.target.value)} placeholder="Enter text" /> <button onClick={handleSend} onMouseDown={(e) => { e.preventDefault(); }} > SEND </button> </div> </div> ); }
解决方案
1. 异步延迟恢复焦点
API调用的await会让浏览器进入任务队列等待,同步调用focus()可能赶不上浏览器的焦点重置时机。用setTimeout把焦点恢复放到下一个任务队列执行,确保浏览器完成当前渲染后再聚焦:
const handleSend = async () => { const result = await convertText(userText); setConvertedText(result); // 延迟0ms让浏览器完成当前任务,再恢复焦点 setTimeout(() => { inputRef.current?.focus(); }, 0); };
2. 补充触摸事件阻止默认行为
移动端虚拟键盘的触发逻辑和触摸事件强关联,当前只处理了onMouseDown,补充onTouchStart阻止默认行为,避免触摸按钮时触发焦点转移:
<button onClick={handleSend} onMouseDown={(e) => e.preventDefault()} onTouchStart={(e) => e.preventDefault()} > SEND </button>
3. 用useEffect监听状态变化恢复焦点
当API返回结果更新convertedText后,通过useEffect触发焦点恢复,确保状态更新完成后再执行聚焦操作:
import { useRef, useState, useEffect } from "react"; // ... 其他代码不变 // 监听转换结果变化,自动恢复输入框焦点 useEffect(() => { inputRef.current?.focus(); }, [convertedText]); const handleSend = async () => { const result = await convertText(userText); setConvertedText(result); };
4. API调用前提前锁定焦点
如果API调用耗时较长,在调用前先强制输入框聚焦,防止中间过程因意外操作丢失焦点:
const handleSend = async () => { // 调用API前先锁定焦点 inputRef.current?.focus(); const result = await convertText(userText); setConvertedText(result); setTimeout(() => { inputRef.current?.focus(); }, 0); };
内容的提问来源于stack exchange,提问作者James
相关产品推荐
相关产品推荐

