Передать реквизиты родительскому компоненту в React.js

241

Нет ли простого способа передать дочерний элемент 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

После использования React в течение года и под влиянием ответа Себастьяна Лорбера, я завершил передачу дочерних компонентов, поскольку аргументы в отношении функций в родителях на самом деле не являются способом React, и не было хорошей идеей. Я переключил ответ.

  • 0
    Существуют различные ответы на подобные проблемы ( здесь и здесь ), но ни один из них не кажется особенно элегантным
  • 0
    Я согласен - передача событий по цепочке - это замечательно, но было бы здорово узнать наверняка, какой компонент породил событие.
Показать ещё 2 комментария
Теги:

6 ответов

228
Лучший ответ

Изменить: см. примеры примеров для обновленных примеров 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 означает, что вы не хотите изменять контекст функции. См. также.

Об инкапсуляции и связи в других ответах

Это для меня идея bad в отношении связи и инкапсуляции:

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 node в родительском каталоге с

React.findDOMNode(this.refs.theChild)

Для более сложных случаев, когда вы хотите получить доступ к нескольким refs дочернего элемента в родительском, ребенок может передать все dom-узлы непосредственно в обратном вызове.

Компонент имеет интерфейс (реквизит), и родительский объект не должен принимать во внимание внутреннюю работу дочернего элемента, включая его внутреннюю структуру DOM или какие узлы DOM, для которых он объявляет refs. Родитель, использующий ссылку ref для ребенка, означает, что вы плотно соединяете 2 компонента.

Чтобы проиллюстрировать эту проблему, я приму эту цитату о Shadow DOM, который используется внутри браузеров для отображения таких вещей, как ползунки, полосы прокрутки, видеоплееры...:

Они создали границу между тем, что вы, веб-разработчик, можете достичь и что рассмотрели детали реализации, поэтому недоступны для вы. Однако браузер может переходить через эту границу по своему усмотрению. С этой границей они смогли построить все элементы HTML используя те же самые добрые веб-технологии, из разделов и пролетов как и вы.

Проблема заключается в том, что если вы позволите дочерним элементам реализации проникнуть в родительский элемент, вы сделаете очень сложным рефакторинг ребенка, не затрагивая родителя. Это означает, что в качестве автора библиотеки (или в качестве редактора браузера с 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): если вы используете 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 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)
  • 1
    Да, я провел прошлый год, используя React, я согласен - нет веской причины для передачи экземпляров компонентов с помощью функций. Я собираюсь осудить мой собственный ответ. Я также готов поменять ответ, возможно, с некоторыми изменениями: ваша «Лучшая реализация» не затрагивает фактическую причину, по которой возник этот вопрос, а именно: «Как я узнаю, кого из моих детей выбрали в группе?». Flux - это ответ для любого приложения приличного размера, однако в небольших приложениях было бы неплохо передавать значения (а не компоненты) обратно в цепочку вызовов с помощью функции props . Согласен?
  • 0
    @KendallB рад, что вы согласны с моим ответом :) How do I know which of my children was chosen among a group? на самом деле это не часть первоначального вопроса, но я включил решение в свой ответ. В отличие от вашего редактирования, я думаю, что совсем не нужно изменять дочерний объект, даже когда он работает со списком, поскольку вы можете использовать bind непосредственно в родительском элементе.
Показать ещё 4 комментария
133

Обновление (9/1/15): ОП поставил этот вопрос немного движущейся мишенью. Его снова обновили. Поэтому я отвечаю, чтобы обновить свой ответ.

Во-первых, ответ на предоставленный вами пример:

Да, это возможно.

Это можно решить, обновив Childs 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


Но сам вопрос вводит в заблуждение

Родитель уже знает Childs 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} />;
  }
});

В этом примере становится намного понятнее, что вы уже знаете, каковы реквизиты Ребенка.

Снимок JSBin


Если он действительно использует реквизиты Childs...

Если он действительно использует реквизиты Childs, вы можете вообще избежать взаимодействия с ребенком.

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


И нет необходимости в дополнительной настройке при подключении дополнительных дочерних компонентов

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

Но я подозреваю, что это не ваш фактический прецедент. Так что давайте копаем дальше...


Надежный практический пример

Общий характер представленного примера трудно обсуждать. Ive создал компонент, который демонстрирует практическое использование для вышеупомянутого вопроса, реализованного очень эффективным образом:

Рабочий пример DTServiceCalculator
DTServiceCalculator repo

Этот компонент является простым калькулятором обслуживания. Вы предоставляете ему список услуг (с именами и ценами), и он рассчитает общее количество выбранных цен.

Дети блаженно невежественны

