榆树 舒适而笨拙。 组成

我们继续谈论榆木0.18


榆树 舒适尴尬
榆树 舒适而笨拙。 Json.Encoder和Json.Decoder
榆树 舒适而笨拙。 Http,任务


在本文中,我们将考虑Elm应用程序体系结构的问题以及实现组件开发方法的可能选择。


作为一项任务,请考虑实现一个允许已注册用户添加问题的下拉窗口。 对于匿名用户,他建议先登录或注册。


我们还假设随后可能需要实现其他类型的用户内容的接收,但是与授权用户和匿名用户合作的逻辑将保持不变。


尴尬的构图


天真的实现的源代码 。 作为此实现的一部分,我们将所有内容存储在一个模型中。


授权和用户轮询所需的所有数据在模型中处于同一级别。 与消息(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 

接口类型被描述为联合类型Ui,与Maybe类型一起使用。


 type Ui = LoginUi -- Popup shown with authentication form | QuestionUi -- Popup shown with textarea to leave user question 

因此,ui = Nothing描述了没有弹出窗口,并且Just弹出窗口通过特定的界面打开。

在更新功能中,配对对,消息和用户数据是匹配的。 根据这对执行不同的操作。


 update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.user) of 

例如,当您单击“打开弹出窗口”按钮时,将生成一个OpenPopup消息。 更新功能中的OpenPopup消息以各种方式处理。 对于匿名用户,将生成一个授权表单,对于一个授权用户,将生成一个您可以在其中提问的表单。


 update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.user) of -- Anonymous user message handling section (OpenPopup, Anonymous) -> ( { model | ui = Just LoginUi, message = "" }, Cmd.none) -- Authenticated user message handling section (OpenPopup, User userName) -> ( { model | ui = Just QuestionUi, message = "" }, Cmd.none) 

显然,此方法可能会随着应用程序功能的增长而出现问题:


  1. 模型和消息中没有数据分组。 一切都在一架飞机上。 因此,没有组件界限;一个部分的逻辑变化很可能会影响其余部分;
  2. 根据复制粘贴的原则,代码重用是可能的,并随之而来。

方便的组成


源代码是一个方便的实现 。 在此实现的框架中,我们将尝试将项目分为独立的组件。 组件之间允许的依赖关系。


项目结构:


  1. 在Type文件夹中声明自定义类型;
  2. 自定义组件在Component文件夹中声明;
  3. Main.elm文件项目入口点;
  4. login.json和questions.json文件分别用作服务器响应授权和保存有关问题的信息的测试数据。

定制组件


基于语言体系结构的每个组件应包含:


  1. 型号
  2. 讯息(讯息)
  3. 执行结果(返回);
  4. 初始化函数(init);
  5. 变异函数(更新);
  6. 查看功能。

如果需要,每个组件都可以包含一个订阅。


图片
图 1.组件活动图


初始化


每个组件都必须被触发,即 必须收到:


  1. 型号
  2. 应该初始化组件状态的命令或命令列表
  3. 执行结果。 如本文示例中所示,初始化时执行的结果对于验证用户的授权可能是必需的。

初始化函数(init)的参数列表取决于组件的逻辑,并且可以是任意的。 可能有几个初始化函数。 假设对于授权组件,可以提供两个初始化选项:带有会话令牌和带有用户数据。


初始化后,使用组件的代码必须使用Cmd.map函数将命令传递给Elm运行时。


变异


必须为每个组件消息调用更新组件函数。 作为执行的结果,该函数返回一个三元组:


  1. 新模型或新状态(Model);
  2. Elm运行时(Cmd Msg)的命令或命令列表。 这些命令可以是用于执行HTTP请求,与端口进行交互等的命令。
  3. 执行结果(也许返回)。 类型可能有两种状态:无和仅有。 在我们的情况下,没有-没有结果,只有-有结果。 例如,对于授权,结果可能是Just(Authenticated UserData)-用户使用UserData数据登录。

突变后,使用组件的代码应更新组件模型,并使用Cmd.map函数将命令传递给Elm运行时。


