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


295

props在React.js中,没有一种简单的方法可以通过事件将孩子的孩子传递给父母吗?

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

使用后反应了一年多,和塞巴斯蒂安·洛伯的回答的刺激下,我已经得出结论传递子组件作为参数传递给函数的父母是不是事实上的方式做出反应,也不是永远的好主意。我已经换了答案。


对于类似的问题(在这里这里),有各种答案,但似乎没有一个特别优雅
Bojangles 2014年

我同意–将事件沿链传递是很棒的,但是要确定是哪个组件引发了该事件真是太棒了。
KendallB 2014年

请看一下我的答案,因为我认为所接受的答案还不够好;stackoverflow.com/a/31756470/82609
塞巴斯蒂安·洛伯

1
kit n'kaboodle-包括所有东西。得到所有的钟声。联合体。urbandictionary.com/define.php?term=kit%20and%20caboodle
Filip Bartuzi

Answers:


268

编辑:请参见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会发出与其自动绑定系统相关的警告。使用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)

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

该组件具有一个接口(props),并且父组件不应承担任何有关子组件内部工作的事情,包括其内部DOM结构或为其声明引用的DOM节点。父母使用孩子的ref意味着您将这两个组件紧密地结合在一起。

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

他们在Web开发人员可以访问的范围和所认为的实现细节之间建立了界限,因此您无法访问。但是,浏览器可以随意跨越此边界。有了这个边界,他们就可以使用相同的古老Web技术来构建所有HTML元素,就像在您的div和跨度中一样。

问题是,如果让子实现细节泄漏到父中,那么很难重构子而不影响父。这意味着作为库作者(或使用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):如果使用PureComponentshouldComponentUpdate,则默认情况下不会优化上述实现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)

在这种情况下,您可以使用以下方法优化Child组件:

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

1
是的,我在使用React的最后一年中,我同意–没有充分的理由通过函数传递实例化的组件。我将弃用自己的答案。我也愿意更改答案,也许还要进行一些修改:您的“更好的实现方式”并未解决出现此问题的实际原因,即“我怎么知道我的哪个孩子是从一个小组中选出来的?” Flux是任何大小合适的应用程序的答案,但是,在小型应用程序中,我很高兴通过上的函数将值(而非组件)传递回调用链props。同意?
KendallB

@KendallB很高兴您同意我的回答:) How do I know which of my children was chosen among a group?这并不是原始问题的一部分,但是我在回答中包含了解决方案。与您的编辑不同,我认为根本不需要修改子级,即使在处理列表时也是如此,因为您可以bind直接在父级中使用。
Sebastien Lorber

我对您的答案进行了编辑,其中涉及裁判。omerwazir.com/posts/react-getdomnode-replaced-with-findDOMNode
ffxsam 2015年

嗨,@ SebastienLorber,我来自未来。关于onClick={this.handleChildClick.bind(null,childData)}您提到的关于自动绑定的这一行,既然React尚未包含在类模型中,这是否仍然适用?我读了很多东西,但这部分仍然让我感到困惑。class model顺便说一下,我正在使用ES6 ,所以我改成nullthis,我的代码似乎仍然有效。您如何将那部分代码转换为ES6样式?
JohnnyQ

我也在努力使它在最新的React中工作,需要改变什么?
侯赛因·杜维尼奥

143

更新(2015年9月1日):OP使这个问题成为了一个移动目标。再次更新。因此,我有责任更新我的回复。

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

是的,这是可能的。

您可以通过将Child's更新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>;
  }
});

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

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

JSBin快照


但问题本身是误导的

父母已经知道孩子的了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快照


如果真的是关于使用儿童道具的话……

如果确实要使用儿童道具,则可以完全避免与儿童进行任何联播。

JSX具有我经常在诸如Child之类的组件上使用的扩展属性 API。它接受所有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回购

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

孩子们幸福无知

ServiceItem是此示例中的子组件。它对外界的看法不多。它需要几个道具,其中一个是单击时要调用的函数。

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

除了handleClick使用提供的index[ source ] 调用提供的回调外,它什么也不做。

父母是孩子

DTServicesCalculator此示例是父组件。这也是一个孩子。我们看看吧。

DTServiceCalculator创建子组件列表,ServiceItem并为他们提供道具[ source ]。它是的父组件,ServiceItem但是将列表传递给它的组件的子组件。它不拥有数据。因此,它再次将组件的处理委托给其父组件

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

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

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模式是减少应用程序中这种必要的连接的好方法。


感谢您的简洁回答,它确实帮助我更好地了解了React!我也有同样的问题。我正在使用Flux进行发布/订阅。不必像您的示例那样将Child事件处理程序传递给Parent,而是可以将其实现为“操作”并监听它。您是否认为这是使用Flux的好选择?
Pathsofdesign 2014年

1
谢谢@Pathsofdesign!这要看情况。Flux具有控制器视图的概念。在此示例中,Parent可能是这样的Controller-View,而Child仅仅是dum-View(组件)。只有Controller-View才应了解该应用程序。在这种情况下,您仍然可以将Action从Parent传递Child为道具。就动作本身而言,Flux具有在动作之间进行交互以更新视图的强烈规定的模式。facebook.github.io/flux/docs/...
chantastic

1
onClick={this.onClick.bind(null, this.state.text)}无需绑定状态,因为父级拥有该状态。这样就onClick={this.onClick}可以了。onClick = ()=>{const text = this.state.text;..}在父母那里
Shishir Arora

25

似乎有一个简单的答案。考虑一下:

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

关键是调用bind(null, this)this.props.onClick事件,从父通过。现在,onClick函数接受参数componentAND event。我认为这是世界上最好的。

更新:2015年9月1日

这是一个坏主意:让子实现细节泄漏给父代绝不是一个好方法。请参阅Sebastien Lorber的答案。


是否以错误的方式(交换位置)处理了论点?
Surrican'2

1
@TheSurrican不,很好。第一个参数绑定到this函数内(无论如何都不需要),第二个参数在调用onClick时作为第一个参数添加。
manmal 2015年

13

问题是如何将参数从子组件传递到父组件。该示例易于使用和测试:

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


就我而言,它忘记了将回调函数传递给click()
Long Nguyen

6

基本上,您使用道具来向儿童和父母发送信息。

除了所有精彩的答案外,让我举一个简单的示例,解释在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');
  }

这是您将值从客户端推送到道具到服务器的方式


我可以知道吗,这就是您如何将值从客户端推送到props到服务器的内容,这是什么意思?
G1P 2016年

2

这是在父构造函数中使用函数绑定的简单3步ES6实现。这是官方反应教程推荐的第一种方式(这里也没有涵盖公共类字段的语法)。您可以在这里找到所有这些信息https://reactjs.org/docs/handling-events.html

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

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

父功能

handleFilterApply(filterVals){} 

家长建设者

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

道具传递给孩子

onApplyClick = {this.handleFilterApply}

儿童活动电话

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

0

这是一个不使用onClick事件的示例。我只是通过道具将回调函数传递给孩子。通过该回调,子调用也将数据发送回去。我受到docs中示例的启发。

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

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

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>
    );
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.