React中TextField输入单个字符后失去焦点问题排查
React TextField输入单个字符后失焦的解决方案
问题原因
输入后立即失焦的核心问题是你在Text组件内部定义了Multiline、Password、TextInput这些子组件。每次父组件触发重渲染(比如onChange更新状态时),这些内部组件都会被重新创建,React会判定为全新组件,卸载旧实例并挂载新实例,导致焦点丢失。
修复方案
把内部子组件改为普通渲染函数,或者移到父组件外部,避免每次重渲染时重建组件类型:
修改后的完整代码
import * as React from "react"; import { styled } from "@mui/material/styles"; import Box from "@mui/material/Box"; import { TextField, Input, inputBaseClasses, FormControl, InputLabel } from "@mui/material"; import { BiShow, BiHide } from "react-icons/bi"; import { useState } from "react"; const ValidationTextField = styled(TextField)({ "& input:valid + fieldset": { borderColor: "green", borderWidth: 2 }, "& input:invalid + fieldset": { borderColor: "red", borderWidth: 2 }, "&:hover:valid + fieldset": { borderColor: "yellow" }, "& input:valid:focus + fieldset": { borderLeftWidth: 6, borderColor: "purple", color: "pink", padding: "4px !important" }, "& .MuiOutlinedInput-root": { "&:hover fieldset": { borderColor: "yellow", color: "orange" } } }); const StyledInput = styled(Input)({ borderRadius: 4, border: "2px solid blue", padding: 4, [`&.${inputBaseClasses.multiline}`]: { height: "auto", border: "2px solid red" } }); // 移到外部作为独立组件,避免重复创建 const PassWordIcon = ({ showPass, setShowPass }) => showPass ? ( <BiHide size={40} onClick={() => setShowPass(false)} /> ) : ( <BiShow size={40} onClick={() => setShowPass(true)} /> ); export default function Text({ errors, label, rows, type, defaultValue, required, onChange, setValue, name }) { const [showPass, setShowPass] = useState(false); // 改为普通渲染函数,而非React组件 const renderMultiline = () => { return ( <FormControl variant="outlined"> <InputLabel>{label}</InputLabel> <StyledInput sx={{ "&:hover": { border: "2px solid yellow" }, "&.Mui-focused": { borderColor: "purple", color: "pink", padding: "4px !important" } }} onChange={(e) => onChange(e.target.value)} disableUnderline multiline rows={rows} /> </FormControl> ); }; const renderPassword = () => { return ( <Box> <ValidationTextField label={label} required={required} error={errors} onChange={(e) => onChange(e.target.value)} type={showPass ? "text" : "password"} sx={{ input: { color: "red", "&::placeholder": { color: "darkgreen" } }, label: { color: "pink" } }} variant="outlined" defaultValue={defaultValue} /> <PassWordIcon showPass={showPass} setShowPass={setShowPass} /> </Box> ); }; const renderTextInput = () => { return ( <ValidationTextField label={label} required={required} type={type} onChange={(e) => onChange(e.target.value)} sx={{ input: { color: "red", "&::placeholder": { color: "darkgreen" } }, label: { color: "pink" } }} variant="outlined" defaultValue={defaultValue} /> ); }; const inputType = (type) => { switch (type) { case "multiline": return renderMultiline(); case "password": return renderPassword(); default: return renderTextInput(); } }; return <Box>{inputType(type)}</Box>; }
关键修改点
- 将
PassWordIcon移到Text组件外部,作为独立组件复用,避免每次重渲染被重建 - 将原内部组件改为普通渲染函数(命名为
renderXXX),不再以组件形式定义,确保每次重渲染时仅执行函数返回JSX,不创建新组件类型 - 移除了样式中无效的嵌套规则(比如
&::placeholder内的&:hover fieldset,属于无效CSS嵌套)
内容的提问来源于stack exchange,提问作者TWOcvfan
相关产品推荐
相关产品推荐

