Lo que me gusta del ecosistema React es que IDEA está detrás de muchas decisiones. Varios autores escriben varios artículos en apoyo del orden existente y explican por qué todo está "bien", para que todos entiendan que la fiesta está en el camino correcto.
Después de un tiempo, IDEA cambia un poco y todo comienza desde el principio.
Y el comienzo de esta historia es la separación de componentes en contenedores y no contenedores (popularmente llamados componentes tontos, perdón por mi francés).

El problema
El problema es muy simple: pruebas unitarias. Recientemente, ha habido algún movimiento hacia las pruebas de integración, bueno, ya sabes "Escribir pruebas. No demasiadas. Principalmente integración". . Esta no es una mala idea, y si el tiempo es corto (y las pruebas no son particularmente necesarias), esto es lo que debe hacer. Simplemente llamémosle pruebas de humo, para verificar que nada parece explotar.
Si hay mucho tiempo y se necesitan pruebas, es mejor no hacerlo, porque escribir buenas pruebas de integración es muy, muy largo. Solo porque crecerán y crecerán, y para probar el tercer botón a la derecha, primero tendrá que hacer clic en 3 botones en el menú y no olvide iniciar sesión. En general, aquí hay una explosión combinatoria en bandeja de plata.
La solución aquí es una y simple (por definición): pruebas unitarias. La capacidad de iniciar pruebas con algún estado listo de alguna parte de la aplicación. Más precisamente, para reducir (reducir) el área de prueba de la Aplicación o el Big Block a algo pequeño: una unidad, sea lo que sea. No es necesario usar enzimas: puede ejecutar pruebas de navegador, si el alma lo solicita. Lo más importante aquí es poder probar algo de forma aislada . Y sin demasiados problemas.
El aislamiento es uno de los puntos clave en las pruebas unitarias, y es por eso que no les gustan las pruebas unitarias. No les gusta por varias razones:
- por ejemplo, su "unidad" se separa de la aplicación y no funciona en su composición, incluso cuando sus propias pruebas son verdes.
- o por ejemplo porque el aislamiento es un caballo tan esférico en el vacío que nadie ha visto. ¿Cómo lograrlo y cómo medirlo?
Personalmente, no veo problemas aquí. En el primer párrafo, por supuesto, puede recomendar pruebas de integración, han sido inventadas para eso, para verificar cómo se ensamblan correctamente los componentes probados previamente. Confía en los paquetes npm que prueban, por supuesto, solo ellos mismos y no ellos mismos como parte de su aplicación. ¿En qué se diferencian sus "componentes" de los paquetes "no sus"?
Con el segundo párrafo, todo es un poco más complicado. Y este artículo tratará exactamente sobre este punto (y todo lo anterior a eso, una introducción), sobre cómo hacer que una unidad " unidad " sea comprobable .
Divide y vencerás
La idea de separar los componentes React en "Contenedor" y "Presentación" no es nueva, está bien descrita y ya ha logrado quedar un poco desactualizada. Si tomamos como base (lo que hace el 99% de los desarrolladores) un artículo de Dan Abramov , entonces Componente de presentación:
- Les preocupa cómo se ven las cosas
- Puede contener componentes tanto de presentación como de contenedor
**
interior, y generalmente tiene un marcado DOM y estilos propios) - Ranuras de soporte (a menudo permiten la contención a través de this.props.children)
- Aplicación independiente (no depende del resto de la aplicación, como acciones o tiendas Flux)
- No dependa de los datos (no especifique cómo se cargan o mutan los datos)
- La interfaz se basa en accesorios (reciba datos y devoluciones de llamadas exclusivamente a través de accesorios)
- A menudo sin estado (rara vez tienen su propio estado (cuando lo hacen, es el estado de la interfaz de usuario en lugar de los datos))
- A menudo SFC (se escriben como componentes funcionales a menos que necesiten estado, ganchos de ciclo de vida u optimizaciones de rendimiento)
Bueno, los contenedores son toda la lógica, todo el acceso a los datos y toda la aplicación en principio.
En un mundo ideal, los contenedores son el tronco y los componentes de presentación son las hojas.
Hay dos puntos clave en la definición de Dan: "Aplicación independiente" , que es casi una definición académica de "unidad", y * "Puede contener otros componentes y contenedores de presentación **
" *, donde estas estrellas son especialmente interesantes.
(traducción gratuita) ** En las primeras versiones de mi artículo, yo (Dan) dije que los componentes de presentación deberían contener solo otros componentes de presentación. Ya no lo creo. El tipo de componente son los detalles y pueden cambiar con el tiempo. En general, no lo comparta y todo estará bien.
Recordemos lo que sucede después de esto:
- En el libro de cuentos, todo cae, porque algún tipo de contenedor, en el tercer botón a la izquierda, se arrastra hacia el lado del cual no hay ninguno. Saludos especiales a graphql, react-router y otros react-intl.
- La capacidad de usar mount en las pruebas se pierde, ya que renderiza todo de la A a la Z, y nuevamente, en algún lugar en las profundidades del árbol de renderizado, alguien hace algo y las pruebas caen.
- Se pierde la capacidad de controlar el estado de la aplicación, ya que (en sentido figurado) se pierde la capacidad de humedecer selectores / solucionadores (especialmente con proxyquire), y toda la puerta debe estar húmeda. Y esto es genial para las pruebas unitarias.
Si le parece que los problemas son un poco artificiales, intente trabajar en equipo cuando estos contenedores, que se utilizarán en sus no contenedores, cambien en otros departamentos, y como resultado, usted y ellos miran las pruebas y no pueden entender por qué ayer todo funcionó, y ahora de nuevo.
Como resultado, debe usar superficial, que por diseño elimina todos los efectos secundarios dañinos (e inesperados). Aquí hay un ejemplo simple del artículo "Por qué siempre uso superficial"
Imagine que la información sobre herramientas muestra "?". Al hacer clic, se mostrará el tipo en sí.
import Tooltip from 'react-cool-tooltip'; const MyComponent = () => { <Tooltip> hint: {veryImportantTextYouHaveToTest} </Tooltip> }
¿Cómo probarlo? Montar + hacer clic + verificar lo que está visible. Esta es una prueba de integración, no una unidad, y la pregunta es cómo hacer clic en un componente "extraño" para usted. No hay ningún problema con superficial, ya que no hay cerebros y el "componente alienígena" en sí. Pero aquí hay cerebros, ya que Tooltip es un contenedor, mientras que MyComponent es prácticamente una presentación.
jest.mock('react-cool-tooltip', {default: ({children}) => childlren});
Pero si reacciona-cool-tooltip, entonces no habrá problemas con las pruebas. El "componente" se ha vuelto mucho más tonto, mucho más corto, mucho más finito .
Componente final
- un componente con un tamaño bien conocido, que puede incluir otros componentes finales previamente conocidos o que no los contienen en absoluto.
- no contiene otros contenedores, ya que contienen un estado no controlado y un "aumento" de tamaño, es decir Hacer que el componente actual sea infinito .
- de lo contrario, es un componente de presentación regular. De hecho, exactamente como se describe en la primera versión del artículo de Dan.
El componente final es solo un engranaje sacado de un mecanismo grande.
Toda la pregunta es cómo sacarlo.
Solución 1 - DI
Mi favorito es la inyección de dependencia. Dan también lo ama . En general, esto no es DI, sino "slots". En pocas palabras, sin necesidad de usar contenedores dentro de la presentación, deben inyectarse allí. Y en las pruebas será posible inyectar algo más.
Este es exactamente el caso cuando "los contenedores son el tronco y los componentes de la presentación son hojas"
Solución 2 - Límites
DI a menudo puede ser genial. Probablemente ahora% username% piensa cómo se puede aplicar en la base de código actual, y la solución no se inventa ...
En tales casos, Borders te salvará.
const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children
Aquí, en lugar de "espacios", todos los "puntos de transición" simplemente se convierten en Límite, lo que representará cualquier cosa durante las pruebas. Declarativamente suficiente, y exactamente lo que necesita para "sacar el equipo".
Solución 3 - Nivel
Las fronteras pueden ser un poco ásperas, y podría ser más fácil hacerlas un poco más inteligentes al agregar un poco de conocimiento sobre Layer.
const checkTier = tier => tier === currentTier; const withTier = tier => WrapperComponent => (props) => ( (process.env.NODE_ENV !== 'test' || checkTier(tier)) && <WrapperComponent{...props} /> ); const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); const ASideContainer = withTier('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome);
Bajo el nombre Nivel / Capa, puede haber diferentes cosas: característica, pato, módulo o simplemente esa capa / nivel. El punto no es importante, lo principal es que puede tirar del engranaje, tal vez no uno, sino el número final, dibujando una línea entre lo que necesita y lo que no necesita (para diferentes pruebas, este es un borde diferente).
Y nada impide marcar estos límites de alguna manera diferente.
Solución 4 - Preocupaciones separadas
Si la solución (por definición) radica en la separación de entidades, ¿qué sucederá si las tomamos y las separamos?
Los "contenedores", que tanto nos disgustan, generalmente se denominan contenedores . Y si no, nada impide en este momento comenzar a nombrar Componentes de alguna manera más sonora. O tienen un cierto patrón en su nombre: Connect (WrappedComonent) o GraphQL / Query.
¿Qué pasa si justo en tiempo de ejecución dibuja una línea entre entidades sobre la base de un nombre?
const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> );
Además de una línea en las pruebas, y react-remock eliminará todos los contenedores que puedan interferir con las pruebas.
En principio, este enfoque se puede usar para probar los contenedores en sí mismos: solo necesita eliminar todo excepto el primer contenedor.
import {createElement, remock} from 'react-remock';
De nuevo, un par de líneas y equipo eliminado.
Total
Durante el año pasado, probar los componentes React se ha vuelto más complicado, especialmente para el montaje: debe sobrescribir los 10 Proveedores, Contextos, y cada vez es más difícil probar el componente correcto en el estilo correcto: hay demasiadas cuerdas para tirar.
Alguien escupe y se adentra en el mundo superficial. Alguien agitó sus manos en las pruebas unitarias y transfirió todo a Cypress (¡camina como camina!).
Alguien más señala con el dedo la reacción, dice que estos son efectos algebraicos y que puedes hacer lo que quieras. Todos los ejemplos anteriores son esencialmente el uso de estos efectos algebraicos y simulacros. Para mí y DI estos son moki.
PD: Esta publicación fue escrita como respuesta a los comentarios en React / RFC sobre el hecho de que el equipo React rompió todo, y todos los polímeros allí también
PPS: esta publicación es en realidad una traducción muy gratuita de otra
PPPS: en general, para un aislamiento real, mire rewiremock