实战:使用react ref 的技巧

1,350 阅读1分钟

场景:需要在函数组件外部调用函数组件ref对象的方法或者操作ref,通过下面的实战代码,在antd upload组件外部触发upload组件打开文件上传弹窗

函数式组件

// uploader.tsx
import React, {
  forwardRef,
  useRef,
  useImperativeHandle,
} from 'react';
import {  Upload } from 'antd';

function Uploader(
  props: IProps,
  ref:
    | ((instance: unknown) => void)
    | React.RefObject<unknown>
    | null
    | undefined,
) {
    const uploadRef = useRef<HTMLAnchorElement>(null);
   useImperativeHandle(
    ref,
    () => ({
      doUpload() {
        if (uploadRef.current) {
          uploadRef.current.click();
        }
      },
    }),
    [],
  );
  return (
    <span className={style}>
      <Upload
        className="upload-list-inline"
        ...
      >
        <a ref={uploadRef}>
          {props.showHandler && (
            <Button type="dashed" size="small">
              + 上传附件
            </Button>
          )}
        </a>
        {!props.showHandler && props.fileList?.length === 0 && '暂无数据'}
      </Upload>
    </span>
  );
}
export default forwardRef(Uploader);

通过上面的函数式组件代码,我们调用了其他组件ref上的方法,并且定义了一个doUpload方法暴露给外面调用

class组件对外暴露方法

// ClassCustomer.tsx
import React, { PureComponent } from 'react';

class ClassCustomer extends PureComponent<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state={
      todo:false,
    }
  }
  //向外层提供ref实例
  componentDidMount() {
    this.props.onRef(this);
  }
  
  changState = () => {
    this.setState({todo:true})
  }
  
  render(){
    return 
        <>
        ...
      </>
  }
}

export default ClassCustomer;

如何使用ref上的方法

介绍一下在函数式组件和class组件中如何调用doUpload方法

  • 函数式组件调用函数式组件中的方法

    // FCCustomer.tsx import React, { useRef, forwardRef, useImperativeHandle } from 'react'; import Uploader from './uploader'; const FCCustomer = ( props: IProps, ref: | React.RefObject | ((instance: unknown) => void) | null | undefined, ) => { const childRef = useRef(null); // 继续把函数式组件ref上的方法向外暴露 useImperativeHandle( ref, () => ({ doUpload() { if (childRef.current) { childRef.current.doUpload(); } }, }), [], ); return ( <Collapse defaultActiveKey={['attachment']} ... > <Panel header="附件" key="attachment" extra={ <Button type="dashed" size="small" onClick={(e) => { e.stopPropagation(); if (childRef.current) { childRef.current.doUpload(); } }} > + 添加 } > <Uploader showHandler={false} ref={childRef} ... /> ); };

    export default forwardRef(FCCustomer);

  • class组件调用 函数式组件的ref 和class组件的Ref

    // Costumer.tsx

    import React, { Component,createRef } from 'react'; import FCCustomer from './FCCustomer' import ClassCustomer from './ClassCustomer'

    class Costumer extends Component<IProps, IState> { FCCustomerChild: React.RefObject; ClassCustomerChild: any; constructor(props: IProps) { super(props); // 可以调用ref上对外暴露的指定方法 this.FCCustomerChild = React.createRef(); }

    onRef = (ref: any) => { this.ClassCustomerChild = ref; };

    // 可以调用class ref上的方法 doRefFc = () => { this.ClassCustomerChild.showEdit(id, comment); };

    render(){ return <> <Icon type="paper-clip" onClick={() => { if (this.FCCustomerChild && this.FCCustomerChild.current) { this.FCCustomerChild.current.doUpload(); } }} /> <FCCustomer ref={this.attachmentChild} ... /> </> } }

如何使用useImperativeHandle 和React.forwardRef