在 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} />
}
});
我知道您可以使用受控组件来传递输入的值,但是传递整个 kit n' kaboodle 会很好。有时子组件包含一组您不想查找的信息。
也许有一种方法可以将组件绑定到事件?
更新 - 2015 年 9 月 1 日
在使用 React 一年多之后,在 Sebastien Lorber 的回答的推动下,我得出结论,将子组件作为参数传递给父函数实际上不是 React 方式,也不是一个好主意。我已经换了答案。
编辑:查看 ES6 更新示例的结尾示例。
这个答案只是处理直接父子关系的情况。当父母和孩子可能有很多中介时,请检查此answer。
其他解决方案没有抓住重点
虽然它们仍然可以正常工作,但其他答案缺少一些非常重要的东西。
在 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);
}
});
带有孩子列表的父母:您仍然拥有父母所需的一切,并且不需要让孩子变得更复杂。
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);
}
});
也可以使用 this.handleChildClick.bind(null,childIndex)
然后使用 this.state.childrenData[childIndex]
请注意,我们绑定的是 null
上下文,否则 React 会发出与其 autobinding 系统相关的警告。使用 null 意味着您不想更改函数上下文。 See also。
关于其他答案中的封装和耦合
就耦合和封装而言,这对我来说是个坏主意:
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 节点声明 refs。使用孩子的 ref 的父母意味着您将这两个组件紧密耦合。
为了说明这个问题,我将引用关于 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>
)
PERF WARNING(适用于 ES5/ES6):如果您使用 PureComponent
或 shouldComponentUpdate
,则默认情况下不会优化上述实现,因为使用 onClick={e => doSomething()}
,或者在渲染期间直接绑定阶段,因为它会在每次父渲染时创建一个新函数。如果这是您的应用程序中的性能瓶颈,您可以将数据传递给子项,并在“稳定”回调中重新注入(在父类上设置,并在类构造函数中绑定到 this
)以便 PureComponent
优化可以启动,或者您可以实现自己的 shouldComponentUpdate
并忽略道具比较检查中的回调。
您还可以使用 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)
更新(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);
},
但问题本身具有误导性
家长已经知道孩子的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 的 props 是什么变得更清楚了。
如果真的是关于使用儿童道具……
如果它真的是关于使用孩子的道具,你可以完全避免与孩子的任何联系。
JSX 有一个 spread attributes API,我经常在 Child 等组件上使用。它采用所有 props
并将它们应用于组件。孩子看起来像这样:
var Child = React.createClass({
render: function () {
return <a {...this.props}> {this.props.text} </a>;
}
});
允许您直接在 Parent 中使用这些值:
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} />;
}
});
当您连接额外的子组件时,不需要额外的配置
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>;
}
});
但我怀疑这不是你的实际用例。所以让我们进一步挖掘......
一个强大的实际例子
所提供示例的通用性很难谈论。我创建了一个组件,演示了上述问题的实际用途,以非常 Reacty 的方式实现:
DTServiceCalculator working example
DTServiceCalculator repo
该组件是一个简单的服务计算器。您向它提供服务列表(带有名称和价格),它将计算所选价格的总和。
孩子们幸福地无知
ServiceItem
是本示例中的子组件。它对外界没有太多的看法。它requires a few props,其中之一是单击时要调用的函数。
<div onClick={this.props.handleClick.bind(this.props.index)} />
它只使用提供的 index
[source] 调用提供的 handleClick
回调。
父母是孩子
DTServicesCalculator
是本示例的父组件。这也是一个孩子。我们看看吧。
DTServiceCalculator
创建子组件 (ServiceItem
) 列表并为它们提供道具 [source]。它是 ServiceItem
的父组件,但它是向其传递列表的组件的子组件。它不拥有数据。因此它再次将组件的处理委托给其父组件 source
<ServiceItem chosen={chosen} index={i} key={id} price={price} name={name} onSelect={this.props.handleServiceItem} />
handleServiceItem
捕获从子级传递的索引,并将其提供给其父级 [source]
handleServiceClick (index) {
this.props.onSelect(index);
}
业主什么都知道
“所有权”的概念是 React 中的一个重要概念。我建议阅读有关它的更多信息here。
在我展示的示例中,我一直将事件处理委托给组件树,直到我们到达拥有该状态的组件。
当我们最终到达那里时,我们会像这样处理状态选择/取消选择 [source]:
handleSelect (index) {
let services = […this.state.services];
services[index].chosen = (services[index].chosen) ? false : true;
this.setState({ services: services });
}
结论
尽量保持最外层的组件不透明。努力确保他们对父组件如何选择实现它们的偏好很少。
请注意谁拥有您正在操作的数据。在大多数情况下,您需要将事件处理委托给拥有该状态的组件。
除此之外:Flux pattern 是减少应用中此类必要连接的好方法。
Parent
可能是这样一个控制器视图,而 Child
只是一个哑视图(组件)。只有控制器视图应该了解应用程序。在这种情况下,您仍会将动作从 Parent
传递到 Child
作为道具。就 Action 本身而言,Flux 有一个强烈规定的模式,用于在 Action 之间进行交互以更新 View。 facebook.github.io/flux/docs/…
onClick={this.onClick.bind(null, this.state.text)}
无需像父级那样绑定状态。所以只需 onClick={this.onClick}
就可以了。 onClick = ()=>{const text = this.state.text;..}
在哪里
onClick
中的 this
设置为 null
,并将 event
参数设置为 this
:this.props.onClick.bind(null, this)
。这是错误的,至少有两个完全不同的原因。
似乎有一个简单的答案。考虑一下:
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 函数接受参数 component
和 event
。我认为这是世界上最好的。
更新:2015 年 9 月 1 日
这是一个坏主意:让子实现细节泄露给父级绝不是一条好路。请参阅塞巴斯蒂安·洛伯的回答。
this
(无论如何都不需要),第二个参数在调用 onClick 时作为第一个参数添加。
问题是如何将参数从子组件传递给父组件。此示例易于使用和测试:
//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")
);
}
基本上,您使用道具将信息发送给孩子和父母。
除了所有精彩的答案之外,让我举一个简单的例子来解释 React 中从子组件到父组件的传递值
应用程序.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>
);
}
}
页眉.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>
);
}
}
主.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');
}
这就是您将值从客户端推送到服务器的方式
这是在父构造函数中使用函数绑定的简单 3 步 ES6 实现。这是官方 react 教程推荐的第一种方式(这里也没有介绍公共类字段语法)。您可以在此处找到所有这些信息 https://reactjs.org/docs/handling-events.html
绑定父函数,以便子函数可以调用它们(并将数据传递给父函数!:D)
确保在父构造函数中绑定您在父构造函数中创建的函数将绑定函数作为道具传递给子函数(没有 lambda,因为我们将 ref 传递给函数)从子事件调用绑定函数(Lambda!我们'在事件触发时调用函数。如果我们不这样做,函数将在加载时自动运行,而不是在事件时触发。)
父函数
handleFilterApply(filterVals){}
父构造函数
this.handleFilterApply = this.handleFilterApply.bind(this);
传递给孩子的道具
onApplyClick = {this.handleFilterApply}
子事件调用
onClick = {() => {props.onApplyClick(filterVals)}
这是一个不使用 onClick 事件的示例。我只是通过道具将一个回调函数传递给孩子。使用该回调,子调用也会发回数据。 docs 中的示例启发了我。
小例子(这是在一个 tsx 文件中,所以 props 和 states 必须完全声明,我删除了组件中的一些逻辑,所以它的代码更少)。
*更新:重要的是将此绑定到回调,否则回调具有子范围而不是父级。唯一的问题:它是“老”父母......
SymptomChoser 是父级:
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>
);
}
}
症状是孩子(在孩子的道具中我声明了回调函数,在函数 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>
);
}
}
不定期副业成功案例分享
props
上的函数将值(而不是组件)传递回调用链。同意?How do I know which of my children was chosen among a group?
这并不是原始问题的一部分,但我在回答中包含了解决方案。与您的编辑不同,我认为根本不需要修改子项,即使在处理列表时也是如此,因为您可以直接在父项中使用bind
。onClick={this.handleChildClick.bind(null,childData)}
,这是否仍然适用,因为 React 尚未包含在 class model 中。我读过很多东西,但这部分仍然让我感到困惑。顺便说一句,我正在使用 ES6class model
,所以我将null
更改为this
,我的代码似乎仍然有效。你会如何将你的那部分代码翻译成 ES6 风格?