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

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

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

更新 – 9/1/2015

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


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

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函数接受参数组件和事件。我认为这是最好的选择。

更新:9/1/2015

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


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

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

是的,这是可能的。

你可以通过更新Child的onClick为this.props.onClick来解决这个问题。绑定(null,这):

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等组件上使用。它获取所有的道具并将它们应用到一个组件上。孩子看起来是这样的:

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快照

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

一个健壮的实际示例

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

DTServiceCalculator工作示例 DTServiceCalculator回购

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

孩子们是幸福的无知

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

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

它只使用所提供的索引[source]调用所提供的handleClick回调函数。

父母就是孩子

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

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

< serserceitem chosen={\ id}索引价格={\价格}handleServiceItem} - >

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

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


编辑: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);
  }
});

小提琴

带有子对象列表的父对象:您仍然拥有父对象上所需的所有内容,并且不需要使子对象更加复杂。

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]

注意,我们绑定的是一个空上下文,否则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} />
  }
});

使用道具: 正如我上面所解释的,你已经在父组件中有了道具,所以传递整个子组件来访问道具是没有用的。

使用参考文献: 在事件中已经有了单击目标,在大多数情况下这就足够了。 另外,你可以直接在子对象上使用一个ref:

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

并访问父节点中的DOM节点

React.findDOMNode(this.refs.theChild)

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

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

为了说明这个问题,我将引用Shadow 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>
)

小提琴


PERF WARNING (apply to ES5/ES6): if you are using PureComponent or shouldComponentUpdate, the above implementations will not be optimized by default because using onClick={e => doSomething()}, or binding directly during the render phase, because it will create a new function everytime the parent renders. If this is a perf bottleneck in your app, you can pass the data to the children, and reinject it inside "stable" callback (set on the parent class, and binded to this in class constructor) so that PureComponent optimization can kick in, or you can implement your own shouldComponentUpdate and ignore the callback in the props comparison check.

你也可以使用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)

基本上,你使用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)

确保在父构造函数中绑定了在父构造函数中创建的函数 将绑定函数作为prop向下传递给子函数(没有lambda,因为我们将引用传递给函数) 从子事件调用绑定函数(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>
    );
  }
}