·  阅读

在 react 实际项目开发中,有时会需要在父组件中调用子组件方法,而且子组件是在循环中生成的,虽然这种做法不符合 react 的设计哲学,但是在开发中确实有这方面的需求,下面记录一下两种解决方法:

第一种方法是使用 useRef/useImperativeHandle/forwardRef :

  • useRef 初始化为数组
  • 子组件循环生成中设置 childRefs.current[item.id] = ref;
  • 子组件需要用 forwardRef 包一下,(使用 connect 包裹的子组件需要添加 { forwardRef: true } ,见 这里 )
  • 使用 useImperativeHandle 导出子组件的方法,注意子组件的第二个参数
  • 下面是最简化的代码和相关注释:

    // 父组件:
    import React, { useRef } from 'react';
    import ChildComp from './ChildComp';
    function App() {
      const arr = [
          id: 1,
          name: '🍎',
          id: 2,
          name: '🍌',
          id: 3,
          name: '🍊',
      // useRef 初始化为数组
      const childRefs = useRef<any>([]);
      return (
        <div className="App">
          {arr.map((item) => (
            <ChildComp
              // ⚠️注意 ref 有可能为空 所以在此处加一个判断
              ref={(ref) => {
                if (ref) {
                  childRefs.current[item.id] = ref;
              data={item}
              key={item.id}
          <button
            onClick={() => {
              childRefs.current &&
                childRefs.current.forEach((childRef: any) => {
                  // 调用所有子组件的方法
                  childRef.exportChildMethod();
                  // 获取所有子组件的值
                  console.log(childRef.exportChildValue());
              // 获取某个子组件的值
              console.log(
                childRefs.current && childRefs.current[1].exportChildValue()
            调用子组件的方法
          </button>
        </div>
    export default App;
    // 子组件
    import React, { forwardRef, useState, useImperativeHandle } from 'react';
    // 使用forwardRef包裹的组件可以获得一个ref的参数
    const Child = (props: any, ref: any) => {
      const { data } = props;
      const [childNum, setChildNum] = useState<number>(0);
      // 在useImperativeHandle中导出被父组件使用的方法
      useImperativeHandle(ref, () => ({
        exportChildMethod: () => {
          childMethod();
        exportChildValue: () => {
          return childNum;
      const childMethod = () => {
        console.log(`log from child--${data.name}`);
      return (
          <p>{data.name}</p>
          <p>{childNum}</p>
          <button
            onClick={() => {
              setChildNum(childNum + 1);
            child add
          </button>
        </div>
    // 需要使用forwardRef包裹
    export default forwardRef(Child);
    

    第二种方法是不使用 useRef/forwardRef/useImperativeHandle, 原理就是 props 改变触发子组件的方法:

  • 父组件设置一个毫秒数ms为空
  • 父组件触发动作,设置毫秒数msnew Date().getTime()
  • ms传到子组件中
  • 子组件在useEffect中依赖 ms的值,ms改变触发子组件内的函数
  • import React from 'react';
    import ChildComp from './ChildComp';
    function App() {
      const arr = [
          id: 1,
          name: '🍎',
          id: 2,
          name: '🍌',
          id: 3,
          name: '🍊',
      const [ms, setMs] = useState<number | undefined>();
      const emitChildMethod = () => {
        setMs(new Date().getTime());
      return (
        <div className="App">
          {arr.map((item) => (
            <ChildComp ms={ms} data={item} key={item.id} />
            <button onClick={emitChildMethod}>调用子组件的方法</button>
          </div>
        </div>
    export default App;
    // 子组件
    import React, { useState, useEffect } from 'react';
    const Child = (props: any) => {
      const { data } = props;
      const [childNum, setChildNum] = useState<number>(0);
      useEffect(() => {
        // 此处需要做一下判断
        if (ms) {
          childMethod();
      }, [ms]);
      const childMethod = () => {
        console.log(`log from child--${data.name}`);
      return (
          <p>{data.name}</p>
          <p>{childNum}</p>
          <button
            onClick={() => {
              setChildNum(childNum + 1);
            child add
          </button>
        </div>
    export default Child;
    
  • 这两种方法都是不得已而为止的办法,破坏了单项数据流的设计模式
  • 也不利于代码调试和业务梳理
  • 但是在老代码的改造和添加新功能方面有一定优势
  • 分类:
    前端