Nous continuons de parler de l' orme 0,18 .
Orme. Confortable et maladroit
Orme. Confortable et maladroit. Json.Encoder et Json.Decoder
Orme. Confortable et maladroit. Http, Task
Dans cet article, nous examinerons les problèmes d'architecture d'application Elm et les options possibles pour implémenter l'approche de développement des composants.
En tant que tâche, envisagez la mise en œuvre d'une fenêtre déroulante qui permet à un utilisateur enregistré d'ajouter une question. Dans le cas d'un utilisateur anonyme, il suggère d'abord de se connecter ou de s'inscrire.
Nous supposons également que par la suite il peut être nécessaire de mettre en œuvre la réception d'autres types de contenu utilisateur, mais la logique de travailler avec des utilisateurs autorisés et anonymes restera la même.
Composition maladroite
Le code source d'une implémentation naïve . Dans le cadre de cette implémentation, nous allons tout stocker dans un modèle.
Toutes les données nécessaires pour l'autorisation et l'interrogation des utilisateurs sont au même niveau dans le modèle. La même situation avec les messages (Msg).
type alias Model = { user: User , ui: Maybe Ui -- Popup is not open is value equals Nothing , login: String , password: String , question: String , message: String } type Msg = OpenPopup | LoginTyped String | PasswordTyped String | Login | QuestionTyped String | SendQuestion
Le type d'interface est décrit comme le type d'union Ui, qui est utilisé avec le type Maybe.
type Ui = LoginUi -- Popup shown with authentication form | QuestionUi -- Popup shown with textarea to leave user question
Ainsi, ui = Nothing décrit l'absence de fenêtre contextuelle et la fenêtre contextuelle Just est ouverte avec une interface spécifique.
Dans la fonction de mise à jour, la paire est appariée, le message et les données utilisateur. Différentes actions sont effectuées en fonction de cette paire.
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.user) of
Par exemple, lorsque vous cliquez sur le bouton «Ouvrir une popup», un message OpenPopup est généré. Le message OpenPopup dans la fonction de mise à jour est géré de différentes manières. Pour un utilisateur anonyme, un formulaire d'autorisation est généré, et pour un utilisateur autorisé, un formulaire dans lequel vous pouvez laisser une question.
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.user) of
De toute évidence, cette approche peut avoir des problèmes avec la croissance des fonctions d'application:
- Il n'y a pas de regroupement de données dans le modèle et les messages. Tout se trouve dans un seul avion. Ainsi, il n'y a pas de limites de composants, un changement dans la logique d'une partie affectera très probablement le reste;
- la réutilisation du code est possible sur le principe du copier-coller avec toutes les conséquences qui en découlent.
Composition pratique
Le code source est une implémentation pratique . Dans le cadre de cette implémentation, nous essaierons de diviser le projet en composants indépendants. Dépendances autorisées entre les composants.
Structure du projet:
- dans le dossier Type, les types personnalisés sont déclarés;
- les composants personnalisés sont déclarés dans le dossier Component;
- Point d'entrée du projet de fichier Main.elm;
- Les fichiers login.json et questions.json sont utilisés comme données de test pour la réponse du serveur à l'autorisation et l'enregistrement d'informations sur la question, respectivement.
Composants personnalisés
Chaque composant, basé sur l'architecture du langage, doit contenir:
- Modèle
- Messages (Msg)
- résultat de l'exécution (retour);
- fonction d'initialisation (init);
- fonction de mutation (mise à jour);
- fonction d'affichage.
Chaque composant peut contenir un abonnement si nécessaire.

