Comunicación entre padres e hijos en Elm: OutMsg vs Translator vs NoMap Patrones

Cuando su aplicación Elm comience a crecer, querrá separarla en pedazos más pequeños para poder escalar. Cubrí esto en otra publicación de blog: ejemplo estructurado de TodoMVC con Elm.

Parte si esta escala eventualmente involucra la necesidad de enviar un mensaje desde un módulo a su padre, como cuando los botones de navegación en una vista específica necesitan enviar un mensaje al enrutador de nivel superior.

Después de un tiempo, comencé a notar 3 patrones diferentes para manejar esto, y refactoré Elm TodoMVC a todos esos enfoques diferentes en este repositorio para que pueda compararlos uno al lado del otro.

El patrón OutMsg

Creo que Folkertdev fue el primero que vi escribir sobre la comunicación entre padres e hijos en Elm, su blog explica bastante bien este enfoque.

Pero, para resumir, básicamente devuelve un valor adicional en su función de actualización. Entonces, en lugar de devolver esto:

(Modelo, Cmd Msg)

Devuelves esto:

(Modelo, Cmd Msg, OutMsg)

Luego, la función de actualización principal es responsable de manejarlos. De esta manera, el niño no necesita saber nada sobre su padre, pero el padre necesita saber sobre los OutMsgs de su hijo.

He implementado TodoMVC usando este enfoque. Pero si desea verificar una escala real de esto, Richard Feldman implementó el ejemplo de elm-spa de esta manera.

Otro ejemplo que utiliza este enfoque es elm-datepicker.

El patrón de traductor

El patrón de traductor es muy similar al de OutMsg, pero en lugar de que el padre sepa sobre los tipos de Msgs del niño, es el padre el que pasa qué mensajes se generarán, a través de un traductor. Alex Lew explica su enfoque mucho mejor aquí.

Básicamente tienes un traductor que es un registro como este:

tipo alias TranslationDictionary msg =
  {onInternalMessage: InternalMsg -> msg
  , onPlayerWin: Int -> msg
  , onPlayerLose: msg
  }

También implementé TodoMVC utilizando este enfoque, y creo que elm-autocomplete también es un buen ejemplo.

Elm-parent-child-update es una biblioteca que le ayuda con la actualización child-parent que parece seguir este patrón.

El patrón NoMap

Esto es algo que noté que estaba haciendo. La idea básica es evitar hacer Cmd.map y Html.map, por lo que todos deben hablar el mismo idioma, en otras palabras, sus funciones de actualización y visualización deberán devolver el tipo de mensaje de nivel superior.

Con esto, probablemente tendrá Msgs como MsgForLogin, MsgForRouter, etc., por lo que en su vista haría algo como:

botón [onClick (MsgForLogin SignUp)] []

Así es como primero refactoré TodoMVC, de hecho, la primera vez que vi OutMsg no entendí el motivo, porque no estaba mapeando mis Msgs.

Echa un vistazo a la aplicación lightning-talk-app para obtener un ejemplo más amplio con este enfoque. Además, esta aplicación parece seguir la forma en que Kris Jenkins estructura las aplicaciones de Elm, lo que favorece este enfoque al separar los tipos de Msgs en un archivo Types.elm.

La biblioteca elm-taco utiliza una mezcla de patrones OutMsg y NoMap al tener un "taco" de nivel superior al que puede enviar mensajes.

Observaciones y comparaciones

Mientras investigaba y refactorizaba esos patrones, noté algunas cosas, que pueden ser ventajas o desventajas dependiendo de sus necesidades:

  • En NoMap, la función de actualización de los padres se mantiene casi igual a medida que su aplicación crece, mientras que en OutMsg y Translate la función de actualización de los padres puede ser muy grande, ya que debe manejar OutMsg de cada niño (ejemplo)
  • En OutMsg y Translate, los módulos anidados no necesitan importar nada de los padres superiores, haciéndolos más encapsulados, sería más fácil extraer y publicar algunos submódulos como una biblioteca, por ejemplo
  • Para que NoMap funcione, sus Msgs deben vivir en un archivo separado de Update, o de lo contrario tendrá un bucle de dependencia. Esto es bueno porque te obliga a dividir las cosas, pero malo al mismo tiempo si quieres tener un solo archivo para cada módulo (Home.elm, Login.elm, Router.elm)
  • En NoMap, es más fácil enviar Msgs a cualquier otro lugar, pero puede ser más difícil seguir todos los cambios de estado causados ​​por él.
  • Según lo medido en el momento de este escrito, para los refactores de TodoMVC, el enfoque NoMap tiene 546 LOC, OutMsg 561 y Translator 612 si esto es importante para usted
  • En NoMap, eventualmente necesita usar el caso _ catch-all para ignorar los mensajes de otros lugares que no desea manejar, por lo que hay menos ayuda del compilador, no puede decir lo que se está perdiendo (gracias por @mordrax por señalar eso en holgura de olmo)
  • En OutMsg and Translator, solo puede mirar los tipos o traductores para descubrir qué comunicaciones entre padres e hijos se necesitan, para que el compilador pueda guiarlo a implementarlas, mientras que en NoMap esta comunicación es más implícita
  • El enfoque del traductor parece ser una buena idea para dar sus propios mensajes a un componente externo, como elm-autocomplete
  • Encontré el patrón del traductor difícil de seguir con mensajes de error más difíciles de entender del compilador de Elm mientras lo construía
  • Si no modifica el estándar (Modelo, Cmd Msg), puede usar la biblioteca fina de retorno de elm
  • Algunas personas consideran no tener Html.map como una buena práctica para evitar crear "componentes"
  • Puede obtener muchos beneficios al mezclar esos enfoques, por ejemplo, simplemente puede evitar Html.map para las vistas, mientras aún usa OutMsg para actualizaciones, o puede usar NoMap solo para los Msgs de nivel superior, con OutMsgs a continuación, mientras se procesa un componente externo traducido

Recursos

Creo que la comunicación entre padres e hijos a menudo es más importante al escalar y hacer SPA, es por eso que muchas cosas que encontré estaban leyendo este hilo de reddit sobre Scaling Elm Apps:

¡Aclamaciones!