在React.js中将道具传递给父组件

在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} />
}
});

我知道你可以使用受控组件来传递输入的值,但最好是传递整个套件。有时子组件包含一组您不愿意查找的信息。

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

更新日期:2015年9月1日

在使用React一年多之后,在Sebastien Lorber的回答的刺激下,我得出结论,将子组件作为参数传递给父函数是实际上是React的方式,这从来都不是一个好主意。我把答案换了。

389532 次浏览

答案似乎很简单。考虑一下:

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


var Parent = React.createClass({
onClick: function(component, event) {
component.props // #=> {Object...}
},
render: function() {
<Child onClick={this.onClick} />
}
});

关键是在从父类传递的this.props.onClick事件上调用bind(null, this)。现在,onClick函数接受参数componentevent。我认为这是最好的选择。

更新:9/1/2015

这是一个坏主意:让子实现细节泄露到父实现从来都不是一个好方法。见塞巴斯蒂安·洛伯的回答。

更新(2015年9月1日):OP已经使这个问题成为一个移动的目标。又更新了。所以,我觉得有责任更新我的回复。

首先,回答你提供的例子:

是的,这是可能的。

你可以通过将Child的onClick更新为this.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 snapshot .


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

父节点已经知道子节点的props

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

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 snapshot .


如果真的是用孩子的道具……

如果它真的是关于使用一个孩子的道具,你可以完全避免与孩子的任何联系。

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 snapshot .

< p >
当您连接其他子组件时,不需要额外的配置

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 snapshot .

但我怀疑这不是你的实际用例。所以让我们进一步挖掘……


一个健壮的实际示例

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

< p > DTServiceCalculator工作示例 < br > DTServiceCalculator回购 < / p >

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

孩子们是幸福的无知

ServiceItem是本例中的子组件。它对外界没有太多看法。它需要一些道具,其中一个是单击时要调用的函数。

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

它只使用所提供的index[]调用所提供的handleClick回调。

父母就是孩子

DTServicesCalculator是本例中的父组件。它也是一个孩子。让我们看。

DTServiceCalculator创建了一个子组件列表(__abc1),并为它们提供了props []。它是ServiceItem的父组件,但它是传递给它列表的组件的子组件。它不拥有数据。因此,它再次将组件的处理委托给它的父组件

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

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

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

主人无所不知

“所有权”的概念在React中很重要。我建议阅读更多关于它的资料。

在我所展示的示例中,我一直将事件的处理委托给组件树,直到我们到达拥有状态的组件。

当我们最终到达那里,我们像这样处理状态选择/取消选择[]:

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


结论

试着让你最外层的组件尽可能不透明。努力确保它们对于父组件可能选择如何实现它们的偏好很少。

请注意谁拥有您正在操作的数据。在大多数情况下,你需要将事件处理委托给状态为拥有的组件。

旁白: flux pattern是减少应用程序中这种必要连接的好方法。

编辑: 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]

注意,我们绑定了一个null上下文,否则React会发出一个与autobinding系统相关的警告。使用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} />
}
});
< p > 使用道具: 正如我上面所解释的,你已经在父组件中有了props,所以传递整个子组件来访问props是没有用的 < p > 使用参考文献: 在事件中已经有了单击目标,在大多数情况下这就足够了。 另外,你可以直接在子对象上使用一个ref:

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

并访问父节点中的DOM节点

React.findDOMNode(this.refs.theChild)

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

组件有一个接口(props),父组件不应该假设子组件的内部工作,包括它的内部DOM结构或它声明引用的DOM节点。父组件使用子组件的ref意味着两个组件紧密耦合。

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

他们在您Web开发人员所能达到的范围之间创建了一个边界 以及所谓的实现细节,因此无法访问 你。然而,浏览器可以随意跨越这个边界。 有了这个边界,他们就能够构建所有HTML元素 使用相同的优秀的旧Web技术,脱离了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


性能警告(适用于ES5/ES6):如果你使用PureComponentshouldComponentUpdate,上述实现将不会在默认情况下进行优化,因为使用onClick={e => doSomething()},或在呈现阶段直接绑定,因为它将在每次父函数呈现时创建一个新函数。如果这是你的应用程序中的性能瓶颈,你可以将数据传递给孩子,并在“稳定”回调中重新注入它(在父类上设置,并在类构造函数中绑定到this),以便PureComponent优化可以发挥作用,或者你可以实现自己的shouldComponentUpdate并忽略道具比较检查中的回调。

你也可以使用重新安排库,它提供了更高阶的组件来实现微调优化:

// 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)

基本上,你使用props向子节点和父节点发送信息。

除了这些精彩的答案之外,让我举一个简单的例子来解释在React中从子组件向父组件传递值的原因