Fig. 1. Diagramme d'activité des composants
Initialisation
Chaque composant doit être déclenché, c'est-à-dire doit être reçu:
- modèle
- La commande ou la liste des commandes qui doivent initialiser l'état du composant
- résultat d'exécution. Le résultat de l'exécution au moment de l'initialisation peut être nécessaire pour valider l'autorisation utilisateur, comme dans les exemples de cet article.
La liste des arguments de la fonction d'initialisation (init) dépend de la logique du composant et peut être arbitraire. Il peut y avoir plusieurs fonctions d'initialisation. Supposons, pour un composant d'autorisation, deux options d'initialisation peuvent être fournies: avec un jeton de session et avec les données utilisateur.
Le code qui utilise le composant, après l'initialisation, doit transmettre des commandes à elm runtime à l'aide de la fonction Cmd.map .
Mutation
La fonction de mise à jour du composant doit être appelée pour chaque message de composant. À la suite de l'exécution, la fonction renvoie un triple:
- nouveau modèle ou nouvel état (modèle);
- commande ou liste de commandes pour Elm runtime (Cmd Msg). Les commandes peuvent être des commandes pour exécuter des requêtes HTTP, interagir avec des ports, etc.
- résultat de l'exécution (peut-être retourner). Peut-être que le type a deux états Nothing et Just a. Dans notre cas, rien - il n'y a pas de résultat, juste un - il y a un résultat. Par exemple, pour l'autorisation, le résultat peut être Just (Authentified UserData) - l'utilisateur est connecté avec les données UserData.
Après la mutation, le code utilisant le composant doit mettre à jour le modèle du composant et passer des commandes à l'exécution Elm à l'aide de la fonction Cmd.map .
Arguments requis pour la fonction de mise à jour, selon l'architecture de l'application Elm:
- Message (Msg)
- modèle (modèle).
Si nécessaire, la liste des arguments peut être complétée.
Soumission
La fonction de vue est appelée lorsqu'il est nécessaire d'insérer la vue des composants dans la vue générale de l'application.
L'argument requis de la fonction de vue doit être le modèle de composant. Si nécessaire, la liste des arguments peut être complétée.
Le résultat de l'exécution de la fonction d'affichage doit être transmis à la fonction Html.map .
Intégration d'applications
L' exemple décrit deux composants: Auth et Question . Composantes des principes décrits ci-dessus. Considérez comment ils peuvent être intégrés dans l' application .
Tout d'abord, déterminons comment notre application devrait fonctionner. Il y a un bouton sur l'écran, lorsque vous cliquez dessus:
- pour un utilisateur non autorisé, un formulaire d'autorisation s'affiche, après autorisation - un formulaire de publication de questions;
- Pour un utilisateur autorisé, un formulaire de placement de question s'affiche.
Pour décrire l'application dont vous avez besoin:
- Modèle
- Messages (Msg)
- point de départ de l'application (principal);
- fonction d'initialisation (init);
- fonction de mutation;
- fonction de présentation;
- fonction d'abonnement.
Modèle
type alias Model = { user: User , ui: Maybe Ui } type Ui = AuthUi Component.Auth.Model | QuestionUi Component.Question.Model
Le modèle contient des informations sur l'utilisateur (utilisateur) et le type de l'interface actuelle (ui). L'interface peut être dans l'état par défaut (Nothing) ou l'un des composants Just a.
Pour décrire les composants, nous utilisons le type Ui, qui associe (balise) chaque modèle de composant à une variante spécifique d'un ensemble de types. Par exemple, une balise AuthUi associe un modèle d'autorisation (Component.Auth.Model) à un modèle d'application.
Des messages
type Msg = OpenPopup | AuthMsg Component.Auth.Msg | QuestionMsg Component.Question.Msg
Dans les messages, vous devez baliser tous les messages des composants et les inclure dans les messages de l'application. Les balises AuthMsg et QuestionMsg lient respectivement les messages du composant d'autorisation et la question de l'utilisateur.
Un message OpenPopup est nécessaire pour traiter une demande d'ouverture d'une interface.
Fonction principale
main : Program Never Model Msg main = Html.program { init = init , update = update , subscriptions = subscriptions , view = view }
Le point d'entrée de l'application est généralement décrit pour une application Elm.
Fonction d'initialisation
init : ( Model, Cmd Msg ) init = ( initModel, Cmd.none ) initModel : Model initModel = { user = Anonymous , ui = Nothing }
La fonction d'initialisation crée un modèle de départ et ne nécessite pas l'exécution de commandes.
Fonction de mutation
Code source de la fonction update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.ui) of (OpenPopup, Nothing) -> case Component.Auth.init model.user of (authModel, commands, Just (Component.Auth.Authenticated userData)) -> let (questionModel, questionCommands, _) = Component.Question.init userData in ( { model | ui = Just <| QuestionUi questionModel, user = User userData }, Cmd.batch [Cmd.map AuthMsg commands, Cmd.map QuestionMsg questionCommands] ) (authModel, commands, _) -> ( { model | ui = Just <| AuthUi authModel }, Cmd.map AuthMsg commands ) (AuthMsg authMsg, Just (AuthUi authModel)) -> case Component.Auth.update authMsg authModel of (_, commands, Just (Component.Auth.Authenticated userData)) -> let (questionModel, questionCommands, _) = Component.Question.init userData in ( { model | ui = Just <| QuestionUi questionModel, user = User userData }, Cmd.batch [Cmd.map AuthMsg commands, Cmd.map QuestionMsg questionCommands] ) (newAuthModel, commands, _) -> ( { model | ui = Just <| AuthUi newAuthModel }, Cmd.map AuthMsg commands ) (QuestionMsg questionMsg, Just (QuestionUi questionModel)) -> case Component.Question.update questionMsg questionModel of (_, commands, Just (Component.Question.Saved record)) -> ( { model | ui = Nothing }, Cmd.map QuestionMsg commands ) (newQuestionModel, commands, _) -> ( { model | ui = Just <| QuestionUi newQuestionModel }, Cmd.map QuestionMsg commands ) _ -> ( model, Cmd.none )
Parce que le modèle et les messages de l'application sont connectés, nous traiterons quelques messages (Msg) et le type d'interface (model.ui: Ui).
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.ui) of
Logique de travail
Si un message OpenPopup est reçu et que l'interface par défaut est spécifiée dans le modèle (model.ui = Nothing), nous initialisons le composant Auth. Si le composant Auth signale que l'utilisateur est autorisé, nous initialisons le composant Question et l'enregistrons dans le modèle d'application. Sinon, nous enregistrons le modèle de composant dans le modèle d'application.
(OpenPopup, Nothing) -> case Component.Auth.init model.user of (authModel, commands, Just (Component.Auth.Authenticated userData)) -> let (questionModel, questionCommands, _) = Component.Question.init userData in ( { model | ui = Just <| QuestionUi questionModel, user = User userData }, Cmd.batch [Cmd.map AuthMsg commands, Cmd.map QuestionMsg questionCommands] ) (authModel, commands, _) -> ( { model | ui = Just <| AuthUi authModel }, Cmd.map AuthMsg commands )
Si un message avec la balise AuthMsg est reçu et que l'interface d'autorisation est spécifiée dans le modèle (model.ui = Just (AuthUi authModel)), nous transmettons le message de composant et le modèle de composant à la fonction Auth.update. En conséquence, nous obtenons un nouveau modèle de composant, des commandes et un résultat.
Si l'utilisateur est autorisé, nous initialisons le composant Question, sinon nous mettons à jour les données d'interface dans le modèle d'application.
(AuthMsg authMsg, Just (AuthUi authModel)) -> case Component.Auth.update authMsg authModel of (_, commands, Just (Component.Auth.Authenticated userData)) -> let (questionModel, questionCommands, _) = Component.Question.init userData in ( { model | ui = Just <| QuestionUi questionModel, user = User userData }, Cmd.batch [Cmd.map AuthMsg commands, Cmd.map QuestionMsg questionCommands] ) (newAuthModel, commands, _) -> ( { model | ui = Just <| AuthUi newAuthModel }, Cmd.map AuthMsg commands )
Comme pour le composant Auth, les messages du composant Question sont traités. Si la question est publiée avec succès, l'interface passe à la valeur par défaut (model.ui = Nothing).
(QuestionMsg questionMsg, Just (QuestionUi questionModel)) -> case Component.Question.update questionMsg questionModel of (_, commands, Just (Component.Question.Saved record)) -> ( { model | ui = Nothing }, Cmd.map QuestionMsg commands ) (newQuestionModel, commands, _) -> ( { model | ui = Just <| QuestionUi newQuestionModel }, Cmd.map QuestionMsg commands )
Tous les autres cas sont ignorés.
_ -> ( model, Cmd.none )
Fonction d'affichage
view : Model -> Html Msg view model = case model.ui of Nothing -> div [] [ div [] [ button [ Events.onClick OpenPopup ] [ text "Open popup" ] ] ] Just (AuthUi authModel) -> Component.Auth.view authModel |> Html.map AuthMsg Just (QuestionUi questionModel) -> Component.Question.view questionModel |> Html.map QuestionMsg
Selon le type d'interface (model.ui), la fonction de présentation génère une interface par défaut ou appelle la fonction de présentation du composant et mappe le type de message du composant au type de message d'application (Html.map).
Fonction d'abonnement
subscriptions : Model -> Sub Msg subscriptions model = Sub.none
Pas d'abonnement.
Suivant
Cet exemple, bien qu'un peu plus pratique, mais assez naïf. Ce qui manque:
- bloquer l'interaction avec l'application pendant le processus de téléchargement;
- validation des données. Nécessite une conversation séparée;
- fenêtre vraiment pop-up avec la possibilité de fermer.