将 props 传递给 React 中的父组件.js其他解决方案没有抓住重点更好的实施关于其他答案中的封装和耦合首先,回答您提供的示例:但这个问题本身具有误导性。一个可靠的实际示例结论

2022-08-29 23:48:38

在 React.js 中,难道没有一种简单的方法可以使用事件将孩子传递给其父级吗?props

var Child = React.createClass({
  render: function() {
    <a onClick={this.props.onClick}>Click me</a>
  }
});

var Parent = React.createClass({
  onClick: function(event) {
    // event.component.props ?why is this not available?
  },
  render: function() {
    <Child onClick={this.onClick} />
  }
});

我知道你可以使用受控组件来传递输入的值,但是传递整个套件n'kaboodle会很好。有时,子组件包含一组您不希望查找的信息。

也许有一种方法可以将组件绑定到事件?

更新 – 9/1/2015

在使用 React 一年多之后,在 Sebastien Lorber 的回答的推动下,我得出的结论是,将子组件作为父函数的参数传递给父母的方式实际上不是 React 的方式,也不是一个好主意。我已经切换了答案。


答案 1

编辑:请参阅 ES6 更新示例的结束示例。

这个答案只是处理直接亲子关系的情况。当父母和孩子可能有很多中介时,请检查这个答案

其他解决方案没有抓住重点

虽然它们仍然工作正常,但其他答案缺少一些非常重要的东西。

在 React 中,难道没有一种简单的方法可以使用事件将孩子的道具传递给其父级.js吗?

父母已经拥有了那个子道具!:如果孩子有一个道具,那么这是因为它的父方向孩子提供了那个道具!为什么你希望孩子把道具传回给父母,而父母显然已经拥有了那个道具?

更好的实施

孩子:真的不一定比这更复杂。

var Child = React.createClass({
  render: function () {
    return <button onClick={this.props.onClick}>{this.props.text}</button>;
  },
});

具有单个子项的父项:使用传递给子项的值

var Parent = React.createClass({
  getInitialState: function() {
     return {childText: "Click me! (parent prop)"};
  },
  render: function () {
    return (
      <Child onClick={this.handleChildClick} text={this.state.childText}/>
    );
  },
  handleChildClick: function(event) {
     // You can access the prop you pass to the children 
     // because you already have it! 
     // Here you have it in state but it could also be
     //  in props, coming from another parent.
     alert("The Child button text is: " + this.state.childText);
     // You can also access the target of the click here 
     // if you want to do some magic stuff
     alert("The Child HTML is: " + event.target.outerHTML);
  }
});

JsFiddle

父母有孩子列表:你仍然在父母身上有你需要的一切,不需要让孩子更复杂。

var Parent = React.createClass({
  getInitialState: function() {
     return {childrenData: [
         {childText: "Click me 1!", childNumber: 1},
         {childText: "Click me 2!", childNumber: 2}
     ]};
  },
  render: function () {
    var children = this.state.childrenData.map(function(childData,childIndex) {
        return <Child onClick={this.handleChildClick.bind(null,childData)} text={childData.childText}/>;
    }.bind(this));
    return <div>{children}</div>;
  },

  handleChildClick: function(childData,event) {
     alert("The Child button data is: " + childData.childText + " - " + childData.childNumber);
     alert("The Child HTML is: " + event.target.outerHTML);
  }
});

JsFiddle

也可以使用,然后使用this.handleChildClick.bind(null,childIndex)this.state.childrenData[childIndex]

请注意,我们与上下文绑定,因为否则 React 会发出与其自动绑定系统相关的警告。使用 null 意味着您不想更改函数上下文。另请参见null

关于其他答案中的封装和耦合

在耦合和封装方面,这对我来说是一个主意:

var Parent = React.createClass({
  handleClick: function(childComponent) {
     // using childComponent.props
     // using childComponent.refs.button
     // or anything else using childComponent
  },
  render: function() {
    <Child onClick={this.handleClick} />
  }
});

使用 props:正如我上面所解释的,你已经在父级中拥有 props,因此传递整个子组件来访问 props 是没有用的。

使用 refs:您已经在事件中拥有点击目标,在大多数情况下,这就足够了。此外,您可以直接在孩子上使用ref:

<Child ref="theChild" .../>

并访问父节点中的 DOM 节点

React.findDOMNode(this.refs.theChild)

