【问题标题】:Pass props to parent component in React.js在 React.js 中将 props 传递给父组件
【发布时间】:2014-05-03 14:07:11
【问题描述】:

在 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 方式,也不是永远是个好主意。我换了答案。

【问题讨论】:

标签: javascript reactjs


【解决方案1】:

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

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 意味着您不想更改函数上下文。 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 是没有用的。

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

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

并使用

访问父节点中的DOM节点
React.findDOMNode(this.refs.theChild)

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

组件有一个接口(props),父组件不应该假设子组件的内部工作,包括它的内部 DOM 结构或它为哪些 DOM 节点声明 refs。使用孩子的 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>
)

JsFiddle


PERF WARNING(适用于ES5/ES6):如果你使用PureComponentshouldComponentUpdate,上述实现默认不会优化因为使用onClick={e =&gt; 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)

【讨论】:

  • 是的,我同意去年使用 React 的经历——没有充分的理由通过函数传递实例化的组件。我将弃用我自己的答案。我也愿意改变答案,也许有一些修改:您的“更好的实施”没有解决这个问题出现的实际原因,即“我怎么知道我的孩子中的哪个被选中?” Flux 是任何体面大小的应用程序的答案,但是,在小型应用程序中,我很高兴通过 props 上的函数将值(而不是组件)备份到调用链。同意吗?
  • @KendallB 很高兴您同意我的回答:) How do I know which of my children was chosen among a group? 这并不是原始问题的一部分,但我在回答中包含了解决方案。与您的编辑不同,我认为根本不需要修改子项,即使在处理列表时也是如此,因为您可以直接在父项中使用 bind
  • 我对您的答案进行了编辑,关于参考的部分。 omerwazir.com/posts/react-getdomnode-replaced-with-findDOMNode
  • 嗨@SebastienLorber 我来自未来。关于这一行 onClick={this.handleChildClick.bind(null,childData)} 你提到了 autobinding 现在这是否仍然适用,因为 React 没有包含在 class model 中。我读了很多东西,但这部分仍然让我感到困惑。顺便说一句,我正在使用 ES6 class model,所以我将 null 更改为 this,我的代码似乎仍然有效。你会如何将你的那部分代码翻译成 ES6 风格?
  • 我也在努力让它在最新的 React 中工作,需要改变什么?
【解决方案2】:

更新(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 的 props 是什么。

JSBin snapshot


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

如果真的是关于使用 Child 的道具,您可以完全避免与 Child 发生任何勾搭。

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

JSBin snapshot


连接其他子组件时无需额外配置

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 的方式实现:

DTServiceCalculator working example
DTServiceCalculator repo

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

孩子们幸福地无知

ServiceItem 是本例中的子组件。它对外界没有太多的看法。它requires a few props,其中一个是点击时调用的函数。

&lt;div onClick={this.props.handleClick.bind(this.props.index)} /&gt;

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

父母是孩子

DTServicesCalculator 是这个例子的父组件。这也是一个孩子。来看看吧。

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

&lt;ServiceItem chosen={chosen} index={i} key={id} price={price} name={name} onSelect={this.props.handleServiceItem} /&gt;

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 是减少应用中此类必要连接的好方法。

【讨论】:

  • 感谢简洁的回答,它确实帮助我更好地理解了 React!我有一个同样的问题。我将 Flux 用于 pub/sub。而不是像您的示例那样将 Child 事件处理程序传递给 Parent ,而是可以将其实现为“动作”并监听它。您认为这是 Flux 的一个很好的替代方案和用途吗?
  • 感谢@Pathsofdesign!这将取决于。 Flux 有这个 Controller-Views 的概念。在这个例子中,Parent 可能是这样一个控制器视图,而Child 只是一个哑视图(组件)。只有控制器视图应该了解应用程序。在这种情况下,您仍会将 Action 从 Parent 传递给 Child 作为道具。就 Action 本身而言,Flux 有一个强烈规定的模式,用于在 Action 之间进行交互以更新 View。 facebook.github.io/flux/docs/…
  • onClick={this.onClick.bind(null, this.state.text)} 不需要像父级那样绑定状态。所以只需onClick={this.onClick} 就可以了。 onClick = ()=&gt;{const text = this.state.text;..} 在哪里
【解决方案3】:

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

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。我认为这是世界上最好的。

更新:2015 年 9 月 1 日

这是个坏主意:让子实现细节泄露给父代绝不是一条好路。请参阅 Sebastien Lorber 的回答。

【讨论】:

  • 论点是否错误(交换了位置)?
  • @TheSurrican 不,很好。第一个参数绑定到函数内的this(无论如何都不需要),第二个参数在调用 onClick 时作为第一个参数添加。
【解决方案4】:

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

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

Look at JSFIDDLE

【讨论】:

  • 在我的情况下,它忘记将回调函数传递给 click()
【解决方案5】:

基本上,您使用道具向孩子和父母发送信息。

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

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

【讨论】:

  • 我想知道这是您将值从客户端推送到服务器的方式,您的意思是什么
【解决方案6】:

这是一个简单的 3 步 ES6 实现,在父构造函数中使用函数绑定。这是官方 react 教程推荐的第一种方式(这里也没有介绍公共类字段语法)。你可以在这里找到所有这些信息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)}

【讨论】:

    【解决方案7】:

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

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

    *更新:重要的是绑定 this 到回调,否则回调有子的范围而不是父的范围。唯一的问题:它是“旧”父母......

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

    症状是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>
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-30
      • 1970-01-01
      • 2019-06-20
      • 1970-01-01
      • 2018-12-28
      • 2019-08-09
      • 2018-08-11
      相关资源
      最近更新 更多