Reaccionar: ¿la forma correcta de pasar el estado del elemento del formulario a elementos hermanos/padres?
- Supongamos que tengo una clase P de React, que representa dos clases secundarias, C1 y C2.
- C1 contiene un campo de entrada. Me referiré a este campo de entrada como Foo.
- Mi objetivo es permitir que C2 reaccione a los cambios en Foo.
Se me ocurrieron dos soluciones, pero ninguna de ellas me parece del todo correcta.
Primera solución:
- Asigne a P un estado,
state.input
. - Cree una
onChange
función en P, que tome un evento y establezcastate.input
. - Pase esto
onChange
a C1 comoprops
y deje que C1 se unathis.props.onChange
aonChange
Foo.
Esto funciona. Siempre que el valor de Foo cambia, activa a setState
en P, por lo que P tendrá la entrada para pasar a C2.
Pero no parece del todo correcto por la misma razón: estoy configurando el estado de un elemento padre a partir de un elemento hijo. Esto parece traicionar el principio de diseño de React: flujo de datos unidireccional.
¿Es así como se supone que debo hacerlo o existe una solución más natural de React?
Segunda solución:
Simplemente ponga Foo en P.
Pero, ¿es este un principio de diseño que debo seguir cuando estructuro mi aplicación: colocar todos los elementos del formulario en la render
clase de nivel más alto?
Como en mi ejemplo, si tengo una representación grande de C1, realmente no quiero poner todo render
C1 en render
P solo porque C1 tiene un elemento de formulario.
¿Cómo debería hacerlo?
Entonces, si lo entiendo correctamente, ¿su primera solución es sugerir que mantenga el estado en su componente raíz? No puedo hablar por los creadores de React, pero en general considero que esta es una solución adecuada.
Mantener el estado es una de las razones (al menos creo) por las que se creó React. Si alguna vez implementó su propio lado del cliente de patrón de estado para lidiar con una interfaz de usuario dinámica que tiene muchas piezas móviles interdependientes, entonces le encantará React, porque alivia gran parte de esta molestia de administración de estado.
Al mantener el estado más arriba en la jerarquía y actualizarlo a través de eventos, su flujo de datos sigue siendo prácticamente unidireccional, solo está respondiendo a eventos en el componente raíz, en realidad no está obteniendo los datos allí a través de un enlace bidireccional. le estás diciendo al componente raíz que "oye, algo sucedió aquí abajo, revisa los valores" o estás pasando el estado de algunos datos en el componente secundario para actualizar el estado. Cambió el estado en C1 y desea que C2 lo sepa, por lo que, al actualizar el estado en el componente raíz y volver a renderizar, los accesorios de C2 ahora están sincronizados desde que el estado se actualizó en el componente raíz y se transmitió. .
class Example extends React.Component {
constructor (props) {
super(props)
this.state = { data: 'test' }
}
render () {
return (
<div>
<C1 onUpdate={this.onUpdate.bind(this)}/>
<C2 data={this.state.data}/>
</div>
)
}
onUpdate (data) { this.setState({ data }) }
}
class C1 extends React.Component {
render () {
return (
<div>
<input type='text' ref='myInput'/>
<input type='button' onClick={this.update.bind(this)} value='Update C2'/>
</div>
)
}
update () {
this.props.onUpdate(this.refs.myInput.getDOMNode().value)
}
})
class C2 extends React.Component {
render () {
return <div>{this.props.data}</div>
}
})
ReactDOM.renderComponent(<Example/>, document.body)
Habiendo usado React para crear una aplicación ahora, me gustaría compartir algunas ideas sobre esta pregunta que hice hace medio año.
te recomiendo leer
- Pensando en reaccionar
- Flujo
La primera publicación es extremadamente útil para comprender cómo debes estructurar tu aplicación React.
Flux responde a la pregunta de por qué debería estructurar su aplicación React de esta manera (en lugar de cómo estructurarla). React es solo el 50% del sistema, y con Flux puedes ver el panorama completo y ver cómo constituyen un sistema coherente.
Volvamos a la pregunta.
En cuanto a mi primera solución, está totalmente bien dejar que el controlador vaya en la dirección inversa, ya que los datos todavía van en una sola dirección.
Sin embargo, permitir que un controlador active un setState en P puede ser correcto o incorrecto según su situación.
Si la aplicación es un conversor de Markdown simple, siendo C1 la entrada sin formato y C2 la salida HTML, está bien permitir que C1 active un setState en P, pero algunos podrían argumentar que esta no es la forma recomendada de hacerlo.
Sin embargo, si la aplicación es una lista de tareas pendientes, siendo C1 la entrada para crear una nueva tarea pendiente y C2 la lista de tareas pendientes en HTML, probablemente desee que el controlador suba dos niveles más que P dispatcher
, lo que permite store
actualizar el archivo data store
. que luego envía los datos a P y completa las vistas. Vea ese artículo de Flux. Aquí hay un ejemplo: Flux - TodoMVC
Generalmente, prefiero la forma descrita en el ejemplo de la lista de tareas pendientes. Cuanto menos estado tengas en tu aplicación, mejor.
Cinco años después, con la introducción de React Hooks, ahora existe una forma mucho más elegante de hacerlo con el uso del gancho useContext.
Usted define el contexto en un alcance global, exporta variables, objetos y funciones en el componente principal y luego envuelve los elementos secundarios en la aplicación en un contexto proporcionado e importa todo lo que necesita en los componentes secundarios. A continuación se muestra una prueba de concepto.
import React, { useState, useContext } from "react";
import ReactDOM from "react-dom";
import styles from "./styles.css";
// Create context container in a global scope so it can be visible by every component
const ContextContainer = React.createContext(null);
const initialAppState = {
selected: "Nothing"
};
function App() {
// The app has a state variable and update handler
const [appState, updateAppState] = useState(initialAppState);
return (
<div>
<h1>Passing state between components</h1>
{/*
This is a context provider. We wrap in it any children that might want to access
App's variables.
In 'value' you can pass as many objects, functions as you want.
We wanna share appState and its handler with child components,
*/}
<ContextContainer.Provider value={{ appState, updateAppState }}>
{/* Here we load some child components */}
<Book title="GoT" price="10" />
<DebugNotice />
</ContextContainer.Provider>
</div>
);
}
// Child component Book
function Book(props) {
// Inside the child component you can import whatever the context provider allows.
// Earlier we passed value={{ appState, updateAppState }}
// In this child we need the appState and the update handler
const { appState, updateAppState } = useContext(ContextContainer);
function handleCommentChange(e) {
//Here on button click we call updateAppState as we would normally do in the App
// It adds/updates comment property with input value to the appState
updateAppState({ ...appState, comment: e.target.value });
}
return (
<div className="book">
<h2>{props.title}</h2>
<p>${props.price}</p>
<input
type="text"
//Controlled Component. Value is reverse vound the value of the variable in state
value={appState.comment}
onChange={handleCommentChange}
/>
<br />
<button
type="button"
// Here on button click we call updateAppState as we would normally do in the app
onClick={() => updateAppState({ ...appState, selected: props.title })}
>
Select This Book
</button>
</div>
);
}
// Just another child component
function DebugNotice() {
// Inside the child component you can import whatever the context provider allows.
// Earlier we passed value={{ appState, updateAppState }}
// but in this child we only need the appState to display its value
const { appState } = useContext(ContextContainer);
/* Here we pretty print the current state of the appState */
return (
<div className="state">
<h2>appState</h2>
<pre>{JSON.stringify(appState, null, 2)}</pre>
</div>
);
}
const rootElement = document.body;
ReactDOM.render(<App />, rootElement);
Puede ejecutar este ejemplo en el editor Code Sandbox.
La primera solución, mantener el estado en el componente principal , es la correcta . Sin embargo, para problemas más complejos, debería pensar en alguna biblioteca de administración de estado ; redux es la más popular que se usa con reaccionar.