如何在父组件中访问子组件的引用(refs)

96
如果我有像这样的东西
<Parent>
  <Child1 />
  <Child2 />
  <Child3 />
</Parent>

我想从 Child2 访问具有 refs="child2refs" 的元素,我该如何做?


你能在这里发布你的 React 代码吗?在这种情况下,this.props.children 应该始终起作用… - omarjmh
为了访问子组件,我们可以添加ref到它<Child2 ref="child2" />,然后通过this.refs.child来访问它。但是这种方法在连接到redux或其他插件的组件上不起作用。我们需要使用getWrappedInstance()来获取包装实例,然后才能访问该组件的状态、refs和方法。这里有一个视频对此进行了解释-https://youtu.be/VpdKjocgCtA - Prem
1
现在有转发引用的可能性:https://reactjs.org/docs/forwarding-refs.html - dude
@DaveF 但是何时使用querySelector,并且该元素将在多快的时间内被替换?至少是不可靠的。 - doug65536
9个回答

101

建议用于React版本低于16.3

如果无法避免使用,请参考从React文档提取的建议模式:

import React, { Component } from 'react';

const Child = ({ setRef }) => <input type="text" ref={setRef} />;

class Parent extends Component {
    constructor(props) {
        super(props);
        this.setRef = this.setRef.bind(this);
    }

    componentDidMount() {
        // Calling a function on the Child DOM element
        this.childRef.focus();
    }

    setRef(input) {
        this.childRef = input;
    }

    render() {
        return <Child setRef={this.setRef} />
    }
}

父组件将一个绑定到父组件this的函数作为属性传递。当React调用子组件ref属性setRef时,它将把子组件ref赋值给父组件childRef属性。

建议使用React >= 16.3版本

引用转发是一项可选功能,可以让某些组件接收并向下传递(换句话说,“转发”)到子组件中的引用。

我们可以通过使用 React.forwardRef 创建组件来实现引用的转发。返回的组件的ref属性必须与React.createRef返回类型相同。每当React挂载DOM节点时,使用React.createRef创建的refcurrent属性将指向底层的DOM节点。

import React from "react";

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

class AutoFocus extends React.Component {
  constructor(props) {
    super(props);
    this.childRef = React.createRef();
    this.onClick = this.onClick.bind(this);
  }

  componentDidMount() {
    this.childRef.current.focus();
  }

  onClick() {
    console.log("fancy!");
  }

  render() {
    return <LibraryButton onClick={this.onClick} ref={this.childRef} />;
  }
}

转发引用 HOC 示例

创建的组件将它们的ref转发给一个子节点。

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;

      // Assign the custom prop "forwardedRef" as a ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // Note the second param "ref" provided by React.forwardRef.
  // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
  // And it can then be attached to the Component.
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

请查看React文档中的转发Refs


59
从父组件向下传递一个引用似乎与从父组件访问子组件引用不是同一件事。 - ESR
这不是我想要的例子。LibraryButton 不是一个 class。你能展示一个在 class LibraryButton extends React.Component 中的例子吗?谢谢。 - new2cpp
在高阶组件和连接组件中使用转发引用时,我认为提到传统的做法是使用connect方法上的选项参数{ withRef: true }并在HOC中访问包装的组件实例,例如:this.wrappedComponentRef.current.getWrappedInstance()会很有帮助。请参见此处的在连接组件上使用引用部分:https://itnext.io/advanced-react-redux-techniques-how-to-use-refs-on-connected-components-e27b55c06e34#63a4。 - rdrw

22
  1. 在子组件中添加一个 ref,指向你需要的节点。
  2. 在父组件中添加一个 ref,指向子组件。
/*
* Child component
*/
class Child extends React.Component {
  render() {
    return (
      <div id="child">
        <h1 ref={(node) => { this.heading = node; }}>
          Child
        </h1>
      </div>
    );
  }
}

/*
 * Parent component
 */
class Parent extends React.Component {
  componentDidMount() {
    // Access child component refs via parent component instance like this
    console.log(this.child.heading.getDOMNode());
  }

  render() {
    return (
      <div>
        <Child
          ref={(node) => { this.child = node; }}
        />
      </div>
    );
  }
}

示例:https://codepen.io/itsfadnis/pen/aLWVVx?editors=0011


1
不确定是否取决于版本号,但在 React < 16.3 中,我能够在不使用 getDOMNode() 的情况下只使用 this.child.heading 来完成此操作。 - Ian J Miller
2
@IanJMiller 引用作为 this.child.heading 访问,在这个例子中我只是打印与其关联的 DOM 节点。 - Nikhil Fadnis
这是哪个版本的React? - Brad
1
不错。这个实现比React.forwardRef()要直接得多。 - png
那么对于一个子组件的子组件呢?比如说,一个被渲染在<Child>内部的组件,我该如何从父组件中引用它? - pmiranda
当你在 Child 中返回 Child,是否存在循环依赖关系? - clapas

18

这是一个以ref为重点的输入示例(在React 16.8.6中测试通过):

子组件:

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return (<input type="text" ref={this.myRef} />);
  }
}

父组件内包含子组件:

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.childRef = React.createRef();
  }
  componentDidMount() {
    this.childRef.current.myRef.current.focus();
  }
  render() {
    return <Child ref={this.childRef} />;
  }
}

ReactDOM.render(
    <Parent />,
    document.getElementById('container')
);

