
Poner las cosas en contexto con React

Context es actualmente una API experimental para React, ¡pero pronto será un ciudadano de primera clase ! Hay muchas razones por las que es interesante, pero quizás la más importante es que permite que los componentes principales pasen datos implícitamente a sus hijos, sin importar cuán profundo sea el árbol de componentes. En otras palabras, se pueden agregar datos a un componente principal y luego cualquier hijo puede acceder a ellos.
Si bien este suele ser el caso de uso para algo como Redux , es bueno usarlo si no necesita una administración de datos compleja. ¡Piénsalo! Creamos un flujo de datos personalizado, decidiendo qué accesorios se pasan y en qué niveles. Muy genial.
El contexto es excelente en áreas donde hay muchos componentes que dependen de un solo dato, pero que se encuentran en lo más profundo del árbol de componentes. Pasar explícitamente cada accesorio a cada componente individual a menudo puede resultar abrumador y es mucho más fácil simplemente usar el contexto aquí.
Por ejemplo, consideremos cómo normalmente pasaríamos accesorios por el árbol. En este caso, estamos pasando el color red
usando accesorios en cada componente para moverlo en el flujo.
class Parent extends React.Component { render(){ return Child color="red" /; }}class Child extends React.Component { render(){ return GrandChild color={this.props.color} / }}class GrandChild extends React.Component { render(){ return ( div style={{color: this.props.color}} Yep, I'm the GrandChild /div ); }}
¿Qué pasaría si nunca quisiéramos que el Child
componente tuviera el accesorio en primer lugar? El contexto nos ahorra tener que revisar el Child
componente con color y pasarlo directamente de Parent
a GrandChild
:
class Parent extends React.Component { // Allow children to use context getChildContext() { return { color: 'red' }; } render(){ return Child /; }}Parent.childContextTypes = { color: PropTypes.string};class Child extends React.Component { render() { // Props is removed and context flows through to GrandChild return GrandChild / }}class GrandChild extends React.Component { render() { return ( div style={{color: this.context.color}} Yep, I'm still the GrandChild /div ); }}// Expose color to the GrandChildGrandChild.contextTypes = { color: PropTypes.string};
Si bien es un poco más detallado, la ventaja es exponer color
cualquier parte inferior del árbol de componentes. Bueno, a veces…
Hay algunas trampas
No siempre puedes quedarte con el pastel y comértelo también, y el contexto en su forma actual no es una excepción. Hay algunos problemas subyacentes con los que probablemente entrarás en contacto si terminas utilizando el contexto para todos los casos excepto los más simples.
El contexto es ideal para usarlo en una renderización inicial. ¿Actualizar el contexto sobre la marcha? No tanto. Un problema común con el contexto es que los cambios de contexto no siempre se reflejan en un componente.
Analicemos estos problemas con más detalle.
Gotcha 1: uso de componentes puros
El contexto es difícil cuando se usa PureComponent
, ya que de forma predeterminada no realiza ninguna diferencia superficial con el contexto. La diferenciación superficial consiste PureComponent
en comprobar si los valores del objeto son estrictamente iguales. Si no es así, entonces (y sólo entonces) se actualizará el componente. Pero como no se comprueba el contexto, bueno… no pasa nada.
Gotcha 2: ¿Debería actualizarse el componente? Tal vez.
El contexto tampoco se actualiza si un componente shouldComponentUpdate
regresa false
. Si tiene un shouldComponentUpdate
método personalizado, también deberá tener en cuenta el contexto. Para habilitar las actualizaciones con contexto, podríamos actualizar cada componente individual con una configuración personalizada shouldComponentUpdate
similar a esta.
import shallowEqual from 'fbjs/lib/shallowEqual';class ComponentThatNeedsColorContext extends React.PureComponent { // nextContext will show color as soon as we apply ComponentThatNeedsColorContext.contextTypes // NOTE: Doing the below will show a console error come react v16.1.1 shouldComponentUpdate(nextProps, nextState, nextContext){ return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) || !shallowEqual(this.context, nextContext); }}ComponentThatNeedsColorContext.contextTypes = { color: PropTypes.string};
Sin embargo, esto no resuelve el problema de un intermediario PureComponent
entre el padre y el hijo que bloquea las actualizaciones de contexto. Esto significa que cada uno de los métodos PureComponent
entre el padre y el hijo tendrían que estar contextTypes
definidos y también tendrían que tener un shouldComponentUpdate
método actualizado. Y en este punto, eso es mucho trabajo para muy poco beneficio.
Mejores enfoques para los problemas
Afortunadamente, tenemos algunas formas de solucionar los problemas.
Método 1: utilizar un componente de orden superior
Un componente de orden superior puede leer desde el contexto y pasar los valores necesarios al siguiente componente como accesorio.
import React from 'react';const withColor = (WrappedComponent) = { class ColorHOC extends React.Component { render() { const { color } = this.context; return WrappedComponent style={{color: color}} {...this.props} / } } ColorHOC.contextTypes = { color: React.PropTypes.string }; return ColorHOC;};export const Button = (props)= button {...props}Button/button// ColoredButton will render with whatever color is currently in context with a style propexport const ColoredButton = withColor( Button );
Enfoque 2: Utilizar accesorios de renderizado
Los Render Props nos permiten usar accesorios para compartir código entre dos componentes.
class App extends React.Component { getChildContext() { return { color: 'red' } } render() { return Button / }}App.childContextTypes = { color: React.PropTypes.string}// Hook 'Color' into 'App' contextclass Color extends React.Component { render() { return this.props.render(this.context.color); }}Color.contextTypes = { color: React.PropTypes.string}class Button extends React.Component { render() { return ( button type="button" {/* Return colored text within Button */} Color render={ color = ( Text color={color} text="Button Text" / ) } / /button ) }}class Text extends React.Component { render(){ return ( span style={{color: this.props.color}} {this.props.text} /span ) }}Text.propTypes = { text: React.PropTypes.string, color: React.PropTypes.string,}
Enfoque 3: Inyección de dependencia
Una tercera forma de solucionar estos problemas es usar la inyección de dependencia para limitar la API de contexto y permitir que los componentes se suscriban según sea necesario.
El nuevo contexto
La nueva forma de usar el contexto, que actualmente está programada para la próxima versión menor de React (16.3) , tiene la ventaja de ser más legible y fácil de escribir sin los “errores” de las versiones anteriores. Ahora tenemos un nuevo método llamado createContext
, que define un nuevo contexto y devuelve tanto a Provider
como Consumer
.
Establece Provider
un contexto al que todos los subcomponentes pueden conectarse. Está conectado a través Consumer
del cual se utiliza un accesorio de renderizado. El primer argumento de esa función render prop es el value
que le hemos dado al Provider
. Al actualizar el valor dentro de Provider
, todos los consumidores se actualizarán para reflejar el nuevo valor.
Como beneficio adicional al usar el nuevo contexto, ya no tenemos que usar childContextTypes
, getChildContext
, y contextTypes
.
const ColorContext = React.createContext('color');class ColorProvider extends React.Component { render(){ return ( ColorContext.Provider value={'red'} { this.props.children } /ColorContext.Provider ) }}class Parent extends React.Component { render(){ // Wrap 'Child' with our color provider return ( ColorProvider Child / /ColorProvider ); }}class Child extends React.Component { render(){ return GrandChild / }}class GrandChild extends React.Component { render(){ // Consume our context and pass the color into the style attribute return ( ColorContext.Consumer {/* 'color' is the value from our Provider */} { color = ( div style={{color: color}} Yep, I'm still the GrandChild /div ) } /ColorContext.Consumer ); }}
Contextos separados
Dado que tenemos un control más granular sobre cómo exponemos el contexto y qué componentes pueden usarlo, podemos envolver componentes individualmente con diferentes contextos, incluso si viven dentro del mismo componente. Podemos ver esto en el siguiente ejemplo, donde al usar LightProvider
dos veces, podemos darle a dos componentes un contexto separado.
Conclusión
Context es una API poderosa, pero también es muy fácil de usar incorrectamente. También existen algunas advertencias al usarlo y puede ser muy difícil resolver los problemas cuando los componentes fallan. Si bien los componentes de orden superior y la inyección de dependencias ofrecen alternativas en la mayoría de los casos, el contexto se puede utilizar de manera beneficiosa en porciones aisladas de su código base.
With the next context though, we no longer have to worry about the gotchas we had with the previous version. It removes having to define contextTypes
on individual components and opens up the potential for defining new contexts in a reusable manner.
Deja una respuesta