根据Elm应用程序体系结构,update函数的必需参数:


  1. 讯息(讯息)
  2. 型号(Model)。

如有必要,可以补充参数列表。


投稿


当需要将组件视图插入到常规应用程序视图中时,将调用视图函数。


视图函数的必需参数必须是组件模型。 如有必要,可以补充参数列表。


执行view函数的结果必须传递给Html.map函数。


应用整合


示例描述了两个组件: AuthQuestion 。 上述原理的组成部分。 考虑如何将它们集成到应用程序中


首先,让我们确定我们的应用程序应如何工作。 当您单击它时,屏幕上有一个按钮:


  1. 对于未经授权的用户,授权后将显示授权表单-问题发布表单;
  2. 对于授权用户,将显示问题放置表格。

要描述您需要的应​​用程序:


  1. 型号
  2. 讯息(讯息)
  3. 申请起点(主要);
  4. 初始化函数(init);
  5. 突变功能
  6. 展示功能;
  7. 订阅功能。

型号


 type alias Model = { user: User , ui: Maybe Ui } type Ui = AuthUi Component.Auth.Model | QuestionUi Component.Question.Model 

该模型包含有关用户(用户)和当前界面类型(ui)的信息。 该接口可以处于默认状态(无),也可以处于“仅一个”组件之一。


为了描述组件,我们使用Ui类型,该类型将每个组件模型与一组类型的特定变体相关联(标记)。 例如,AuthUi标记将授权模型(Component.Auth.Model)与应用程序模型相关联。


留言内容


 type Msg = OpenPopup | AuthMsg Component.Auth.Msg | QuestionMsg Component.Question.Msg 

在消息中,必须标记所有组件消息并将它们包括在应用程序消息中。 AuthMsg和QuestionMsg标签分别链接授权组件的消息和用户的问题。


需要OpenPopup消息来处理打开接口的请求。


主要功能


 main : Program Never Model Msg main = Html.program { init = init , update = update , subscriptions = subscriptions , view = view } 

通常针对Elm应用程序描述应用程序入口点。


初始化功能


 init : ( Model, Cmd Msg ) init = ( initModel, Cmd.none ) initModel : Model initModel = { user = Anonymous , ui = Nothing } 

初始化函数创建一个启动模型,不需要执行命令。


变异功能


函数源代码
 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 ) 

因为 连接到应用程序的模型和消息之后,我们将处理几个消息(Msg)和接口类型(model.ui:Ui)。


 update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.ui) of 

工作逻辑


如果收到OpenPopup消息,并且在模型中指定了默认接口(model.ui = Nothing),那么我们将初始化Auth组件。 如果Auth组件报告用户已获得授权,我们将初始化Question组件并将其保存在应用程序模型中。 否则,我们将组件模型保存在应用程序模型中。


 (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标签的消息,并且在模型中指定了授权接口(model.ui = Just(AuthUi authModel)),则我们将组件消息和组件模型传递给Auth.update函数。 结果,我们得到了一个新的组件模型,命令和结果。


如果用户被授权,我们将初始化Question组件,否则我们将更新应用程序模型中的接口数据。


 (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 ) 

与Auth组件类似,对Question组件的消息进行处理。 如果问题已成功发布,则界面将更改为默认界面(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 ) 

所有其他情况都将被忽略。


  _ -> ( model, Cmd.none ) 

查看功能


 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 

根据接口的类型(model.ui),表示功能要么生成默认接口,要么调用组件表示功能,并将组件消息类型映射到应用程序消息类型(Html.map)。


订阅功能


 subscriptions : Model -> Sub Msg subscriptions model = Sub.none 

没有订阅。


下一个


这个例子,虽然更方便一些,但是相当幼稚。 缺少什么:


  1. 在下载过程中阻止与应用程序的交互;
  2. 数据验证。 需要单独的对话;
  3. 真正具有关闭功能的弹出窗口。

Source: https://habr.com/ru/post/zh-CN424341/


All Articles