React.forwardRef使用

关于forwardRef的使用做一下总结。

转发ref的目的是为了不会出现this.ref.ref.ref之类的情况,可以对外界暴露内部的ref,因此称为转发(forward)。

应用场景:

  • 获取深层次子孙组件的 DOM 元素
  • 在函数式组件上使用ref赋值(函数组件本身上ref的是无效的,因为它自己没有实例,但forwardRef返回的组件可以)
  • 传递 refs 到高阶组件

需要了解的:

  • ref并非react元素的属性,它经过了特殊处理,因此不能从props取得,类似的属性还有key
  • React.forwardRef只能用于函数组件
  • React.forwardRef返回高阶组件(HOC)
  • React.forwardRef的参数是一个函数组件或者说render函数
  • render函数组件第二个参数是ref,平时这个参数在函数组件中没用,只有作为forwardRef参数时才有效。

注意:forwardRef并不能解决直接选取到第三方组件库中的dom的作用,除非对方提供了forwardRef的支持。

最基本的使用

import React, { useRef, useImperativeHandle } from "react";

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    myfocus: () => {
      inputRef.current.focus();
    },
    myblur: () => {
      inputRef.current.blur();
    }
  }));

  return <input ref={inputRef} />;
}

const FancyInputWrapped = React.forwardRef(FancyInput);

const App = () => {
  const inputRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.myfocus();
  };

  const handleBlur = () => {
    inputRef.current.myblur();
  };

  return (
    <div>
      <FancyInputWrapped ref={inputRef} />
      <button style={{ marginLeft: 8 }} onClick={handleFocus}>
        FOCUS
      </button>
      <button style={{ marginLeft: 8 }} onClick={handleBlur}>
        BLUR
      </button>
    </div>
  );
};

export default () => <App />;

查看:https://codesandbox.io/s/basic-forward-ref-2hjwky

高阶组件转发

注:实际上,我并没有从这个例子中看到特别有用的部分,其实我们没有fowardRef一样可以实现这个过程,如果你不想用forwardRef解决完全是可以的。

import React from "react";

const FocusInput = React.forwardRef((props, ref) => (
  <input type="text" ref={ref} />
));

function enhance(WrappedComponent) {
  class Enhance extends React.Component {
    render() {
      const { forwardedRef, ...restProps } = this.props;
      // 将定义的 prop 属性 forwardRef 定义为 ref
      return <WrappedComponent ref={forwardedRef} {...restProps} />;
    }
  }

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

// EnhancedChildComponet 会渲染一个高阶组件 enhance(FocusInput)
const EnhancedChildComponet = enhance(FocusInput);

// 我们导入的 EnahcnedComponent 组件是高阶组件(HOC)Enhance
// 通过 React.forward 将 ref 将指向了 Enhance 内部的 FocusInput 组件
// 这意味着我们可以直接调用 ref.current.focus() 方法
class App extends React.Component {
  private ref = React.createRef<HTMLInputElement>();

  constructor(props) {
    super(props);
  }
  handleFocus = () => {
    const { current } = this.ref;
    current.focus();
  };
  handleBlur = () => {
    const { current } = this.ref;
    current.blur();
  };
  render() {
    return (
      <>
        <EnhancedChildComponet ref={this.ref} />
        <button style={{ marginLeft: 8 }} onClick={this.handleFocus}>
          FOCUS
        </button>
        <button style={{ marginLeft: 8 }} onClick={this.handleBlur}>
          BLUR
        </button>
      </>
    );
  }
}

export default () => <App />;

查看:https://codesandbox.io/s/hoc-foward-ref-8z727h?file=/App.tsx

useImperativeHandle

此Hook必须配合forwardRef使用,其作用看起来是封装自己ref对象上的方法,其实也不是特别必要使用的技术。

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

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    myfocus: () => {
      inputRef.current.focus();
    },
    myblur: () => {
      inputRef.current.blur();
    }
  }));

  return <input ref={inputRef} />;
}

const FancyInputWrapped = React.forwardRef(FancyInput);

const App = () => {

  const inputRef = useRef(null)

  const handleFocus = () => {
    inputRef.current.myfocus();
  }

  const handleBlur = () => {
    inputRef.current.myblur();
  }

  return (
    <div>
      <FancyInputWrapped ref={inputRef} />
      <button style={{ marginLeft: 8 }} onClick={handleFocus}>FOCUS</button>
      <button style={{ marginLeft: 8 }} onClick={handleBlur}>BLUR</button>
    </div>
  )
};

export default () => <App />

查看:https://codesandbox.io/s/ji-ben-yong-fa-forked-2hjwky?file=/App.tsx

参考

发表回复