ServiceItem является дочерним компонентом в этом примере. У него нет много мнений о внешнем мире. Для него требуется несколько реквизитов, одна из которых - это функция, которая будет вызываться при нажатии.

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

Он делает только вызов вызываемого handleClick обратного вызова с предоставленным index [source].

Родители - дети

DTServicesCalculator является родительским компонентом в этом примере. Это тоже ребенок. Посмотрим.

DTServiceCalculator создает список дочернего компонента (ServiceItem s) и предоставляет им реквизиты [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);
}

Владельцы знают все

Концепция "Собственность" является важной в Реактировании. Я рекомендую прочитать больше об этом здесь.

В приведенном выше примере Ive я продолжаю делегировать обработку события до дерева компонентов до тех пор, пока мы не перейдем к компоненту, которому принадлежит состояние.

Когда мы, наконец, доберемся туда, мы обрабатываем выбор/отключение состояния так [source]:

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


Заключение

Постарайтесь, чтобы ваши внешние компоненты были максимально непрозрачными. Стремитесь убедиться, что у них очень мало предпочтений относительно того, как родительский компонент может выбрать их реализовать.

Имейте в виду, кто владеет данными, которыми вы управляете. В большинстве случаев вам необходимо делегировать обработку событий по дереву компоненту, которому принадлежит это состояние.

Кроме того: Flux pattern - хороший способ уменьшить этот тип необходимого подключения в приложениях.

  • 0
    Спасибо за краткий ответ, это действительно помогло мне немного лучше понять React! У меня вопрос в том же духе. Я использую Flux для pub / sub. Вместо того, чтобы передавать обработчик события Child в Parent, как в вашем примере, можно реализовать это как «действие» и прослушать его. Считаете ли вы это хорошей альтернативой и использованием Flux?
  • 1
    Спасибо @Pathsofdesign! Это будет зависеть. Flux имеет эту концепцию Controller-Views. В этом примере Parent может представлять собой такой Controller-View, а Child - просто dumb-View (компонент). Только Controller-Views должны иметь знания о приложении. В этом случае вы все равно передадите Действие от Parent Child в качестве опоры. Что касается самого действия, Flux имеет строго предписанный шаблон для взаимодействия между действиями для обновления представлений. facebook.github.io/flux/docs/...
Показать ещё 1 комментарий
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 принимает аргументы component, AND event. Я думаю, что лучший из всех миров.

ОБНОВЛЕНИЕ: 9/1/2015

Это была плохая идея: позволить деталям реализации ребенка течь в родителя никогда не был хорошим путем. См. Ответ Себастьяна Лорбер.

  • 0
    не аргументы неправильные пути (поменялись местами)?
  • 1
    @ TheSurrican Нет, все отлично. Первый аргумент связан с this внутри функции (которая в любом случае не нужна), а второй добавляется в качестве первого аргумента при вызове onClick.
7

Вопрос заключается в том, как передать аргумент от дочернего к родительскому компоненту. Этот пример прост в использовании и проверке:

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

  • 0
    В моем случае забываем передать функцию обратного вызова в click ()
5

В основном вы используете реквизиты для отправки информации от и от Child и Parent.

Добавляя все замечательные ответы, позвольте мне привести простой пример, объясняющий передачу значений от дочернего к родительскому компоненту в 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(event) {
      // this.props.change(this.state.names);
      this.props.change('jessy');
  }

Вот как вы вставляете значения в реквизиты с клиента на сервер

  • 0
    Могу ли я узнать, как вы толкаете значения в реквизит от клиента к серверу, что вы подразумеваете под этим утверждением
1

Вот простая трехэтапная реализация ES6 с использованием привязки функций в родительском конструкторе. Это первый способ, рекомендуемый официальным учебным пособием (существует также синтаксис полей открытых классов, который здесь не рассматривается). Вы можете найти всю эту информацию здесь https://reactjs.org/docs/handling-events.html

Связывание родительских функций, чтобы дети могли их вызвать (и передать данные до родителя!: D)

  • Убедитесь, что в родительском конструкторе вы связываете функцию, созданную в родительском
  • Передайте связанную функцию вниз ребенку в качестве пропеллера (нет лямбда, потому что мы передаем функцию ref)
  • Вызов связанной функции из дочернего события (Lambda! Мы вызываем функцию при запуске события.   Если мы этого не сделаем, функция будет автоматически запускаться при загрузке и не запускаться в событии.)

Родительская функция

handleFilterApply(filterVals){} 

Родительский конструктор

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

Prop, переданный ребенку

onApplyClick = {this.handleFilterApply}

Вызов детского события

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню