React拆分组件子部分时状态更新致输入框失焦的解决方法
问题根源
你在FormBasic内部定义的Form、Actions属于未做缓存的临时函数组件,每次FormBasic状态更新触发重渲染时,都会生成全新的函数引用。React的Diff算法判定为全新组件,会直接卸载旧组件实例、挂载新实例,因此输入框会出现失焦问题。
解决方案
方案1:直接传递JSX元素(最轻量化,推荐)
直接把Form、Actions从函数组件改为JSX元素,不需要额外引入任何API,修改成本最低:
// 修改后的FormBasic组件 import { useState } from 'react' const FormBasic = ({ children }) => { const [ text, setText ] = useState('') const onSubmit = () => console.log("Submitting"); const onChange = (e) => { console.log("Typing", e.target.value); setText(e.target.value) } // 直接返回JSX元素,而非函数组件 const Form = <textarea rows={4} onChange={onChange} value={text} />; const Actions = <button onClick={onSubmit}>Some Buttons</button>; return children({ Form, Actions }); }; export default FormBasic;
调用时直接渲染元素即可,不需要加括号执行:
export default function App() { return ( <FormBasic> {({ Form, Actions }) => { return (<> <div style={{ border: '1px solid red'}}> {Form} </div> <div style={{ border: '1px solid blue'}}> {Actions} </div> </>) }} </FormBasic> ); }
方案2:缓存组件引用(保留函数调用形式)
如果需要给Form/Actions传递自定义参数,必须保留函数调用形式,可以用useCallback缓存函数引用,保证引用稳定性:
import { useState, useCallback } from 'react' const FormBasic = ({ children }) => { const [ text, setText ] = useState('') const onSubmit = () => console.log("Submitting"); const onChange = (e) => { console.log("Typing", e.target.value); setText(e.target.value) } // 用useCallback缓存组件引用,依赖项仅包含用到的外部变量 const Form = useCallback(() => <textarea rows={4} onChange={onChange} value={text} />, [text]); const Actions = useCallback(() => <button onClick={onSubmit}>Some Buttons</button>, []); return children({ Form, Actions }); }; export default FormBasic;
调用方式保持你原来的写法即可。
其他轻量化拆分思路
你也可以用复合组件模式,把Form、Actions作为FormBasic的静态属性,内部用React内置的Context共享状态(Context是React原生能力,重量极低,符合轻量化要求),调用写法更直观:
const FormContext = React.createContext(); const FormBasic = ({ children }) => { const [ text, setText ] = useState('') const onSubmit = () => console.log("Submitting"); const onChange = (e) => setText(e.target.value); return ( <FormContext.Provider value={{text, onChange, onSubmit}}> {children} </FormContext.Provider> ) } FormBasic.Form = () => { const {text, onChange} = React.useContext(FormContext); return <textarea rows={4} onChange={onChange} value={text} /> } FormBasic.Actions = () => { const {onSubmit} = React.useContext(FormContext); return <button onClick={onSubmit}>Some Buttons</button> } // 调用方式 export default function App() { return ( <FormBasic> <div style={{ border: '1px solid red'}}> <FormBasic.Form /> </div> <div style={{ border: '1px solid blue'}}> <FormBasic.Actions /> </div> </FormBasic> ) }
你原来的写法本身是可行的,只是没有处理组件引用稳定性的问题,按上述方案调整后即可正常使用。
内容的提问来源于stack exchange,提问作者Someone Special
相关产品推荐
相关产品推荐

