React如何从this.props.children访问DOM节点

6
假设我有一张包含登录表单的卡片。
 <Card>
     <LoginForm/>
 </Card>

如何在 Card 渲染函数中访问表单中的节点?
<Form >
  <input type="text" name="email"/>
  <input type="password" name="password"/>
  <input type="submit"/>
 </Form>

因为我想做的是将提交按钮呈现在props.children上下文之外,而是将其包装在给定子元素之外!
render () {
return (
  <div className="card">
      <div className="inner">
        {/* render Children */}
        {this.props.children != undefined ?
          <div className="childrenWrapper">
            {this.props.children}
          </div>
          : ""
        }
       </div>
       {/* render submit from login form here, not above  */
  </div>)

example expected result

有一些组件实际上可以满足我的需求。例如来自react-toolbox的标签页组件。它们以某种方式在其他地方呈现标签页中的内容(子元素)。

举个例子。

   <Tabs index={this.state.inverseIndex} onChange={this.handleInverseTabChange} inverse>
      <Tab label='First'><small>First Content</small></Tab>
      <Tab label='Second'><small>Second Content</small></Tab>
      <Tab label='Third'><small>Third Content</small></Tab>
      <Tab label='Disabled' disabled><small>Disabled Content</small></Tab>
    </Tabs>

这将导致以下HTML代码: rendered html example

如您所见,选项卡中的子元素在自己的部分中呈现。我不想在表单上做任何更改来解决这个问题。我想将表单传递到 Card 中,并让 Card 决定如何在其渲染函数中呈现表单。

由于我正在尝试实现 Google 材质设计 组件 ,并仅将其用作模板,因此还需要拆分更多元素并将它们放置在我想要的位置。事实上,我可以将相关 HTML 放置在表单周围,以得到我想要的 Card,但那样的话我就根本不需要该组件了。


设置一个带有 flatButton 的变量,然后将其作为 props 传递给 Card 组件怎么样?啊,我忘记了:要访问 flatButton,我认为你可以使用 ref prop。 - dschu
@dschu 这样做是可以的,但问题在于 FlatButton 本身也在表单中,并且也会在子部分中呈现。 - Snackaholic
是的,你说得对。你需要向LoginForm组件传递另一个属性,告诉组件不要渲染按钮。在这种情况下,你应该考虑重新构建你的组件。 - dschu
但是如果我不想重新构建它们,因为我只想使用卡片组件进行不同的样式设计怎么办呢?问题在于,由于我处于响应式上下文中,我不能仅仅重新设计组件。我想提取给定子元素的子元素以操纵HTML结构以适应我的需求。 - Snackaholic
为什么不在表单内部放置一个 <hr />,然后在其后放置提交按钮呢?据我所知,表单只是一个带有“提交”功能的 div,因此它会查找具有 Name 属性的内容,并将这些值提交到请求中。个人而言,我会将卡片放在表单内部,而不是将表单放在卡片内部。 - Rachael Dawn
我知道你的意思,Jonathan。你希望我在表单中放置必要的HTML以获得我想要的结果,但这就是问题所在,需要找到一种不用那样处理的方法。 - Snackaholic
4个回答

4

这里有一些不错的答案,但是它们都没有直接回答你的问题。因此,尽管你应该重构你的代码(如下所述),我将为你提供一个可行的解决方案:

class Card extends React.Component {
  constructor() {
    super();
    this.state = {};
  }

  render() {
    console.log(typeof this.props.children)
    return (
      <div>
        {typeof this.props.children === 'object'
          ? React.cloneElement(this.props.children, { ref: (n) => this.form = n })
          : null}
        <button onClick={(e) => console.log(this.form.data)}>submit</button>
      </div>
    );
  }
}

class Form extends React.Component {
  constructor() {
    super();
    this.onChange = this.onChange.bind(this);
    this.state = {};
  }

  onChange(e) {
    this.data = e.target.value;
  }

  render() {
    return (
      <form>
        <input type="text" onChange={this.onChange} />
      </form>
    );
  }
}

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

https://jsbin.com/fohehogozo/edit?js,console,output

通过在实例上设置属性,您可以使用 ref 从子组件中访问该属性。我在这里检查 typeof === object,因为只有一个子组件。
警告:此代码不适用于生产环境。请勿在生产环境中运行此代码。我展示的代码是一种可怕的 hack,您不应该在家中尝试这种方法。

为什么我不应该使用这个解决方案,因为它似乎是确定我的子级类型的唯一方法?我的意思是,在这种情况下,我可以预期传入的子级,因此我可以简单地将它们作为属性交给它们,并像下面所述那样使用它们。但是,如果我不能预期传入的子级呢?为什么这不是一个好的解决方案? - Snackaholic
1
我在写这段代码时喝了两杯酒,其中很多代码可以被清理。但是话说回来,从概念上讲,它是正确的,尽管人们可以很容易地看出这样的代码可能难以维护。如果可以的话,重构为更符合惯用法的解决方案是有意义的。逃生口被称为逃生口是有原因的。 - natnai

0

如果您想要拆分传递的子元素,您可以简单地过滤子元素数组以拆分子元素,但是您的子元素似乎是嵌套的。

为什么不让卡片的子元素处理您的inner容器和其他内容之间的分离呢?

我认为在这种情况下重新构造比修改传递的子元素属性更合适。

此外,将提交按钮从实际的表单标签中拉出来会破坏您的表单,因为它将不再提交,除非在按钮和实际表单之间进行一些自定义连接。


0
如果您正在尝试提交表单,可以考虑传递一个onChange事件并将该字段的值(基于字段名称)存储在Card的状态中。然后,在输入上附加onChange事件,以便一旦它们被更新,数据将立即传回容器供您提交。

实际上,我正在尝试在卡片类的另一个位置呈现表单类的一部分。 - Snackaholic
1
只是出于好奇,你为什么需要这样做?它似乎是一种代码异味。 - J.B
因为我想正常渲染表单,如果在卡片样式中使用,则需要以不同的方式进行处理。不幸的是,我还必须更改HTML布局,但我有些困惑。 - Snackaholic
覆盖破坏组件的样式比将其从组件中删除要容易得多! - J.B
但是那样我就必须使用绝对定位,这在完全响应式的网站中有点不好。 - Snackaholic

-1

不要试图操纵DOM;在React中通常是一种反模式(尽管有一些有效的用例)。在您的情况下,与其试图直接移动元素,我会简单地隐藏表单中的按钮并将其添加到父级中。

假设您可以访问<LoginForm>的内部,您可以添加一个属性来隐藏按钮:

const button =
  <div class="flatbuttonWrapper">
    <input type="submit"/>
  </div>;

<Form>
  <input type="text" name="email"/>
  <input type="password" name="password"/>
  {!this.props.hideButton && button}
</Form>

将按钮添加到 Card 组件中:
render() {
  return (
    <div className="card">
      <div className="inner">
        {this.props.children != undefined ?
          <div className="childrenWrapper">
            {this.props.children}
          </div>
          : ""
        }
      </div>
      <div class="flatbuttonWrapper">
        <input type="submit"/>
      </div>
    </div>
  );
}

最后,在你的父级中:

<Card>
  <LoginForm hideButton />
</Card>

所有这些都说了,你真的需要更好地组织你的代码,并将其中一些组件分解成更小、更可重用的部分。例如,Card 组件可能不应该影响按钮的样式或有条件地渲染子元素;它只应该在任何子元素周围添加一个框架。然后,您可以创建一个更复杂的组件,将这些简单的子组件组合起来,以满足您的需求。

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