使用this.props.children的父组件:

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.childRef = React.createRef();
    }
    componentDidMount() {
        this.childRef.current.myRef.current.focus();
    }
    render() {
        const ChildComponentWithRef = React.forwardRef((props, ref) =>
            React.cloneElement(this.props.children, {
                ...props,
                ref
            })
        );
        return <ChildComponentWithRef ref={this.childRef} />
    }
}

ReactDOM.render(
    <Parent>
        <Child />
    </Parent>,
    document.getElementById('container')
);

3
第三个例子就是它! - Marecky
1
无类型 TypeScript。你的 this.props.children 是什么类型? - doug65536

16

首先通过this.props.children访问子元素,每个子元素将会有它的ref作为属性。


4
如果我从父组件的渲染函数中使用console.log(this.props.children),它会显示为undefined :( - user1354934
链接在这里...应该很容易找到。 - omarjmh
让我们在聊天中继续这个讨论 - user1354934
4
这是推荐的 ReactJS 实践吗? - Frederik Brinck Jensen
2
子元素的 ref 属性为 null - qwertzguy

9

如果你的所有东西都是props.children

const Parent = (p: {children: JSX.Element}) => {
    const childRef = useRef()

    return React.cloneElement(p.children, { ref: childRef })
}

<Parent>
  <SingleChild />
</Parent>

请注意,如果您的子组件不能拥有 ref,例如 React.Fragment,则此方法将失败。

完美运作。根据React文档,cloneElement有“陷阱”,但我不知道还有其他方法可以做到这一点。https://react.dev/reference/react/cloneElement#alternatives - Richard Lovejoy

6
使用Ref转发,您可以将ref从父组件传递到子组件。
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>;
  1. 通过调用 React.createRef 创建一个 ref,并将其分配给 ref 变量。
  2. 将您的 ref 通过指定为 JSX 属性传递给子组件。
  3. React 将 ref 作为第二个参数传递给 forwardRef 中的 (props, ref) => ... 函数。
  4. 通过将此 ref 参数作为 JSX 属性指定,将其转发到子组件。
  5. 当 ref 被附加时,ref.current 将指向 DOM 节点。

注意 :只有使用 React.forwardRef 调用定义组件时才存在第二个 ref 参数。普通函数式或类组件不接收 ref 参数,而在 props 中也不可用。

Ref 转发不仅限于 DOM 组件。您还可以将 Ref 转发到类组件实例中。

参考资料:React 文档。


2

我将展示如何解决动态组件的问题:

在父组件上,动态地创建对子组件的引用,例如:

class Form extends Component {
    fieldRefs: [];

    // dynamically create the child references on mount/init
    componentWillMount = () => {
        this.fieldRefs = [];
        for(let f of this.props.children) {
            if (f && f.type.name == 'FormField') {
                f.ref = createRef();
                this.fieldRefs.push(f);
            }
        }
    }

    // used later to retrieve values of the dynamic children refs
    public getFields = () => {
        let data = {};

        for(let r of this.fieldRefs) {
            let f = r.ref.current;
            data[f.props.id] = f.field.current.value;
        }

        return data;
    }
}

子组件(即 <FormField />)实现了自己的“field”引用,可以从父组件中引用:

class FormField extends Component {
    field = createRef();
    
    render() {
        return(
            <input ref={this.field} type={type} />
        );
    }
}

然后,在你的主页面,也就是“父亲的父亲”组件中,你可以通过引用获取字段值,如下:

class Page extends Component {
    form = createRef();

    onSubmit = () => {
        let fields = this.form.current.getFields();
    }

    render() {
        return (
            <Form ref={this.form}>
                <FormField id="email" type="email" autoComplete="email" label="E-mail" />
                <FormField id="password" type="password" autoComplete="password" label="Password" />

                <div class="button" onClick={this.onSubmit}>Submit</div>
            </Form>
        );
    }
}

我实现这个功能是因为我想将所有通用的表单功能封装到一个主要的
组件中,而且唯一能够让主客户端/页面组件设置和样式化自己内部组件的方法就是使用子组件(即在父中的项,这些组件位于其他组件中)。
因此,虽然有些人可能认为这是一种hack,但它与React试图阻止任何父组件实际上的“ref”的方式一样hackey,我认为这是一种荒谬的设计,无论他们如何去合理化。

考虑将React批评放在答案底部,而不是首先提出。 - CodeFinity
谢谢。我已经这样做了,甚至考虑过删除它。然后,我忘记了。现在你提到它,我想我会留下来,以便只有那些能够阅读并理解关注点,仍然能够解决问题的人才可能真正有用地使用它。也许您对解决方案的完整性有一些评论?干杯。 - Ryan Weiss
解决方案是可以的。这取决于您的意图,因为这是您的答案,但如果大家在这里专注于迅速获取代码解决方案,那么您提供的引导可能会阻碍这一目标。 - CodeFinity
1
@CodeFinity 已收到并已编辑输入。谢谢。 - Ryan Weiss

2
我认为这份指南讲得很清楚,可以参考一下:https://github.com/yannickcr/eslint-plugin-react/issues/678。"Original Answer"的意思是"最初的回答"。
class Field extends Component {
  const { inputRef } = this.props;
  render() {
    return (
      <input type="text" ref={inputRef} />
    )
  }
}

class MyComponent extends Component {
  componentDidMount() {
    this.inputNode.focus();
  }

  render() {
    return (
      <div>
        Hello, <Field inputRef={node => this.inputNode = node} />
      </div>
    )
  }
}

0

如果您想在父组件中获得子组件的引用

// children is an array
const updatedChildren = Children.map(children, (child) => {
  return { ...child.props, ref: child.ref };
}),

// children is not an array
const childrenRef = children.ref;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接