对于更高级的情况,您希望访问父级中子级的多个 ref,子级可以直接在回调中传递所有 dom 节点。

该组件具有一个接口(props),父级不应假定有关子级内部工作的任何信息,包括其内部 DOM 结构或它声明引用的 DOM 节点。使用子项引用的父项意味着您紧紧耦合了这两个组件。

为了说明这个问题,我将引用这句关于Shadow DOM的话,它在浏览器中用于渲染滑块,滚动条,视频播放器等内容:

它们在 Web 开发人员可以达到的内容和被视为实现细节的内容之间创建了一个边界,因此您无法访问。但是,浏览器可以随意跨越此边界。有了这个边界,他们就能够使用相同的旧Web技术构建所有HTML元素,就像你一样,从div和span中解脱出来。

问题是,如果让子实现细节泄漏到父级中,则很难在不影响父级的情况下重构子项。这意味着作为库作者(或作为具有Shadow DOM的浏览器编辑器),这是非常危险的,因为您让客户端访问太多,使得在不破坏追溯兼容性的情况下升级代码非常困难。

如果Chrome实现了其滚动条,允许客户端访问该滚动条的内部dom节点,这意味着客户端可能可以简单地破坏该滚动条,并且当Chrome在重构滚动条后执行其自动更新时,应用程序将更容易中断。相反,它们只允许访问一些安全的东西,例如使用CSS自定义滚动条的某些部分。

关于使用其他任何内容

在回调中传递整个组件是危险的,并且可能导致新手开发人员在父级内部执行非常奇怪的操作,例如调用或或为其分配新变量,从而使整个应用程序更难推理。childComponent.setState(...)childComponent.forceUpdate()


编辑:ES6示例

由于现在许多人使用ES6,以下是ES6语法的相同示例

孩子可以很简单:

const Child = ({
  onClick, 
  text
}) => (
  <button onClick={onClick}>
    {text}
  </button>
)

父级可以是一个类(它最终可以管理状态本身,但我在这里将其作为道具传递:

class Parent1 extends React.Component {
  handleChildClick(childData,event) {
     alert("The Child button data is: " + childData.childText + " - " + childData.childNumber);
     alert("The Child HTML is: " + event.target.outerHTML);
  }
  render() {
    return (
      <div>
        {this.props.childrenData.map(child => (
          <Child
            key={child.childNumber}
            text={child.childText} 
            onClick={e => this.handleChildClick(child,e)}
          />
        ))}
      </div>
    );
  }
}

但是,如果它不需要管理状态,它也可以简化:

const Parent2 = ({childrenData}) => (
  <div>
     {childrenData.map(child => (
       <Child
         key={child.childNumber}
         text={child.childText} 
         onClick={e => {
            alert("The Child button data is: " + child.childText + " - " + child.childNumber);
                    alert("The Child HTML is: " + e.target.outerHTML);
         }}
       />
     ))}
  </div>
)

JsFiddle


PERF 警告(适用于 ES5/ES6):如果您使用的是 或 ,则默认情况下不会优化上述实现,因为在渲染阶段直接使用 或 绑定,因为它会在每次父级渲染时创建一个新函数。如果这是应用中的 perf 瓶颈,则可以将数据传递给子级,并将其重新注入到“稳定”回调(在父类上设置,并绑定到类构造函数中),以便优化可以启动,或者您可以实现自己的数据并忽略 props 比较检查中的回调。PureComponentshouldComponentUpdateonClick={e => doSomething()}thisPureComponentshouldComponentUpdate

您还可以使用 Recompose 库,该库提供更高阶的组件来实现微调优化:

// A component that is expensive to render
const ExpensiveComponent = ({ propA, propB }) => {...}

// Optimized version of same component, using shallow comparison of props
// Same effect as React's PureRenderMixin
const OptimizedComponent = pure(ExpensiveComponent)

// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedComponent = onlyUpdateForKeys(['propA', 'propB'])(ExpensiveComponent)

在这种情况下,您可以使用以下命令优化子组件:

const OptimizedChild = onlyUpdateForKeys(['text'])(Child)

答案 2

更新(2015年9月1日):OP使这个问题有点像一个移动的目标。它已再次更新。所以,我觉得有责任更新我的回复。

首先,回答您提供的示例:

是的,这是可能的。

您可以通过将 Child's 更新为 :onClickthis.props.onClick.bind(null, this)

var Child = React.createClass({
  render: function () {
    return <a onClick={this.props.onClick.bind(null, this)}>Click me</a>;
  }
});

然后,Parent 中的事件处理程序可以访问组件和事件,如下所示:

  onClick: function (component, event) {
    // console.log(component, event);
  },

JSBin 快照


但这个问题本身具有误导性。

家长已经知道孩子的道具了

这在提供的示例中并不明确,因为实际上没有提供任何道具。此示例代码可能更好地支持所提出的问题:

var Child = React.createClass({
  render: function () {
    return <a onClick={this.props.onClick}> {this.props.text} </a>;
  }
});

var Parent = React.createClass({
  getInitialState: function () {
    return { text: "Click here" };
  },
  onClick: function (event) {
    // event.component.props ?why is this not available? 
  },
  render: function() {
    return <Child onClick={this.onClick} text={this.state.text} />;
  }
});

在这个例子中,你更清楚的是,你已经知道了Child的道具是什么。

JSBin 快照


如果真的是使用孩子的道具...

如果真的是使用孩子的道具,你可以完全避免与孩子勾搭。

JSX有一个跨度属性API,我经常在Child等组件上使用。它需要所有并将其应用于组件。孩子看起来像这样:props

var Child = React.createClass({
  render: function () {
    return <a {...this.props}> {this.props.text} </a>;
  }
});

允许您直接在父级中使用这些值:

var Parent = React.createClass({
  getInitialState: function () {
    return { text: "Click here" };
  },
  onClick: function (text) {
    alert(text);
  },
  render: function() {
    return <Child onClick={this.onClick.bind(null, this.state.text)} text={this.state.text} />;
  }
});

JSBin 快照


而且在连接其他子组件时不需要其他配置

var Parent = React.createClass({
  getInitialState: function () {
    return {
      text: "Click here",
      text2: "No, Click here",
    };
  },
  onClick: function (text) {
    alert(text);
  },
  render: function() {
    return <div>
      <Child onClick={this.onClick.bind(null, this.state.text)} text={this.state.text} />
      <Child onClick={this.onClick.bind(null, this.state.text2)} text={this.state.text2} />
    </div>;
  }
});

JSBin 快照

但我怀疑这不是你的实际用例。因此,让我们进一步挖掘...


一个可靠的实际示例

所提供示例的通用性很难谈论。我创建了一个组件,演示了上述问题的实际用途,并以非常 Reacty 的方式实现:

DTServiceCalculator 工作示例
DTServiceCalculator repo

此组件是一个简单的服务计算器。您向它提供服务列表(带有名称和价格),它将计算所选价格的总计。

孩子们幸福地无知

服务项是此示例中的子组件。它对外部世界没有太多看法。它需要一些道具,其中一个是单击时要调用的函数。

<div onClick={this.props.handleClick.bind(this.props.index)} />

它只执行任何操作,只需使用提供的 [source] 调用提供的回调。handleClickindex

父母是孩子

DTServicesCalculator 是此示例的父组件。它也是一个孩子。让我们来看看。

DTServiceCalculator创建子组件列表,并为其提供 props []。它是 的父组件,但它是向其传递列表的组件的子组件。它不拥有数据。因此,它再次将组件的处理委托给其父组件源。ServiceItemServiceItem

<ServiceItem chosen={chosen} index={i} key={id} price={price} name={name} onSelect={this.props.handleServiceItem} />

handleServiceItem捕获从子级传递的索引,并将其提供给其父级 [source]

handleServiceClick (index) {
  this.props.onSelect(index);
}

业主知道一切

“所有权”的概念在 React 中是一个重要的概念。我建议在这里阅读更多关于它的信息

在我演示的示例中,我不断将事件的处理委派到组件树上,直到我们到达拥有该状态的组件。

当我们最终到达那里时,我们处理状态选择/取消选择,如下所示[source]:

handleSelect (index) {
  let services = […this.state.services];
  services[index].chosen = (services[index].chosen) ? false : true;
  this.setState({ services: services });
}


结论

尽量保持最外部的组件尽可能不透明。努力确保他们对父组件如何选择实现它们几乎没有偏好。

了解谁拥有您正在操作的数据。在大多数情况下,您需要将树上的事件处理委托给拥有该状态组件。

题外话:Flux 模式是减少应用中此类必要连接的好方法。