You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 17:45:05