App.js

class App extends React.Component {
constructor(){
super();
this.handleFilterUpdate = this.handleFilterUpdate.bind(this);
this.state={name:'igi'}
}
handleFilterUpdate(filterValue) {
this.setState({
name: filterValue
});
}
render() {
return (
<div>
<Header change={this.handleFilterUpdate} name={this.state.name} />
<p>{this.state.name}</p>
</div>
);
}
}

Header.js

class Header extends React.Component {
constructor(){
super();
this.state={
names: 'jessy'
}
}
Change(event) {


// this.props.change(this.state.names);
this.props.change('jessy');
}


render() {
return (
<button onClick={this.Change.bind(this)}>click</button>


);
}
}

Main.js

import React from 'react';
import ReactDOM from 'react-dom';


import App from './App.jsx';


ReactDOM.render(<App />, document.getElementById('app'));

就是这样,现在你可以从你的客户端传递值到服务器。

看看Header.js中的Change函数

Change(event) {
// this.props.change(this.state.names);
this.props.change('jessy');
}

这就是如何将值推入从客户机到服务器的道具中

问题是如何将参数从子组件传递给父组件。这个例子很容易使用和测试:

//Child component
class Child extends React.Component {
render() {
var handleToUpdate  =   this.props.handleToUpdate;
return (<div><button onClick={() => handleToUpdate('someVar')}>Push me</button></div>
)
}
}


//Parent component
class Parent extends React.Component {
constructor(props) {
super(props);
var handleToUpdate  = this.handleToUpdate.bind(this);
}


handleToUpdate(someArg){
alert('We pass argument from Child to Parent: \n' + someArg);
}


render() {
var handleToUpdate  =   this.handleToUpdate;
return (<div>
<Child handleToUpdate = {handleToUpdate.bind(this)} />
</div>)
}
}


if(document.querySelector("#demo")){
ReactDOM.render(
<Parent />,
document.querySelector("#demo")
);
}

看JSFIDDLE

下面是一个简单的3步ES6实现,使用父构造函数中的函数绑定。这是react官方教程推荐的第一种方法(这里还有公共类字段语法没有涉及)。你可以在这里找到所有这些信息https://reactjs.org/docs/handling-events.html

绑定父函数,以便子函数可以调用它们(并将数据传递给父函数!: D)

  1. 确保在父构造函数中绑定了在父构造函数中创建的函数
  2. 将绑定函数作为prop向下传递给子函数(没有lambda,因为我们将引用传递给函数)
  3. 从子事件调用绑定函数(Lambda!我们在事件触发时调用函数。 如果我们不这样做,函数将在加载时自动运行,而不会在事件时被触发。

父函数

handleFilterApply(filterVals){}

父类构造函数

this.handleFilterApply = this.handleFilterApply.bind(this);

传给孩子的道具

onApplyClick = {this.handleFilterApply}

子事件调用

onClick = {() => {props.onApplyClick(filterVals)}

这是一个没有使用onClick事件的例子。我只是通过props将一个回调函数传递给子函数。通过那个回调,子调用也发送回数据。我受到了文档中的例子的启发。

小例子(这是在tsx文件中,所以道具和状态必须完全声明,我删除了组件中的一些逻辑,所以代码更少)。

*更新:重要的是将此绑定到回调,否则回调的范围是子函数而不是父函数。唯一的问题是:这是“老”父母…

症候选择器是父对象:

interface SymptomChooserState {
// true when a symptom was pressed can now add more detail
isInDetailMode: boolean
// since when user has this symptoms
sinceDate: Date,
}


class SymptomChooser extends Component<{}, SymptomChooserState> {


state = {
isInDetailMode: false,
sinceDate: new Date()
}


helloParent(symptom: Symptom) {
console.log("This is parent of: ", symptom.props.name);
// TODO enable detail mode
}


render() {
return (
<View>
<Symptom name='Fieber' callback={this.helloParent.bind(this)} />
</View>
);
}
}

Symptom是child(在child的props中我声明了回调函数,在函数selectedSymptom中调用了回调函数):

interface SymptomProps {
// name of the symptom
name: string,
// callback to notify SymptomChooser about selected Symptom.
callback: (symptom: Symptom) => void
}


class Symptom extends Component<SymptomProps, SymptomState>{


state = {
isSelected: false,
severity: 0
}


selectedSymptom() {
this.setState({ isSelected: true });
this.props.callback(this);
}


render() {
return (
// symptom is not selected
<Button
style={[AppStyle.button]}
onPress={this.selectedSymptom.bind(this)}>
<Text style={[AppStyle.textButton]}>{this.props.name}</Text>
</Button>
);
}
}