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

Возможно, есть способ привязать компонент к событию?

ОБНОВЛЕНИЕ - 01.09.2015

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

KendallB
источник
Существуют различные ответы на похожие проблемы ( здесь и здесь ), но ни один из них не выглядит особенно элегантно
Bojangles
Я согласен - передача событий по цепочке - это замечательно, но было бы здорово узнать наверняка, какой компонент породил событие.
KendallB
Пожалуйста, посмотрите на мои ответы, так как я думаю, что принятый ответ недостаточно хорош; stackoverflow.com/a/31756470/82609
Себастьян Лорбер
1
Kit N 'Kaboodle - чтобы получить все включено. Чтобы получить все навороты. Комбинация. urbandictionary.com/define.php?term=kit%20and%20caboodle
Филип Бартузи

Ответы:

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

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

Использование ссылок : у вас уже есть цель клика в событии, и в большинстве случаев этого достаточно. Кроме того, вы могли бы использовать ссылку непосредственно на ребенка:

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

И получить доступ к узлу DOM в родительском с

React.findDOMNode(this.refs.theChild)

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

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

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

Они создали границу между тем, что вы, веб-разработчик, можете достичь, и тем, что считается деталями реализации, поэтому недоступны для вас. Браузер, однако, может проходить через эту границу по желанию. Имея эту границу, они смогли построить все элементы 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>
)

JsFiddle


ПРЕДУПРЕЖДЕНИЕ О ПРОИЗВОДИТЕЛЬНОСТИ (применимо к 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)
Себастьян Лорбер
источник
1
Да, я провел прошлый год, используя React, я согласен - нет веской причины для передачи экземпляров компонентов с помощью функций. Я собираюсь осудить мой собственный ответ. Я также готов поменять ответ, возможно, с некоторыми изменениями: ваша «Лучшая реализация» не затрагивает фактическую причину, по которой возник этот вопрос, а именно: «Как я узнаю, кого из моих детей выбрали в группе?». Flux - это ответ для любого приложения приличного размера, однако в небольших приложениях было бы хорошо передавать значения (а не компоненты) в цепочку вызовов с помощью функции on props. Согласен?
KendallB
@KendallB рад, что вы согласны с моим ответом :) на How do I know which of my children was chosen among a group?самом деле это не часть первоначального вопроса, но я включил решение в свой ответ. В отличие от вашего редактирования, я думаю, что совсем не обязательно изменять дочерний элемент, даже когда он имеет дело со списком, поскольку вы можете использовать его bindнепосредственно в родительском элементе.
Себастьян Лорбер
Я внес изменения в ваш ответ, часть о ссылках. omerwazir.com/posts/react-getdomnode-replaced-with-findDOMNode
ffxsam
Привет @SebastienLorber Я иду из будущего. Относительно этой строки, которую onClick={this.handleChildClick.bind(null,childData)}вы упомянули об автосвязывании. Это применимо и сейчас, когда React не включен в модель класса . Я прочитал много вещей, но эта часть все еще смущает меня. class modelКстати, я использую ES6 , поэтому я перешел nullна thisи мой код все еще работает. Как бы вы перевели эту часть своего кода в стиле ES6?
JohnnyQ
Я также изо всех сил пытаюсь заставить это работать в последнем React, что должно измениться?
Хуссейн Дювиньо
143

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

Сначала ответьте на предоставленный пример:

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

Вы можете решить эту проблему, обновив 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>;
  }
});

Обработчик событий в вашем Parent может затем получить доступ к компоненту и событию следующим образом:

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

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

Снимок JSBin


Если это действительно об использовании реквизита ребенка ...

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

JSX имеет 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


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

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[ источником ].

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

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

DTServiceCalculatorсоздает список дочерних компонентов ServiceItemи предоставляет им реквизиты [ источник ]. Это родительский компонент, 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 - это хороший способ уменьшить необходимый тип подключения в приложениях.

chantastic
источник
Спасибо за краткий ответ, это действительно помогло мне немного лучше понять React! У меня вопрос в том же духе. Я использую Flux для pub / sub. Вместо передачи обработчика события Child в Parent, как в вашем примере, можно реализовать это как «действие» и прослушать его. Считаете ли вы это хорошей альтернативой и использованием Flux?
Pathsofdesign
1
Спасибо @Pathsofdesign! Это будет зависеть. Flux имеет эту концепцию Controller-Views. В этом примере Parentможет быть такой Controller-View, а Childпросто dumb-View (компонент). Только Controller-Views должны иметь знания о приложении. В этом случае, вы бы еще пройти Акцию от 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;..}у родителей
Шишир Арора
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. Я думаю, что это лучшее из всех миров.

ОБНОВЛЕНИЕ: 01.09.2015

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

KendallB
источник
не аргументы неправильные пути (поменялись местами)?
Surrican
1
@ TheSurrican Нет, все отлично. Первый аргумент привязан к thisфункции (которая в любом случае не нужна), а второй добавляется в качестве первого аргумента при вызове onClick.
мужской день
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'));

Вот и все, теперь вы можете передавать значения с вашего клиента на сервер.

Посмотрите на функцию Change в Header.js

Change(event) {
      // this.props.change(this.state.names);
      this.props.change('jessy');
  }

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

Игнатий Андрей
источник
Могу ли я узнать, как вы толкаете значения в реквизит от клиента к серверу, что вы подразумеваете под этим утверждением
G1P
2

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

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

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

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

handleFilterApply(filterVals){} 

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

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

Опора передана ребенку

onApplyClick = {this.handleFilterApply}

Детское событие

onClick = {() => {props.onApplyClick(filterVals)}
Джордан Х
источник
0

Это пример без использования события onClick. Я просто передаю функцию обратного вызова ребенку с помощью реквизита. С этим обратным вызовом дочерний вызов также отправляет данные обратно. Я был вдохновлен примерами в документах .

Небольшой пример (это в 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>
    );
  }
}
drewjosh
источник