Edycja : zobacz końcowe przykłady zaktualizowanych przykładów ES6.
Ta odpowiedź dotyczy jedynie bezpośredniego związku rodzic-dziecko. Jeśli rodzic i dziecko mają potencjalnie wielu pośredników, sprawdź tę odpowiedź .
Inne rozwiązania nie mają sensu
Choć nadal działają dobrze, w innych odpowiedziach brakuje czegoś bardzo ważnego.
Czy w React.js nie ma prostego sposobu na przekazanie rekwizytów dziecka rodzicowi za pomocą zdarzeń?
Rodzic ma już tę dziecięcą rekwizyt! : jeśli dziecko ma rekwizyt, to dlatego, że jego rodzic dostarczył mu rekwizyt! Dlaczego chcesz, aby dziecko oddało rekwizyt rodzicowi, podczas gdy rodzic oczywiście już go ma?
Lepsze wdrożenie
Dziecko : to naprawdę nie musi być bardziej skomplikowane.
var Child = React.createClass({
render: function () {
return <button onClick={this.props.onClick}>{this.props.text}</button>;
},
});
Rodzic z samotnym dzieckiem : używając wartości, którą przekazuje dziecku
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
Rodzic z listą dzieci : nadal masz wszystko, czego potrzebujesz od rodzica i nie musisz komplikować dziecka.
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
Możliwe jest również użycie, this.handleChildClick.bind(null,childIndex)
a następnie użyciethis.state.childrenData[childIndex]
Uwaga: wiążemy się z null
kontekstem, ponieważ w przeciwnym razie React wydaje ostrzeżenie związane z systemem automatycznego wiązania . Użycie null oznacza, że nie chcesz zmieniać kontekstu funkcji. Zobacz także .
O enkapsulacji i sprzęganiu w innych odpowiedziach
Jest to dla mnie zły pomysł pod względem łączenia i hermetyzacji:
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} />
}
});
Używanie rekwizytów : jak wyjaśniono powyżej, masz już rekwizyty w rodzicu, więc nie ma sensu przekazywać całego komponentu potomnego, aby uzyskać dostęp do rekwizytów.
Korzystanie z referencji : w zdarzeniu masz już docelową liczbę kliknięć iw większości przypadków to wystarczy. Dodatkowo możesz użyć odwołania bezpośrednio do dziecka:
<Child ref="theChild" .../>
I uzyskaj dostęp do węzła DOM w obiekcie nadrzędnym za pomocą
React.findDOMNode(this.refs.theChild)
W bardziej zaawansowanych przypadkach, w których chcesz uzyskać dostęp do wielu referencji dziecka w rodzicu, dziecko może przekazać wszystkie węzły domeny bezpośrednio w wywołaniu zwrotnym.
Komponent ma interfejs (rekwizyty), a rodzic nie powinien zakładać niczego o wewnętrznej pracy dziecka, w tym jego wewnętrznej strukturze DOM lub o których węzłach DOM deklaruje odniesienia. Rodzic korzystający z referencji dziecka oznacza, że ściśle łączysz 2 elementy.
Aby zilustrować problem, wezmę cytat o Shadow DOM , który jest używany w przeglądarkach do renderowania takich elementów jak suwaki, paski przewijania, odtwarzacze wideo ...:
Stworzyli granicę między tym, co Ty, twórca stron internetowych może osiągnąć, a tym, co uważa się za szczegóły implementacji, dlatego są dla ciebie niedostępne. Przeglądarka może jednak dowolnie przemierzać tę granicę. Mając tę granicę na miejscu, byli w stanie zbudować wszystkie elementy HTML przy użyciu tych samych dobrych starych technologii sieciowych, z div i rozpiętości, tak jak ty.
Problem polega na tym, że jeśli pozwolisz, aby szczegóły implementacji podrzędnej wyciekały do elementu nadrzędnego, bardzo trudno jest refaktoryzować dziecko bez wpływu na element nadrzędny. Oznacza to, że jako autor biblioteki (lub jako edytor przeglądarki z Shadow DOM) jest to bardzo niebezpieczne, ponieważ pozwalasz klientowi na zbyt duży dostęp, co bardzo utrudnia aktualizację kodu bez zerwania kompatybilności.
Jeśli Chrome zaimplementował pasek przewijania umożliwiający klientowi dostęp do wewnętrznych węzłów domeny tego paska przewijania, oznacza to, że klient może mieć możliwość po prostu złamania tego paska przewijania, a aplikacje uległyby łatwiejszemu uszkodzeniu, gdy Chrome przeprowadzi automatyczną aktualizację po refaktoryzacji pasek przewijania ... Zamiast tego dają dostęp tylko do niektórych bezpiecznych rzeczy, takich jak dostosowywanie niektórych części paska przewijania za pomocą CSS.
O używaniu czegokolwiek innego
Przechodząc cały komponent w zwrotnego jest niebezpieczne i może prowadzić początkującym programistom zrobić bardzo dziwne rzeczy jak dzwoni childComponent.setState(...)
lub childComponent.forceUpdate()
, albo przypisując mu nowe zmienne wewnątrz rodzica, co czyni całą aplikację znacznie trudniej o przyczyny.
Edycja: przykłady ES6
Ponieważ wiele osób korzysta obecnie z ES6, oto te same przykłady składni ES6
Dziecko może być bardzo proste:
const Child = ({
onClick,
text
}) => (
<button onClick={onClick}>
{text}
</button>
)
Rodzic może być klasą (i może ostatecznie zarządzać samym stanem, ale przekazuję go tutaj jako rekwizyty:
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>
);
}
}
Ale można to również uprościć, jeśli nie trzeba zarządzać stanem:
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
OSTRZEŻENIE PERF (dotyczy ES5 / ES6): jeśli używasz PureComponent
lub shouldComponentUpdate
, powyższe implementacje nie zostaną zoptymalizowane domyślnie, ponieważ używają onClick={e => doSomething()}
lub wiążą się bezpośrednio podczas fazy renderowania, ponieważ utworzy nową funkcję za każdym razem, gdy renderuje rodzic. Jeśli jest to wąskie gardło w Twojej aplikacji, możesz przekazać dane dzieciom i ponownie wprowadzić je do „stabilnego” wywołania zwrotnego (ustawionego w klasie nadrzędnej i powiązanego z this
konstruktorem klasy), aby PureComponent
można było rozpocząć optymalizację lub może zaimplementować własne shouldComponentUpdate
i zignorować wywołanie zwrotne podczas sprawdzania porównania rekwizytów.
Możesz także użyć biblioteki Przekomponuj , która zapewnia komponenty wyższego rzędu w celu uzyskania precyzyjnej optymalizacji:
// 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)
W takim przypadku możesz zoptymalizować komponent potomny, używając:
const OptimizedChild = onlyUpdateForKeys(['text'])(Child)