Pasar accesorios al componente principal en React.js
¿No existe una manera sencilla de pasar un niño props
a su padre usando eventos en 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} />
}
});
Sé que puedes usar componentes controlados para pasar el valor de una entrada, pero sería bueno pasar todo el kit n' kaboodle. A veces, el componente secundario contiene un conjunto de información que preferiría no tener que buscar.
¿Quizás haya una manera de vincular el componente al evento?
ACTUALIZACIÓN – 9/1/2015
Después de usar React durante más de un año, y motivado por la respuesta de Sebastien Lorber, llegué a la conclusión de que pasar componentes secundarios como argumentos para funciones en los padres no es , de hecho, la forma de React, ni nunca fue una buena idea. Cambié la respuesta.
Editar : consulte los ejemplos finales para ver ejemplos actualizados de ES6.
Esta respuesta simplemente maneja el caso de relación directa entre padres e hijos. Cuando padre e hijo tengan potencialmente muchos intermediarios, consulte esta respuesta .
Otras soluciones no entienden el punto
Si bien todavía funcionan bien, a otras respuestas les falta algo muy importante.
¿No existe una forma sencilla de pasar los accesorios de un niño a sus padres mediante eventos en React.js?
¡El padre ya tiene ese accesorio infantil! : si el niño tiene un accesorio, entonces es porque su padre le proporcionó ese accesorio al niño. ¿Por qué quiere que el niño le devuelva el accesorio al padre, mientras que el padre obviamente ya tiene ese accesorio?
Mejor implementación
Niño : realmente no tiene por qué ser más complicado que eso.
var Child = React.createClass({
render: function () {
return <button onClick={this.props.onClick}>{this.props.text}</button>;
},
});
Padre con un solo hijo : usando el valor que le pasa al niño
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
Padre con lista de hijos : todavía tienes todo lo que necesitas sobre el padre y no necesitas complicar más al niño.
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
También es posible utilizar this.handleChildClick.bind(null,childIndex)
y luego utilizar.this.state.childrenData[childIndex]
Tenga en cuenta que estamos vinculando con un null
contexto porque, de lo contrario, React emite una advertencia relacionada con su sistema de vinculación automática . Usar nulo significa que no desea cambiar el contexto de la función. Ver también .
Acerca de la encapsulación y el acoplamiento en otras respuestas
Para mí, esto es una mala idea en términos de acoplamiento y encapsulación:
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} />
}
});
Uso de accesorios : como expliqué anteriormente, ya tienes los accesorios en el padre, por lo que es inútil pasar todo el componente secundario para acceder a los accesorios.
Usando referencias : ya tienes el objetivo de clic en el evento y, en la mayoría de los casos, esto es suficiente. Además, podría haber utilizado una referencia directamente sobre el niño:
<Child ref="theChild" .../>
Y acceda al nodo DOM en el padre con
React.findDOMNode(this.refs.theChild)
Para casos más avanzados en los que desea acceder a varias referencias del hijo en el padre, el hijo podría pasar todos los nodos dom directamente en la devolución de llamada.
El componente tiene una interfaz (accesorios) y el padre no debe asumir nada sobre el funcionamiento interno del hijo, incluida su estructura DOM interna o para qué nodos DOM declara referencias. Que un padre utilice una referencia de un niño significa que acopla estrechamente los 2 componentes.
Para ilustrar el problema, tomaré esta cita sobre Shadow DOM , que se usa dentro de los navegadores para representar elementos como controles deslizantes, barras de desplazamiento, reproductores de video...:
Crearon un límite entre lo que usted, el desarrollador web, puede alcanzar y lo que se considera detalles de implementación, por lo que es inaccesible para usted. El navegador, sin embargo, puede traspasar este límite a voluntad. Con este límite establecido, pudieron crear todos los elementos HTML utilizando las mismas tecnologías web antiguas, a partir de divs y spans tal como lo haría usted.
El problema es que si permite que los detalles de la implementación secundaria se filtren al padre, será muy difícil refactorizar al hijo sin afectar al padre. Esto significa que, como autor de una biblioteca (o como editor de navegador con Shadow DOM), esto es muy peligroso porque permite que el cliente acceda demasiado, lo que hace que sea muy difícil actualizar el código sin romper la retrocompatibilidad.
Si Chrome hubiera implementado su barra de desplazamiento permitiendo al cliente acceder a los nodos dom internos de esa barra de desplazamiento, esto significa que el cliente puede tener la posibilidad de simplemente romper esa barra de desplazamiento, y que las aplicaciones se romperían más fácilmente cuando Chrome realice su actualización automática después de refactorizar el barra de desplazamiento... En cambio, solo dan acceso a algunas cosas seguras como personalizar algunas partes de la barra de desplazamiento con CSS.
Sobre usar cualquier otra cosa
Pasar todo el componente en la devolución de llamada es peligroso y puede llevar a los desarrolladores novatos a hacer cosas muy raras como llamar childComponent.setState(...)
a or childComponent.forceUpdate()
o asignarle nuevas variables dentro del padre, lo que hace que sea mucho más difícil razonar sobre toda la aplicación.
Editar: ejemplos de ES6
Como mucha gente ahora usa ES6, aquí hay los mismos ejemplos para la sintaxis de ES6
El niño puede ser muy simple:
const Child = ({
onClick,
text
}) => (
<button onClick={onClick}>
{text}
</button>
)
El padre puede ser una clase (y eventualmente puede administrar el estado mismo, pero lo paso como accesorios aquí:
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>
);
}
}
Pero también se puede simplificar si no es necesario gestionar el estado:
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
ADVERTENCIA DE RENDIMIENTO (se aplica a ES5/ES6): si está utilizando PureComponent
o shouldComponentUpdate
, las implementaciones anteriores no se optimizarán de forma predeterminada porque se usan onClick={e => doSomething()}
o se vinculan directamente durante la fase de renderizado, ya que creará una nueva función cada vez que el padre renderice. Si esto es un cuello de botella de rendimiento en su aplicación, puede pasar los datos a los hijos y reinyectarlos dentro de una devolución de llamada "estable" (configurada en la clase principal y vinculada al this
constructor de la clase) para que PureComponent
la optimización pueda activarse, o usted Puede implementar el suyo propio shouldComponentUpdate
e ignorar la devolución de llamada en la verificación de comparación de accesorios.
También puede utilizar la biblioteca Recompose , que proporciona componentes de orden superior para lograr optimizaciones precisas:
// 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)
En este caso, podría optimizar el componente secundario utilizando:
const OptimizedChild = onlyUpdateForKeys(['text'])(Child)
Actualización (1/9/15): El OP ha hecho de esta pregunta un objetivo en movimiento. Ha sido actualizado nuevamente. Entonces, me siento responsable de actualizar mi respuesta.
Primero, una respuesta al ejemplo proporcionado:
Sí, esto es posible.
Puedes resolver esto actualizando Child's onClick
para que sea this.props.onClick.bind(null, this)
:
var Child = React.createClass({
render: function () {
return <a onClick={this.props.onClick.bind(null, this)}>Click me</a>;
}
});
El controlador de eventos en su padre puede acceder al componente y al evento de esta manera:
onClick: function (component, event) {
// console.log(component, event);
},
Instantánea de JSBin
Pero la pregunta en sí es engañosa.
El padre ya conoce el del niño props
.
Esto no está claro en el ejemplo proporcionado porque en realidad no se proporcionan accesorios. Este código de muestra podría respaldar mejor la pregunta que se hace:
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} />;
}
});
En este ejemplo queda mucho más claro que ya sabes cuáles son los accesorios de Child.
Instantánea de JSBin
Si realmente se trata de usar accesorios para niños...
Si realmente se trata de usar los accesorios de un Niño, puedes evitar cualquier conexión con el Niño por completo.
JSX tiene una API de atributos extendidos que uso a menudo en componentes como Child. Toma todos los props
y los aplica a un componente. El niño se vería así:
var Child = React.createClass({
render: function () {
return <a {...this.props}> {this.props.text} </a>;
}
});
Permitiéndole utilizar los valores directamente en 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} />;
}
});
Instantánea de JSBin
And there's no additional configuration required as you hookup additional Child components
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
But I suspect that’s not your actual use case. So let’s dig further…
A robust practical example
The generic nature of the provided example is a hard to talk about. I’ve created a component that demonstrations a practical use for the question above, implemented in a very Reacty way:
DTServiceCalculator working example
DTServiceCalculator repo
This component is a simple service calculator. You provide it with a list of services (with names and prices) and it will calculate a total the selected prices.
Children are blissfully ignorant
ServiceItem
is the child-component in this example. It doesn’t have many opinions about the outside world. It requires a few props, one of which is a function to be called when clicked.
<div onClick={this.props.handleClick.bind(this.props.index)} />
It does nothing but to call the provided handleClick
callback with the provided index
[source].
Parents are Children
DTServicesCalculator
is the parent-component is this example. It’s also a child. Let’s look.
DTServiceCalculator
creates a list of child-component (ServiceItem
s) and provides them with props [source]. It’s the parent-component of ServiceItem
but it`s the child-component of the component passing it the list. It doesn't own the data. So it again delegates handling of the component to its parent-component source
<ServiceItem chosen={chosen} index={i} key={id} price={price} name={name} onSelect={this.props.handleServiceItem} />
handleServiceItem
captures the index, passed from the child, and provides it to its parent [source]
handleServiceClick (index) {
this.props.onSelect(index);
}
Owners know everything
The concept of “Ownership” is an important one in React. I recommend reading more about it here.
In the example I’ve shown, I keep delegating handling of an event up the component tree until we get to the component that owns the state.
When we finally get there, we handle the state selection/deselection like so [source]:
handleSelect (index) {
let services = […this.state.services];
services[index].chosen = (services[index].chosen) ? false : true;
this.setState({ services: services });
}
Conclusion
Try keeping your outer-most components as opaque as possible. Strive to make sure that they have very few preferences about how a parent-component might choose to implement them.
Keep aware of who owns the data you are manipulating. In most cases, you will need to delegate event handling up the tree to the component that owns that state.
Aside: The Flux pattern is a good way to reduce this type of necessary hookup in apps.