React.js输入框每输入一个字符就失焦问题如何解决?
问题根因
你在Auth组件内部嵌套定义了Emailform、Passform、ConfirmPass、AuthType、Login、Signup等子组件。每次输入触发setEmail/setPassword更新Auth的状态时,整个Auth组件会重渲染,所有嵌套定义的子组件都会被重新构造为全新的函数,React渲染时会判定为不同的组件类型,直接销毁旧组件实例、挂载新的实例,输入框自然就会失去焦点,和onChange逻辑本身无关。
修复方案
方案1(推荐,符合React规范)
把所有嵌套在Auth内部的子组件全部移到Auth组件外部定义,需要用到的父组件状态、方法通过props传递即可,同时修复两个附带问题:
Form组件参数调用错误:你在Signup中给Form传的是type属性,但组件内部读取的是props.input,会导致输入框type失效useClickOutside的useEffect缺少空依赖数组,会导致每次渲染都重复绑定/解绑全局点击事件
核心修正代码示例
import style from "./auth.module.css"; import { useEffect, useRef, useState } from "react"; import { useAuthState } from 'react-firebase-hooks/auth'; import { auth, signInWithEmailAndPassword, signInWithGoogle, registerWithEmailAndPassword } from "../../firebase"; import { CSSTransition } from "react-transition-group"; // 所有子组件移到Auth外部定义 const Emailform = ({ value, onChange }) => { return ( <div className={style.formBox}> <label className={style.label}>Email:</label> <form className={style.input}> <input type="email" name="email" required value={value} onChange={onChange} /> </form> </div> ); } const Passform = ({ value, onChange }) => { return ( <div className={style.formBox}> <label className={style.label}>Password:</label> <form className={style.input}> <input type="password" name="password" required value={value} onChange={onChange} /> </form> </div> ); } const ConfirmPass = () => { return ( <div className={style.formBox}> <label className={style.label}>Confirm Password:</label> <form className={style.input}> <input type="password" name="password" required /> </form> </div> ); } const AuthType = ({ goToMenu, setActiveMenu, children }) => { return ( <a className={style.menuItem} onClick={(e) => { e.preventDefault(); goToMenu && setActiveMenu(goToMenu); }} > {children} </a> ); } const Login = ({ activeMenu, domNode, email, password, setEmail, setPassword, setActiveMenu }) => { return ( <CSSTransition in={activeMenu === "main"} unmountOnExit timeout={500}> <div ref={domNode}> <div className={style.login}> <h1 className={style.title}>Clip It</h1> <Emailform value={email} onChange={(e) => setEmail(e.target.value)}/> <Passform value={password} onChange={(e) => setPassword(e.target.value)}/> <div className={style.button}> <input type="submit" value="Login" onClick={() => signInWithEmailAndPassword(email, password)} /> <input type="submit" value="Login with Google" onClick={signInWithGoogle} /> </div> <div className={style.text}> <p className={style.plink}>Forgot Password</p> <div> Need an account? <AuthType goToMenu="Signup" setActiveMenu={setActiveMenu}>click here</AuthType> </div> </div> </div> </div> </CSSTransition> ); } const Signup = ({ activeMenu, domNode, email, setEmail, password, setPassword, setActiveMenu }) => { return ( <CSSTransition in={activeMenu === "Signup"} unmountOnExit timeout={500}> <div ref={domNode}> <div className={style.signUp}> <div className={style.title}> Clip It</div> <Form label="First Name" type="text" /> <Form label="Last Name" type="text" /> <Emailform value={email} onChange={(e) => setEmail(e.target.value)}/> <Form label="Date of Birth" type="date" /> <Passform value={password} onChange={(e) => setPassword(e.target.value)}/> <ConfirmPass /> <div className={style.button}> <input type="submit" value="Sign Up" onClick={registerWithEmailAndPassword} /> </div> <div className={style.text}> have an <AuthType goToMenu="main" setActiveMenu={setActiveMenu}> account</AuthType> </div> </div> </div> </CSSTransition> ); } const Form = (props) => { return ( <div className={style.formBox}> <label className={style.label}>{props.label}:</label> <form className={style.input}> {/* 修正参数读取错误 */} <input type={props.type} name={props.label} required /> </form> </div> ); } let useClickOutside = (handler) => { let domNode = useRef(); // 加空依赖数组 useEffect(() => { let clickListener = (event) => { if (domNode.current && !domNode.current.contains(event.target)) { handler(); } }; document.addEventListener("mousedown", clickListener); return () => { document.removeEventListener("mousedown", clickListener); }; }, []); return domNode; }; const Auth = () => { const [activeMenu, setActiveMenu] = useState("main"); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [user, loading, error] = useAuthState(auth); let domNode = useClickOutside(() => { setActiveMenu("main"); }); return ( <div className={style.container}> {/* 传所需props */} <Login activeMenu={activeMenu} domNode={domNode} email={email} password={password} setEmail={setEmail} setPassword={setPassword} setActiveMenu={setActiveMenu} /> <Signup activeMenu={activeMenu} domNode={domNode} email={email} password={password} setEmail={setEmail} setPassword={setPassword} setActiveMenu={setActiveMenu} /> </div> ); } export default Auth;
方案2(临时快速修复)
如果不想调整组件位置,可以把子组件的调用方式从组件调用改为普通函数调用,比如把<Emailform/>改成{Emailform()},<Login/>改成{Login()},避免React把它们判定为不同的组件类型,但这种方式不推荐长期使用,会影响组件的性能优化空间。
内容的提问来源于stack exchange,提问作者Hitman DD
相关产品推荐
相关产品推荐

