React自定义Hook更新useState时如何避免组件重渲染及输入失焦
问题原因
每次调用useCustomHook(也就是使用该Hook的组件重渲染时),InputComponent都会被重新定义成一个全新的函数。React通过引用来判断组件类型,因此会把新的InputComponent视为完全不同的组件,直接卸载旧的输入框实例并挂载新实例——这就是输入框失去焦点的核心原因。
另外,validarTexto函数也会在每次Hook执行时重新创建,虽不会直接导致焦点丢失,但会造成不必要的性能开销。
解决思路
1. 把组件移出Hook,通过props传递状态(推荐)
将InputComponent定义在Hook外部,让它通过props接收状态值和变更回调,保证组件引用始终稳定,不会因Hook内部状态更新被重新创建:
import React, { useState } from 'react'; // 单独定义输入组件,通过props接收数据 const InputComponent = ({ value, onChange }) => { return <input type="text" value={value} onChange={onChange} />; }; const useCustomHook = () => { const [textoDelInput, setTextoDelInput] = useState(''); const validarTexto = () => { return textoDelInput !== ''; }; const handleChange = (event) => { setTextoDelInput(event.target.value); }; // 返回组件、状态、回调和校验函数 return { InputComponent, inputValue: textoDelInput, handleChange, validarTexto }; }; // 使用示例 const App = () => { const { InputComponent, inputValue, handleChange, validarTexto } = useCustomHook(); return ( <div> <InputComponent value={inputValue} onChange={handleChange} /> <button onClick={() => console.log(validarTexto())}>校验</button> </div> ); };
2. 用useMemo缓存内部组件
如果必须在Hook内部定义组件,可以用useMemo包裹InputComponent,同时用useCallback缓存回调函数,确保组件引用稳定:
import React, { useState, useMemo, useCallback } from 'react'; const useCustomHook = () => { const [textoDelInput, setTextoDelInput] = useState(''); // 用useCallback缓存校验函数,避免重复创建 const validarTexto = useCallback(() => { return textoDelInput !== ''; }, [textoDelInput]); // 用useCallback缓存change回调,保证引用稳定 const handleChange = useCallback((event) => { setTextoDelInput(event.target.value); }, []); // 用useMemo缓存组件,只有依赖变化时才重新创建 const InputComponent = useMemo(() => { return () => ( <input type="text" value={textoDelInput} onChange={handleChange} /> ); }, [textoDelInput, handleChange]); return [validarTexto, InputComponent]; };
3. 改用组合组件封装逻辑
如果需要同时封装组件和状态逻辑,更推荐直接创建组合组件,而非在Hook里返回组件,这更符合React的设计理念:
import React, { useState } from 'react'; const InputWithValidation = ({ children }) => { const [textoDelInput, setTextoDelInput] = useState(''); const validarTexto = () => { return textoDelInput !== ''; }; const handleChange = (event) => { setTextoDelInput(event.target.value); }; // 通过render prop把校验函数暴露给父组件 return ( <div> <input type="text" value={textoDelInput} onChange={handleChange} /> {typeof children === 'function' && children(validarTexto)} </div> ); }; // 使用示例 const App = () => { return ( <InputWithValidation> {(validar) => ( <button onClick={() => console.log(validar())}>校验</button> )} </InputWithValidation> ); };
内容的提问来源于stack exchange,提问作者Andreina Paola Riera
相关产品推荐
相关产品推荐

