一、ref

  ref是React提供的用来操纵React组件实例或者DOM元素的接口。表示为对组件真正实例的引用,其实就是ReactDOM.render()返回的组件实例。

  ref可以挂到任何元素上,可以挂到组件上也可以挂载到DOM元素上。

  Class组件中使用ref:

  在React的Class组件时期,我们通过createRef创建ref

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}

  在这个例子里ref挂到了原生DOM元素<input />,在这种情况下可以通过ref.current获取到这个DOM元素,并直接调用上面的方法。

  ref如果挂在到一个Class组件上,这样ref.current获取到的就是这个Class组件的实例。

  函数式组件中使用ref:

    但是,ref不能挂到一个函数式组件(除非使用forwardRef,因为:ref回调函数会在组件被挂载之后将组件实例传递给函数,函数式组件没有实例。

  在函数式组件中通过useRef创建ref 

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

二、createRef和useRef的区别

       createRef 只能用在class组件中,useRef 只能用在函数式组件中。

  createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

  如果在函数式组件中使用createRef创建的ref,其值会随着函数式组件的重新执行而不断初始化。hooks不能用在class组件中,所以class组件只能使用createRef。

三、forwardRef

  前面我们说到:ref不能挂到一个函数式组件(除非使用forwardRef)。

  forwardRef可以直接包裹一个函数式组件,被包裹的函数式组件会获得被分配给自己的ref(作为第二个参数)。

  如果你直接将ref分配给没有被forwardRef包裹的函数式组件,React会在控制台给出错误。

const App: React.FC = () => {
  const ref = useRef(null);

  useEffect(() => {
    ref.current.focus();
  }, []);

  return (
    <>
      <Child ref={ref} />
    </>
  );
};

const Child = forwardRef((props, ref: Ref<any>) => {
  return <input type="text" name="child" ref={ref} />;
});

   **注意:React.forwardRef参数必须是function,而这个API通常用来解决HOC(高阶组件)中丢失ref的问题。

四、useImperativeHandle

   在forwardRef例子中的代码实际上是不推荐的,因为无法控制要暴露给父组件的值,所以我们使用useImperativeHandle控制要将哪些东西暴露给父组件。

   useImperativeHandle 应当与 forwardRef 一起使用:

   调用方式: 

useImperativeHandle(ref, createHandle, [deps])
  • 接收一个ref
  • 接收一个函数,这个函数返回的对象即是要暴露出的ref
  • 类似useEffect,接收一个依赖数组
const FancyInput=(props, ref) =>{
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} />;
}
export default forwardRef(FancyInput);

  在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()