React forwardRef,useImperativeHandle 自由获取子组件的属性或方法

515 阅读2分钟

在vue组合式api中可以通过父组件定义ref ,子组件属性上定义同名ref,父组件就可以获取子组件实例,在子组件上通过调用defineExpose 暴露属性或者方法

那在react 如何做呢,

forwardRef

forwardRef 允许你的组件使用 将一个 DOM 节点暴露给父组件

简单来说:使用 forwardRef,你可以在函数式组件中传递 ref,将它们转发给内部的子组件。从而获取子组件信息

默认情况下,每个组件的 DOM 节点都是私有的。然而,有时候将 DOM 节点公开给父组件是很有用的,比如允许对它进行聚焦。如果你想将其公开,可以将组件定义包装在 forwardRef() 中:

import { forwardRef } from 'react';  
const MyInput = forwardRef(function MyInput(props, ref) {  
const { label, ...otherProps } = props;  
    return (  
        <label>  
            {label}  

            <input {...otherProps} />  
        </label>  
    );  
});

你将在 props 之后收到一个 ref 作为第二个参数。将其传递到要公开的 DOM 节点中:

import { forwardRef } from 'react';  
const MyInput = forwardRef(function MyInput(props, ref) {  
const { label, ...otherProps } = props;  
    return (  
        <label>  
            {label}  

            <input {...otherProps} ref={ref}/>  
        </label>  
    );  
});

这样,父级的 Form 组件就能够访问 MyInput 暴露的 <input> DOM 节点:

    function Form() {  
        const ref = useRef(null);  
        function handleClick() {  
            ref.current.focus();  
        }  
    return (  
        <form>  
            <MyInput label="Enter your name:" ref={ref} />  
            <button type="button" onClick={handleClick}>  
                Edit  
            </button>  
        </form>  
    );  
}

那如果想在父组件中使用子组件的属性或者方法应该怎么做呢?

useImperativeHandle

useImperativeHandle 是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。

useImperativeHandle(ref, createHandle, dependencies?)

参数 

  • ref:该 ref 是你从 forwardRef 渲染函数 中获得的第二个参数。
  • createHandle:该函数无需参数,它返回你想要暴露的 ref 的句柄。该句柄可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。
  • 可选的 dependencies:函数 createHandle 代码中所用到的所有反应式的值的列表。反应式的值包含 props、状态和其他所有直接在你组件体内声明的变量和函数。倘若你的代码检查器已 为 React 配置好,它会验证每一个反应式的值是否被正确指定为依赖项。该列表的长度必须是一个常数项,并且必须按照 [dep1, dep2, dep3] 的形式罗列各依赖项。React 会使用 Object.is 来比较每一个依赖项与其对应的之前值。如果一次重新渲染导致某些依赖项发生了改变,或你没有提供这个参数列表,你的函数 createHandle 将会被重新执行,而新生成的句柄则会被分配给 ref。

父组件: 定义一个ref 传递给子组件MyInput

mport { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // 下方代码不起作用,因为 DOM 节点并未被暴露出来:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

子组件:通过 forwardRef 获得ref,通过 useImperativeHandle 暴露子组件的属性或者方法

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

export default MyInput;