背景
最近项目改造中需要接入我司自研的Input组件。本来是件so easy的事情,接到一半发现有个场景是在用户点击重命名之后,原来的静态文本会变成输入框供用户重命名,同时输入框要进入focus和select的状态。看了下组件库暴露的props,发现木有autoFocus和autoSelect之类的东东。就开始琢磨着自己在父元素通过Input的ref来实现吧。从此采坑之旅正式开始。
使用ref控制input自动focus
对于Hooks组件获取子组件的ref有两种方式,1. 直接使用useRef获取。2. 使用callback ref的方式获取。首选试试使用useRef,上代码:
function App () {
// 使用useRef然后在useEffect里面去focus的方式不靠谱,因为: 1. ref变更的时候不会通知父组件。2. useEffect里面的执行是因为父组件自身状态发生重绘导致,但此时不能保证子组件的ref已经更新。
const ref = useRef(null);
const [isShowInput, setIsshowInput] = useState(false);
useEffect(() => {
ref.current?.focus();
ref.current?.select();
}, [ref.current]);
return(
<>
<button onClick={() => setIsshowInput(!isShowInput)}>click</button>
<Input ref={ref}/>
</>
)
}
// Input组件的基本代码。
const Input = forwardRef((props, ref) => {
const inputEl = useRef(null);
const [value, setValue] = useState('');
useImperativeHandle(ref, () => inputEl.current);
return <input ref={inputEl} type="text" value={value} onChange={(e) => setValue(e.target.value)}/>;
});
这个代码点击click按钮的时候,会出现输入框,同时输入框会处于focus和select的状态。测试的时候发现,第一次点击的时候没有focus上。可以看注释,里面写上了分析的原因。
既然不靠谱,那我就用callback ref试试,这也是官方推荐的这种场景的用法 文档。下面看看使用callback ref的代码:
function App () {
const [isShowInput, setIsshowInput] = useState(false);
const callbackRef = useCallback((node) => {
console.log('callbackRef', node);
node && node.focus();
node && node.select();
}, []);
function onChange(e) {
setValue(e.target.value);
}
return(
<>
<button onClick={() => setIsshowInput(!isShowInput)}>click</button>
{isShowInput && <Input ref={callbackRef} autoFocus={true}/>}
</>
)
}
使用callback ref的方式的话可以实现当子组件的ref变化时得到通知,但是有个问题是,如果像示例这样Input组件内部实现的时候value是受控的话,即通过state来控制value,会导致输入时不断触发callback,也就是代码中的console.log会被不断打印。此时无法判断是否首次而是否需要执行focus等逻辑。
而不巧的是,我们的组件库的Input组件的value是受控的,所以callbackRef在用户输入的时候会被不断触发。
结论
经过几番尝试,题主觉得目前唯一靠谱的解决方案是在Input组件内部实现autoFocus和autoSelect的逻辑然后通过props暴露给父组件。代码如下:
// Input组件的基本代码。
const Input = forwardRef((props, ref) => {
const inputEl = useRef(null);
const [value, setValue] = useState('');
useImperativeHandle(ref, () => inputEl.current);
useEffect(() => {
props.autoFocus && inputEl.focus();
props.autoSelect && inputEl.select();
});
return <input ref={inputEl} type="text" value={value} onChange={(e) => setValue(e.target.value)}/>;
});
大神们如果有其他更好的方案,也欢迎提出交流~