forwardRef和useImperativeHandle的使用

13,948 阅读2分钟

forwardRef

React.forwardRef字面意思理解为转发Ref,它会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。其主要作用是:

1.转发refs到DOM组件(ref不像props作为参数可以传递,所以要想传递ref得用forwardRef)

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

因此,当 React 附加了 ref 属性之后,ref.current 将直接指向 DOM 元素实例。

如果一个组件被多次使用,正常情况下想要调用其组件内的方法需要传入props来调用,每次传入的话就比较多余。所以就引入了React.forwardRef。(在tsx中的运用)

interface SelectFileModalRef {
  handleShowModal: () => void;
  handleCancel: () => void;
}
const SelectFileModal = forwardRef<SelectFileModalRef, Props>(
  (props: Props, ref: Ref<SelectFileModalRef>) => {
   useImperativeHandle(ref, () => ({
      handleShowModal,
      handleCancel,
    }));
  
  }
);

子组件暴露出来的handleShowModal和handleCancel在父组件就可以使用selectFileModalRef.current?.handleShowModal() 来调用进行使用。

2.在高阶组件中转发 refs

首先要搞清楚什么是高阶组件:高阶组件(HOC)它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数

// 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {
  // ...并返回另一个组件...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ...负责订阅相关的操作...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... 并使用新数据渲染被包装的组件!
      // 请注意,我们可能还会传递其他属性
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}


如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。

此时需要用到forwardRef

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回调的第二个参数 “ref”。
  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。通常与forwardRef一起使用,暴露之后父组件就可以通过 selectFileModalRef.current?.handleCancel();来调用子组件的暴露方法。

 useImperativeHandle(ref, () => ({
      handleShowModal,
      handleCancel,
    }));