Aplicación ReactJS – resiliencia VS que falla rápidamente

Estoy en el medio del desarrollo de una aplicación React y este es el enfoque que utilicé para mis componentes: valido los puntales que espero recibir con la validación de PropTypes, pero sigo asignando valores predeterminados para evitar que se rompe si algo sale mal con los datos recibidos.

Recientemente me dijeron que no deberíamos hacer eso, que los accesorios son lo que esperamos de los padres y si no se respeta el contrato para permitir que el componente se rompa.

¿Qué enfoque es correcto y cuáles son los pros y los contras?

Algunas de mis consideraciones como alimento para pensar …

Siguiendo mi enfoque inicial, en las pruebas pruebo explícitamente los valores predeterminados que pasan al componente bajo prueba algunos datos no válidos y esperando que se imprima una instantánea válida. Las pruebas no fallan debido a algunos datos erróneos, pero imprimo las advertencias de validación de PropTypes (que podrían ser transformadas en errores si lo desea, creo, o ser silenciadas burlándose de ellas en la prueba).

Estas advertencias tanto en las pruebas como en la aplicación real son más concisas y claras que ver un error que dice “no se puede leer ‘someProp’ de undefined” o similar (y dejar que React represente el salto de ciclo). Las validaciones de propType le dicen directa y claramente lo que hizo mal (aprobó el tipo incorrecto como prop, el accesorio faltaba por completo, etc.).

Con el segundo enfoque, las pruebas fallan porque la aplicación se rompe. Creo que este es un buen enfoque solo si la cobertura de la prueba es realmente buena (90/100%), de lo contrario es un riesgo: podría activarse y romperse en casos extremos que arruinen la reputación del producto. Los cambios de refactorización o de requisitos ocurren con bastante frecuencia y algunos casos extremos podrían terminar con datos no deseados que rompen la aplicación y no se capturaron en pruebas automáticas o manuales.

Esto significa que cuando la aplicación está activa, el código podría romperse en un componente principal debido a algunos datos incorrectos y toda la aplicación dejará de funcionar, mientras que en el primer caso la aplicación es resistente y simplemente muestra algunos campos vacíos de forma controlada.

¿Pensamientos?

Sigue un ejemplo simplificado:

Componente reactivo

import React from 'react'; import PropTypes from 'prop-types'; import styles from './styles.css'; export const App = ({ person : { name, surname, address, subscription } = {} }) => ( 

{person.name}

{person.surname}

{person.address}

{ person.subscription && }
); // PS. this is incorrect in this example (as pointed out in an answer). Real code used inline initialization. // App.defaultProps = { // person: { subscription: undefined }, // }; App.propTypes = { person: PropTypes.shape({ name: PropTypes.string.isRequired, surname: PropTypes.string.isRequired, address: PropTypes.string, subscription: PropTypes.object, }).isRequired, };

Prueba

 import React from 'react'; import { shallow } from 'enzyme'; import { mockOut } from 'testUtils/mockOut'; import { App } from '../index.js'; describe('', () => { mockout(App, 'Subscription'); it('renders correctly', () => { const testData = { name: 'a name', surname: 'a surname', address: '1232 Boulevard Street, NY', subscription: { some: 'data' }, } const tree = shallow(); expect(tree.html()).toMatchSnapshot(); }); it('is resilient in case of bad data - still generates PropTypes validation logs', () => { const tree = shallow(); expect(tree.html()).toMatchSnapshot(); }); }); 

ACTUALIZAR:

El foco principal de la pregunta es si es correcto o no asignar valores predeterminados a los accesorios que están marcados con isRequired (en lugar de dejar que su ausencia rompa el componente)

Recientemente me dijeron que no deberíamos hacer eso, que los accesorios son lo que esperamos de los padres y si no se respeta el contrato para permitir que el componente se rompa.

Exactamente, si un accesorio en el componente es opcional, el componente (que representa la vista real) debe manejar eso, no el componente principal.

Sin embargo, puede tener una situación en la que el padre debe romperse si se rompe alguno de los componentes del contrato infantil. Puedo pensar en dos formas posibles de manejar esta situación:

  1. Pasar el notificador de errores a los componentes secundarios, donde si algo sale mal, el niño puede informar el error al componente principal. Pero esta no es una solución limpia porque si hay N hijo y si más de uno se romperá (o informará el error) para ser padre, no tendrá ni idea ni será difícil de administrar. [Esto no es efectivo en absoluto, pero escribió aquí porque Solía ​​seguir esto cuando estaba aprendiendo Reaccionar: P]

  2. Usar try/catch en el componente principal y no confiar ciegamente en ningún componente secundario y mostrar mensajes de error cuando algo va mal. Cuando está utilizando try/catch en todos sus componentes, puede lanzar un error de forma segura desde los componentes cuando no se cumple ningún contrato.

¿Qué enfoque es correcto y cuáles son los pros y los contras?

IMO, el segundo enfoque ( try/catch in components y throwing error cuando los requisitos no se cumplen) es válido y resolverá todos los problemas. Al escribir pruebas para el componente cuando no se pasan los accesorios, puede esperar un error al cargar el componente.

Actualizar

Si usa React> 16, esta es la forma de manejar los errores.

No es correcto asignar valores predeterminados a .isRequred props a través del componente defaultProps . De acuerdo con los documentos oficiales :

El defaultProps se usará para garantizar que this.props.name tenga un valor si no fue especificado por el componente principal. La comprobación de tipos propTypes ocurre después de que se resuelven los defaultProps, por lo que la comprobación de tipo también se aplicará a los DefaultProps.

Si establece el valor de propiedad predeterminado en Component.defaultProps, nunca recibirá una advertencia si el componente primario no proporciona esta propiedad.

En mi opinión, no dejaré que uno o dos atributos faltantes rompan mi aplicación. React actúa como capa de presentación en mi aplicación y creo que es mucho más que mostrar “¡Vaya! Hay algo mal” cuando no puedo encontrar una clave en un objeto. Parece un mensaje de un servidor defectuoso con 500 estado, pero sabemos que definitivamente no es tan malo.

Para mí, construyo algunas reglas para manejar la comunicación entre la función render y defaultProps:

Digamos que tenemos un objeto de usuario pasado desde el padre:

 defaultProps: { user: { avatar: { small: '' } } } 

en la función de render

 render() { const { user } = this.props; // if user.avatar is not defined or user.avatar.small is empty string or undefined then we render another component we have prepared for this situation. if (!user.avatar || !user.avatar.small) { return ( // components ... ); } // normal situation return ( // components ... ); } 

El ejemplo anterior es para cadena y necesitamos implementos diferentes para otros tipos de datos.

Buena suerte.