react Hook之useRef、useImperativeHandle

25,364 阅读2分钟

useRef

useRef 返回一个可变的ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

基本用法,设置焦点

function TextInputWithFocusButton() {
    const inputEl: MutableRefObject<any> = useRef(null);
    const onButtonClick = () => {
        // `current` 指向已挂载到 DOM 上的文本输入元素
        inputEl.current.focus();
    };
    return (
        <>
            <input ref={inputEl} type="text" />
            <button onClick={onButtonClick}>Focus the input</button>
        </>
    );
}

在组件的整个生命周期里,inputEl.current都是存在的,这扩展了useRef本身的用途,可以使用useRef维护类似于class.component中实例属性的变量。

类似于class.component中实例属性的变量

利用ref保存前一个值

const Count = () => {
    const [count, setCount] = useState(0);
    const prevCountRef = useRef(count);
    const prevCount = prevCountRef.current;

    useEffect(() =>{
        prevCountRef.current = count;
        console.log('useEffect:', prevCountRef)
    }, [count]);
    
    return (
        <div>
            <button onClick={() => { setCount( count + 1 ) }}>加1</button>
            <p>count: {count}</p>
            <p>prevCount: {prevCount}</p>
        </div>
    )
}

利用ref保存未来的值

function Example() {
  const [count, setCount] = useState(0);
  const currentCount = useRef(count);
  const prevCount = currentCount.current;

  currentCount.current = count;

  return (
    <div>
      <p>prev: {prevCount}</p>
      <p>current: {currentCount.current}</p>
      <button onClick={() => setCount(count + 1)}>setCount</button>
    </div>
  );
}

export default Example;

useImperativeHandle

建议useImperativeHandle和forwardRef同时使用,减少暴露给父组件的属性,避免使用 ref 这样的命令式代码

import {  useRef,forwardRef,MutableRefObject,useImperativeHandle,Ref} from "react";

//只暴露value、getType、focus给父级
const InputEl = forwardRef((props: {}, ref: Ref<any>): JSX.Element=>{
    const inputEl: MutableRefObject<any> = useRef();

    useImperativeHandle(ref, ()=>({//第一个参数:暴露哪个ref;第二个参数:暴露什么
        value: (inputEl.current as HTMLInputElement).value,
        getType: () => (inputEl.current as HTMLInputElement).type,
        focus: () => (inputEl.current as HTMLInputElement).focus()
    }));

    return(
        <input ref={inputEl} type="text" {...props}/>
    )
})
//暴露整个input节点给父级
const InputEl = forwardRef((props: {}, ref: Ref<any>): JSX.Element=>{
    return(
        <input ref={ref} type="text" {...props}/>
    )
});

//父级
function InputWithFocusButton() {
    const inputEl: MutableRefObject<any> = useRef(null);

    function onButtonClick() {
        console.log('子组件input的对象:', inputEl.current);
        inputEl.current.focus();
    };
    return (
        <>
            <InputEl ref={inputEl} />
            <button onClick={onButtonClick}>Focus the input</button>
        </>
    );
}

通过forwardRef,父组件获取子组件的ref,子组件在暴露ref中,限制暴露的一些参数