javascript - ReactJS: elevar el estado frente a mantener un estado local
create-react-app (3)
En mi empresa, estamos migrando el front-end de una aplicación web a ReactJS. Estamos trabajando con create-react-app (actualizado a v16), sin Redux. Ahora estoy atascado en una página cuya estructura se puede simplificar con la siguiente imagen:
Los datos que muestran los tres componentes (SearchableList, SelectableList y Map) se recuperan con la misma solicitud de back-end en el método
componentDidMount()
de MainContainer.
El resultado de esta solicitud se almacena en el estado de MainContainer y tiene una estructura más o menos así:
state.allData = {
left: {
data: [ ... ]
},
right: {
data: [ ... ],
pins: [ ... ]
}
}
LeftContainer recibe como prop
state.allData.left
de MainContainer y pasa
props.left.data
a SearchableList, una vez más como prop.
RightContainer recibe como prop
state.allData.right
de MainContainer y pasa
props.right.data
a
props.right.pins
y
props.right.pins
a Map.
SelectableList muestra una casilla de verificación para permitir acciones en sus elementos. Siempre que ocurra una acción en un elemento del componente SelectableList, puede tener efectos secundarios en los pines del mapa.
He decidido almacenar en el estado de RightContainer una lista que mantiene todos los identificadores de los elementos mostrados por SelectableList;
esta lista se pasa como accesorios a SelectableList y Map.
Luego paso a SelectableList una devolución de llamada, que cada vez que se realiza una selección actualiza la lista de identificadores dentro de RightContainer;
los nuevos accesorios llegan tanto a SelectableList como a Map, por lo
render()
se llama a
render()
en ambos componentes.
Funciona bien y ayuda a mantener todo lo que le puede suceder a SelectableList y Map dentro de RightContainer, pero estoy preguntando si esto es correcto para los conceptos de elevación de estado y fuente única de verdad .
Como alternativa factible, pensé en agregar una propiedad
_selected
a cada elemento en
state.right.data
en MainContainer y pasar la devolución de llamada de selección tres niveles a SelectableList, manejando todas las acciones posibles en MainContainer.
Pero tan pronto como ocurra un evento de selección, esto eventualmente forzará la carga de LeftContainer y RightContainer, introduciendo la necesidad de implementar lógicas como
shouldComponentUpdate()
para evitar el
render()
inútil
render()
especialmente en LeftContainer.
¿Cuál es / podría ser la mejor solución para optimizar esta página desde un punto de vista arquitectónico y de rendimiento?
A continuación tiene un extracto de mis componentes para ayudarlo a comprender la situación.
MainContainer.js
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
allData: {}
};
}
componentDidMount() {
fetch( ... )
.then((res) => {
this.setState({
allData: res
});
});
}
render() {
return (
<div className="main-container">
<LeftContainer left={state.allData.left} />
<RightContainer right={state.allData.right} />
</div>
);
}
}
export default MainContainer;
RightContainer.js
class RightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [ ... ]
};
}
onDataSelection(e) {
const itemId = e.target.id;
// ... handle itemId and selectedItems ...
}
render() {
return (
<div className="main-container">
<SelectableList
data={props.right.data}
onDataSelection={e => this.onDataSelection(e)}
selectedItems={this.state.selectedItems}
/>
<Map
pins={props.right.pins}
selectedItems={this.state.selectedItems}
/>
</div>
);
}
}
export default RightContainer;
¡Gracias por adelantado!
Al usar Redux, puede evitar tales devoluciones de llamada y mantener todo el estado en una sola tienda, así que haga que su componente principal esté conectado, y que los componentes izquierdo y derecho sean tontos, y simplemente pase los accesorios que obtiene de padres a hijos, y usted No tiene que preocuparse por las devoluciones de llamada en este caso.
Como dice React docs
A menudo, varios componentes necesitan reflejar los mismos datos cambiantes. Recomendamos elevar el estado compartido hasta su ancestro común más cercano.
Debe haber una única "fuente de verdad" para cualquier dato que cambie en una aplicación React. Por lo general, el estado se agrega primero al componente que lo necesita para la representación. Luego, si otros componentes también lo necesitan, puede elevarlo hasta su ancestro común más cercano. En lugar de intentar sincronizar el estado entre diferentes componentes, debe confiar en el flujo de datos de arriba hacia abajo.
El estado de elevación implica escribir más código "repetitivo" que los enfoques de enlace bidireccionales, pero como beneficio, requiere menos trabajo encontrar y aislar errores. Dado que cualquier estado "vive" en algún componente y ese componente solo puede cambiarlo, la superficie de los insectos se reduce considerablemente. Además, puede implementar cualquier lógica personalizada para rechazar o transformar la entrada del usuario.
Esencialmente, también necesita elevar los estados del árbol que se están utilizando en el componente Hermanos también.
Por lo tanto, su primera implementación donde almacena los
RightContainer
selectedItems
como un estado en
RightContainer
está completamente justificada y es un buen enfoque, ya que el padre no necesita saber y estos
data
están siendo compartidos por los dos componentes
child
de
RightContainer
y esos dos ahora. tener una sola fuente de verdad.
Según su pregunta:
Como alternativa factible, pensé en agregar una propiedad _selected a cada elemento en
state.right.data
enMainContainer
y pasar la devolución de llamada de selección tres niveles hacia abajo aSelectableList
, manejando todas las acciones posibles enMainContainer
No estaría de acuerdo con que este es un enfoque mejor que el primero, ya que
MainContainer
no necesita conocer los
MainContainer
selectedItems
o el controlador de ninguna de las actualizaciones.
MainContainer
no está haciendo nada sobre esos estados y solo lo está transmitiendo.
Considere
optimise on performance
, usted mismo habla sobre la implementación de
shouldComponentUpdate
, pero puede evitarlo creando sus componentes extendiendo
React.PureComponent
que esencialmente implementa el
shouldComponentUpdate
con una comparación
shallow
de
state
y
props
.
Según los documentos:
Si la función
render()
su componente React rinde el mismo resultado con los mismos accesorios y estado, puede usarReact.PureComponen
t para aumentar el rendimiento en algunos casos.
Sin embargo, si varios componentes profundamente anidados están utilizando los mismos datos, tiene sentido utilizar redux y almacenar esos datos en el estado redux. De esta forma, es accesible globalmente para toda la aplicación y se puede compartir entre componentes que no están directamente relacionados.
Por ejemplo, considere el siguiente caso
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
Ahora aquí si tanto Home como MyComp desean acceder a los mismos datos.
Puede pasar los datos como accesorios desde la aplicación llamándolos a través de
render
prop.
Sin embargo, se haría fácilmente conectando ambos componentes al estado de Redux usando una función de
connect
como
const mapStateToProps = (state) => {
return {
data: state.data
}
}
export connect(mapStateToProps)(Home);
y de manera similar para
MyComp
.
También es fácil de configurar acciones para actualizar información relevante
Además, es particularmente fácil de configurar Redux para su aplicación y podrá almacenar datos relacionados con las mismas cosas en los reductores individuales. De esta manera, también podrá modularizar los datos de su aplicación
Mi honesto consejo sobre esto. De la experiencia es:
Redux es simple. Es fácil de entender y escalar, PERO debe usar Redux para algunos casos de uso específicos.
Como Redux encapsula su aplicación, puede pensar en almacenar cosas como:
- configuración regional de la aplicación actual
- usuario autenticado actual
- token actual de alguna parte
Cosas que necesitarías a escala global. react-redux incluso permite un decorador @connect en componentes. Asi como:
@connect(state => ({
locale: state.locale,
currentUser: state.currentUser
}))
class App extends React.Component
Todos ellos se transmiten como accesorios y connect se pueden usar en cualquier lugar de la aplicación. Aunque recomiendo simplemente pasar los accesorios globales con el operador de propagación
<Navbar {...this.props} />
Todos los demás componentes (o "páginas") dentro de su aplicación pueden hacer su propio estado encapsulado. Por ejemplo, la página Usuarios puede hacer lo suyo.
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
loadingUsers: false,
users: [],
};
}
......
Accedería a locale y currentUser a través de accesorios porque se transmitieron desde los componentes del Contenedor.
Este enfoque lo he hecho varias veces y funciona.
Pero , dado que quería consolidar realmente el conocimiento de React primero, antes de hacer Redux puede simplemente almacenar su estado en el componente de nivel superior y transmitirlo a los niños.
Desventajas:
- Tendrás que seguir pasándolos a componentes de nivel interno
- Para actualizar el estado desde los componentes de nivel interno, tendrá que pasar la función que actualiza el estado.
Estas desventajas son un poco aburridas y difíciles de manejar. Por eso se construyó Redux.
Espero haber ayudado. buena suerte