【问题标题】:What's the proper way of passing a ref to a prop?将 ref 传递给 prop 的正确方法是什么?
【发布时间】:2016-12-16 06:49:36
【问题描述】:

我正在尝试将一个组件的引用传递给另一个组件。由于string refs are being deprecated 我正在使用回调引用。

所以我有类似的东西:

<One ref={c => this.one = c}/>
<Two one={this.one}/>

问题是,每当我尝试在Two 中访问this.props.one 时,我都会得到undefined

我什至在Two上试过这个:

componentDidMount(){
    setTimeout(()=>{
        console.log(this.props.one);
    },5000)
}

似乎问题在于,在创建 prop 时,ref 还不存在,因为它是在安装 One 后创建的。但我不知道如何“刷新”Two 上的道具以获取已安装组件的引用。

那么将 ref 传递给另一个组件的正确方法是什么?

编辑

一些用户建议将该逻辑封装在更高的组件中,该组件本身会呈现其他子组件。

这种方法的问题是您无法创建可重用的逻辑,并且您必须在那些封装组件中一遍又一遍地重复相同的逻辑。

假设您想创建一个通用的&lt;Form&gt; 组件,它封装了向您的商店提交逻辑、错误检查等。您可以执行以下操作:

<Form>
    <Input/>
    <Input/>
    <Input/>
    <Input/>
    <SubmitButton/> 
</Form>

在此示例中,&lt;Form&gt; 无法访问子级的实例(和方法),因为this.props.children 不会返回这些实例。它返回一些伪组件列表。

那么如何在不传递 ref 的情况下检查某个 &lt;Input/&gt; 是否检测到验证错误?

您必须使用验证逻辑将这些组件封装在另一个组件中。例如&lt;UserForm&gt;。但是由于每个表单都不同,因此必须在&lt;CategoryForm&gt;&lt;GoupForm&gt; 等中复制相同的逻辑。这非常低效,这就是为什么我想将验证逻辑封装在&lt;Form&gt; 中并传递&lt;Input&gt; 的引用组件到&lt;Form&gt;

【问题讨论】:

  • 我不确定您为什么要这样做,但最好还是考虑一下如何传递您想要的数据。 React 倾向于根据数据进行渲染,而不是根据渲染结果进行渲染,因此最好将数据放在更高的级别,以便可以将其传递给所有需要它的组件。
  • 因为我需要传递组件的实例,这样我才能调用它的方法。使用回调引用不违反规则:facebook.github.io/react/docs/more-about-refs.html
  • 啊,感谢您的澄清。对于那个特定的用例,我建议使用context,它旨在解决这个问题。 facebook.github.io/react/docs/context.html
  • 我说得太早了,上下文并不能解决你的问题。上下文允许孩子从树的更高层引用类型化的“属性”,而无需专门传递这些属性。它可以帮助编写通用组件。执行此操作的“反应”方式是将表单状态移出 Input 组件。考虑有一个代表你的通用数据和验证逻辑的类——这个类封装了一个输入组件,但从父级传递下来。由于表单有这些实例的列表,它可以忽略子组件本身。

标签: javascript reactjs


【解决方案1】:

一般来说,“ref”特性是 React 中的一种反模式。它的存在是为了实现副作用驱动的开发,但是为了从 React 编程方式中获益最多,您应该尽可能避免使用“refs”。

至于您的特定问题,将 ref 传递给它的兄弟姐妹是鸡与蛋的场景。 ref 回调在子级挂载时触发,而不是在渲染期间触发,这就是您的示例不起作用的原因。您可以尝试的一件事是将 ref 推入状态,然后从状态读取到另一个孩子。所以:

<One ref={c => !this.state.one && this.setState({ one: c })}/>
<Two one={this.state.one}/>

注意:如果没有!this.state.one,这将导致无限循环。

这里是这个工作的 codepen 示例(查看控制台以查看记录的同级 ref):http://codepen.io/anon/pen/pbqvRA

【讨论】:

  • 我更新了代码以仅显示包含!this.state.one 的解决方案,它确实有效。需要 setState 的原因是父组件需要在 ref 回调触发之后呈现。此外,这可能会导致非常奇怪的行为,因为每次 React 渲染它都会在技术上构造一个新的 ref 对象。
  • 我接受这个,因为它确实回答了这个问题。
  • 伙计们,我现在不行了。检查小提琴,看看有没有说超出最大调用堆栈的错误。任何其他想法如何解决它?
  • 还要注意我的评论中我是如何谈论无限循环预防的。 “注意:没有 !this.state.one 这将导致无限循环。”
  • Kevin.groat 在下面回答了 React 16 及更高版本中更好的解决方案
【解决方案2】:

现在使用 new ref api(从 React 16 开始可用 - 感谢 perilandmishap 指出这一点)变得更加简单。

class MyComponent extends React.Component {
  constructor (props) {
    super(props);
    this.oneRef = React.createRef();
  }

  render () {
    return (
      <React.Fragment>
        <One ref={this.oneRef} />
        <Two one={this.oneRef} />
      </React.Fragment>
    }
  }
}

你会使用 Two 中的道具,比如:

this.props.one.current

这种方法的一些注意事项:

ref 将是一个具有current 属性的对象。该属性将是null 直到元素/组件被安装。安装后,它将成为One 的实例。 应该在安装 &lt;Two /&gt; 后安全地引用它。

卸载&lt;One /&gt; 实例后,引用上的current 属性将恢复为null

【讨论】:

  • 是的,新的裁判非常可爱,但请注意,这仅适用于 > react 16
  • 只有在&lt;One /&gt;&lt;Two/&gt; 之前初始化时才有效。例如,如果您将&lt;Two/&gt; 设为&lt;One/&gt; 的子级,那么Two 将始终收到null,即使它位于componentDidMount 内部。您需要以某种方式告诉Two One 已挂载。
【解决方案3】:

一般来说,如果你需要传递一个在调用时可能没有设置的引用,你可以传递一个 lambda:

<One ref={c => this.one = c}/>
<Two one={() => this.one}/>

然后将其引用为

this.props.one()

如果在你调用它的时候已经设置了,你会得到一个值。在此之前,你会得到undefined(假设它没有被初始化)。

值得注意的是,当它可用时您不一定会重新渲染,我希望它在第一次渲染时是undefined。这是使用状态来保存您的引用确实可以处理的事情,但您不会获得超过一次的重新渲染。

考虑到所有这些,我建议将使用 Two 中的 One 引用的任何代码移到呈现 OneTwo 的组件中,以避免这两种策略的所有问题,和@Carl Sverre 的答案中的那个。

【讨论】:

    猜你喜欢
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-23
    • 2012-04-19
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多