Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The React Playbook

The React Playbook

Un conjunto de jugadas, que a lo largo de mi experiencia desarrollando, han servido para mantener sana la salud de nuestro código. Preparada en conjunto con #MercadoLibre para el #DevFest19 #DevFestSantiago.

Avatar for Daniel Ochoa

Daniel Ochoa

October 04, 2019

Other Decks in Programming

Transcript

  1. Esos son temas Esos son temas interesantes, pero interesantes, pero

    solo son una parte solo son una parte del ciclo de vida de del ciclo de vida de una app una app
  2. ¿Cuál es la variable ¿Cuál es la variable que más

    se repite en que más se repite en las complejidades de las complejidades de implementación? implementación?
  3. ¿Cuál es el objetivo de ¿Cuál es el objetivo de

    un desarrollador? un desarrollador?
  4. Construir aplicaciones que Construir aplicaciones que sean... sean... Fáciles de

    leer Fáciles de leer Fáciles de mantener Fáciles de mantener Fáciles de extender Fáciles de extender
  5. El negocio Arquitectura y diseño Manejo del estado Estructura de

    archivos Convenciones de escritura (linters) Muchos más! Requerimientos Implementación Hay muchos factores que Hay muchos factores que influyen en esto! influyen en esto!
  6. Pero al final, el Pero al final, el átomo átomo

    de de nuestra app, negocio, nuestra app, negocio, feature, siempre será... feature, siempre será...
  7. import React from 'react' const Component = ({ name })

    => ( <h1>Hi {name}</h1> ) Un componente Un componente
  8. Si nuestros componentes Si nuestros componentes son son limpios limpios

    y y concisos concisos... ... Muy probablemente nos Muy probablemente nos cueste más "romper" el cueste más "romper" el diseño o la arquitectura diseño o la arquitectura
  9. Jugada Nº1 Jugada Nº1 Nunca olvidemos tratar a Nunca olvidemos

    tratar a nuestros componentes nuestros componentes como funciones como funciones
  10. Cuando nos olvidamos de Cuando nos olvidamos de esto... esto...

    Los componentes "hacen demasiado" Conocen muchas muchas props Tienen más de un nivel de abstracción Son "booleano-dependientes" (más de esto en breve) Cambian por las razones equivocadas Nos convertimos en todo lo que juramos destruir: que todo ocurra en el mismo lugar
  11. ¿Han visto un componente ¿Han visto un componente parecido a

    este? parecido a este? const Layout = () => ( <div className="layout"> <header> <nav> <a href="/home">Go home</a> <a href="/heeey">Hooo</a> <a href="/we-are">More than 100</a> </nav> </header> <ProductDescr /> <SomeCallToAction /> <footer> <h2>Hi mom! I'm doing a tech talk!</h2> <a href="http://tiny.cc/q4trdz">Careers</a> // Wink wink ;) </footer> </div> ) * Esta es solo una aproximación. Este markup definitivamente podría ser peor.
  12. Este podría ser un enfoque Este podría ser un enfoque

    más limpio más limpio const Layout = () => ( <div className="layout"> <Header /> <ProductDescr /> <SomeCallToAction /> <Footer /> </div> ) Fácil de leer Es "pequeño" Se entiende lo que hace
  13. Ejemplo clásico: UI Feedback Ejemplo clásico: UI Feedback const Component

    = (props) => ( <div> {props.succeeded ? <Alert success text='Vamo!' /> : <Alert failure text='Y, bueno... esto es incómodo' /> } </div> Si tenemos un segmento de código así: Esperamos ver en la UI algo como esto:
  14. Nadie nos detiene de hacer Nadie nos detiene de hacer

    una maldad como esta: una maldad como esta: const Component = () => ( <div> <Alert success failure text='OMG' /> </div> )
  15. El problema principal es que en la El problema principal

    es que en la implementación hay que hacerse cargo implementación hay que hacerse cargo de de num_bools num_bools combinaciones, cuando realmente combinaciones, cuando realmente hay hay solo solo num_bools num_bools estados posibles estados posibles válidos! válidos! 2 2
  16. ¿Qué nos dice ¿Qué nos dice la literatura? la literatura?

    “ Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice. It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing. It does one thing if the flag is true and another if the flag is false!"
  17. ¿Habrá alguna ¿Habrá alguna manera de escribir manera de escribir

    mejor este mejor este componente? componente?
  18. Hagamos más general la diferenciación Hagamos más general la diferenciación

    const Component = (props) => ( <div> {props.succeeded ? <Alert variant='success' text='Vamo!' /> : <Alert variant='failure' text='Oh =(' /> } </div> ) ¿Qué valores puede tomar la prop variant? Alert.propTypes = { variant: PropTypes.oneOf(['success', 'failure']) };
  19. Ahora pensemos en el uso... Ahora pensemos en el uso...

    ¿Qué API querría yo ¿Qué API querría yo que tuviera? que tuviera?
  20. const SuccessAlert = ({text}) => ( <Alert variant='success' text={text} />

    ) const FailureAlert = ({text}) => ( <Alert variant='failure' text={text} /> ) const Component = (props) => ( <div> {props.succeeded ? <SuccessAlert text='Vamo!' /> : <FailureAlert text='Oh =(' /> } </div> ) Busquemos más encapsulamiento Busquemos más encapsulamiento
  21. Con esto... Con esto... No hay más booleanos en la

    API del componente La implementación sigue el principio de mínima sorpresa Cada componente que encapsula hace sólo una cosa La implementación de los componentes puede variar de forma independiente
  22. Jugada Nº3 Jugada Nº3 Recordar quién es el Recordar quién

    es el usuario del componente usuario del componente
  23. ¿Cuál es el uso que ¿Cuál es el uso que

    tendrá el tendrá el componente que componente que estoy escribiendo? estoy escribiendo?
  24. Consideremos el siguiente componente Consideremos el siguiente componente <Modal content={"modal

    content"} customContentStyle={{ backgroundColor: "blue" }} /> Y hagámonos la pregunta... Y hagámonos la pregunta... ¿Cómo puedo cambiar de posición el ¿Cómo puedo cambiar de posición el botón para cerrar el modal? botón para cerrar el modal?
  25. La primera respuesta sería hacer algo La primera respuesta sería

    hacer algo como esto: como esto: <Modal content={"modal content"} closeButtonPosition="top-right" customContentStyle={{ backgroundColor: "blue" }} /> Modal.propTypes = { closeButtonPosition: PropTypes.oneOf( [ "top-right", "top-left", "bottom-left", "bottom-right" ] ) };
  26. ¿Y qué pasa si ahora ¿Y qué pasa si ahora

    necesito invertir la necesito invertir la posición del header posición del header y el footer del y el footer del modal? modal?
  27. El componente empieza a contradecir El componente empieza a contradecir

    todo lo que hemos hablado hoy todo lo que hemos hablado hoy <Modal content={"modal content"} displayFooterOnTop // WTF displayHeaderOnBottom // WTF x 2 closeButtonPosition="top-right" customContentStyle={{ backgroundColor: "blue" }} /> Es altamente probable que cada feature Es altamente probable que cada feature request que tenga que ver con layout, request que tenga que ver con layout, implique una nueva prop implique una nueva prop
  28. De forma similar, un caso IRL De forma similar, un

    caso IRL Es evidente que la información de ambas cards es distinta... ¿Podemos decir lo mismo del layout?
  29. En el caso del Modal... En el caso del Modal...

    Muy probablemente estemos en el punto de vista de una biblioteca consumida por developers, la flexibilidad en la implementación del layout es clave! ¿Cómo debería ser el API del componente?
  30. Cosas que debería tener el API del Cosas que debería

    tener el API del Modal Modal Flexible en la definición del layout Abstracción de funcionalidad básica Ser declarativa
  31. ¿Y si tuviéramos un API como...? ¿Y si tuviéramos un

    API como...? <Modal> <Modal.Content> <Modal.Header> <div>Personalización del layout</div> <Modal.CloseButton>X</Modal.CloseButton> </Modal.Header> <Modal.Body> <h1>Heeeey! Este es el contenido del modal</h1> <Modal.CloseButton> Y si haces click acá, el modal se cierra </Modal.CloseButton> </Modal.Body> </Modal.Content> <Modal.OpenButton text="Abrir!" /> </Modal> ¿Han visto algo así en sus implementaciones?
  32. De esta manera De esta manera invertimos el control invertimos

    el control en la definición del en la definición del layout layout
  33. Esto es un Esto es un compound compound component component

    Un Un compound component compound component es un tipo de es un tipo de componente que maneja el estado componente que maneja el estado interno de un feature, mientras delega interno de un feature, mientras delega el control de la presentación a quien el control de la presentación a quien implementa y no a quien declara implementa y no a quien declara
  34. La definición de ToggleButton La definición de ToggleButton import React

    from 'react' const ModalContext = React.createContext() const { Provider, Consumer } = ModalContext const ToggleButton = ({ children, openOnClick = false, ...props }) => ( <Consumer> {({ toggleModal }) => { const changeModalState = () => toggleModal(openOnClick) return ( <button {...props} onClick={changeModalState}> {children} </button> ) }} </Consumer> ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  35. La definición de los componentes static La definición de los

    componentes static class Modal extends Component { state = { isModalOpen: false } static CloseButton = ({ children, ...props }) => ( <ToggleButton openOnClick={false} {...props}> {children} </ToggleButton> ) static OpenButton = ({ children, ...props }) => ( <ToggleButton openOnClick={true} {...props}> {children} </ToggleButton> ) static Content = ({ children }) => ( <Consumer> {({ isModalOpen, toggleModal }) => { const closeModal = () => isModalOpen && toggleModal(false) return ( <Portal> <div onClick={closeModal}> <div onClick={e => e.stopPropagation()} > {children} </div> </div> </Portal> ) }} </Consumer> ) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
  36. La definición de render() La definición de render() class Modal

    extends Component { ... toggleModal = newModalState => this.setState({ isModalOpen: newModalState }) render() { return ( <Provider value={{ isModalOpen: this.state.isModalOpen, toggleModal: this.toggleModal, }} > {this.props.children} </Provider> ) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
  37. Veamos una implementación Veamos una implementación class MapItem extends Component

    { render() { <div onClick={() => this.notifyEvent("click")}> <svg /> </div> } } class ItemBookmark extends Component { render() { <div onClick={() => this.notifyEvent("click")}> <svg /> </div> } } class Slider extends Component { render() { <div className='slider'> <SliderItem onMouseEnter={() => this.notifyEvent("mouseEnter")} /> <SliderItem onMouseEnter={() => this.notifyEvent("click")} /> </div> } } * Algunas partes de la implementación fueron intencionalmente omitidas o simplificadas
  38. Tratemos de generalizar Tratemos de generalizar class Tracker extends Component

    { trackEvent = (action_type, options) => { TrackerService.track(this.props.title, action_type, options) } trackClick = (options) => { trackEvent("Click", options) } trackMouseEnter = (options) => { trackEvent("MouseEnter", options) } render() { { this.props.children({ trackEvent: this.trackEvent, trackClick: this.trackClick, trackMouseEnter: this.trackMouseEnter, })} } }
  39. Actualicemos la implementación Actualicemos la implementación class MapItem extends Component

    { render() { <Tracker title="MapItem"> {({ trackClick }) => { <div onClick={() => trackClick()}> <svg /> </div> }} </Tracker> } }
  40. Esto es un Esto es un Render Props Render Props

    Render props es un patrón que permite Render props es un patrón que permite compartir funcionalidad entre compartir funcionalidad entre componentes, delegando el control de componentes, delegando el control de la implementación a un componente la implementación a un componente padre padre
  41. De esta manera De esta manera invertimos el control invertimos

    el control en la en la implementación del implementación del componente componente
  42. Veamos cómo cambia el patrón Veamos cómo cambia el patrón

    import React, { useState } from 'react' const useTracker = title => { const [tracking, setTracking] = useState(false); const trackEvent = (action_type, options) => setTracking(true) TrackerService.track(title, action_type, options) .then(() => { setTracking(false) }) return { tracking, trackClick: trackEvent("Click", options), trackMouseEnter: trackEvent("MouseEnter", options), trackEvent } }
  43. Y así queda el componente Y así queda el componente

    const MapItem = (props) => { const { trackClick, tracking } = useTracker('MapItem') return ( <div onClick={() => trackClick()}> { tracking ? 'Tracking' : 'Not Tracking' } </div> ) }