React 子组件向父组件通信

1,309 阅读2分钟

概述

React典型的数据流自上而下的,父组件通过props可以直接传递数据给子组件。但有些时候我们可能会想要在父组件中拿到子组件中的一些方法,这个时候我们可以通过refs获取到子组件的实例,进而实现子组件向父组件的通信。

最近在学习typescript,所以以下示例都是用tsx写的。

Class Component

在类组件中,通过ref引用子组件后就可以调用类中自定义的方法,这是类的特性所决定的。

  1. React.creactRef

引用保存在ref.current属性中

class Children extends Component {
  public state = {
    name: 'Children',
  };

  public print() {
    console.log('print');
  }

  public render() {
    return <div>{this.state.name}</div>;
  }
}

class Father extends Component {
  public ref = React.createRef<any>();

  public handleClick() {
    this.ref.current.print();
  }

  public render() {
    return (
      <>
        <button onClick={() => this.handleClick()}>click</button>
        <Children ref={this.ref} />
      </>
    );
  }
}
  1. 回调Refs

此处有两个示例,其中textInput是对element元素的引用, inputRef是对Input组件的引用

class Input extends Component {
  textInput: HTMLElement | null = null;

  componentDidMount() {
    this.textInput?.focus();
  }

  public showInfo = () => {
    console.log('this is callback ref');
  };

  render() {
    return (
      <div>
        <input
          ref={(element) => {
            this.textInput = element;
          }}
        />
        <button onClick={() => this.handleClick()}>click</button>
      </div>
    );
  }

  private handleClick = () => {
    console.log(this.textInput);
  };
}

class TopInput extends Component {
  public inputRef: Input | null = null;

  public executeChildren = () => {
    this.inputRef?.showInfo();
  };
  render() {
    return (
      <div>
        <Input
          ref={(element) => {
            this.inputRef = element;
          }}
        />
        <button onClick={() => this.executeChildren()}>click_2</button>
      </div>
    );
  }
}
  1. 使用props自定义ref属性
interface SubProps {
  onRef: (ref: any) => void;
}
interface SubState {
  name: string;
}

class Sub extends Component<SubProps, SubState> {
  name = 'Sub';
  onCallback() {
    console.log('callback');
  }

  componentDidMount() {
    this.props.onRef(this);
  }

  render() {
    return <div />;
  }
}

class Super extends Component {
  customRef: Sub | null = null;

  handleClick() {
    this.customRef?.onCallback();
  }

  render() {
    return (
      <div>
        <button onClick={() => this.handleClick()}>onRef</button>
        <Sub
          onRef={(node: Sub | null) => {
            this.customRef = node;
          }}
        />
      </div>
    );
  }
}

Function Component

通过useImperativeHandle hook结合forwardRef可以得到子组件暴露出来的实例

type Handle = {
  handleChange: () => void;
  print: () => void;
  open: boolean;
};

const Dog = forwardRef((props, ref: React.ForwardedRef<Handle>) => {
  const [open, setOpen] = useState(false);
  useImperativeHandle(
    ref,
    () => ({
      handleChange: () => {
        setOpen(!open);
      },
      print: () => {
        console.log('useImperativeHandle');
      },
      open: open,
    }),
    [open],
  );

  console.log(open);
  return <div>forwardRef {open && <span>open</span>}</div>;
});

function Animal() {
  const dogRef = useRef<Handle>(null);
  const handleClick = () => {
    dogRef.current?.print();
    dogRef.current?.handleChange();
    // 此处拿到的open是还未变化的值
    console.log(dogRef.current?.open);
  };

  return (
    <div>
      <Dog ref={dogRef} />
      <button onClick={handleClick}>dogClick</button>
    </div>
  );
}

总结

对于类组件,核心是拿到对子组件的引用。

而函数组件,关键在于useImperativeHandle的使用。

当然对于嵌套更深的祖孙组件,也可以通过window.dispatchEvent()事件监听的方式通知目标组件。

以上就是我的一些想法总结,欢迎大家指正补充。