简体   繁体   English

如何在Elm中创建有状态的,模块化的,独立的Web组件?

[英]How do you create stateful, modular, self-contained web components in Elm?

Suppose you want to create a UI which has 3 buttons. 假设您要创建一个包含3个按钮的UI。 When you click in one of them, the others are released. 当您单击其中一个时,其他人将被释放。 In JavaScript, you could write: 在JavaScript中,您可以写:

 var elements = ["Foo","Bar","Tot"].map(function(name){ var element = document.getElementById(name); element.onclick = function(){ elements.map(function(element){ element.className = 'button'; }); element.className = 'button selected'; }; return element; }); 
 .button { border: 1px solid black; cursor: pointer; margin: 4px; padding: 4px; } .selected { background-color: #DDDDDD; } 
 <div> <span id='Foo' class='button'>Foo</span> <span id='Bar' class='button'>Bar</span> <span id='Tot' class='button'>Tot</span> </div> 

That is stateful, but not modular, self contained nor pure. 这是有状态的,但不是模块化的,自包含的,也不是纯粹的。 In fact, it is so bad the state (a ternary bit) isn't even obvious. 事实上,状态(三元位)甚至不是很明显。 You can not inject it inside another model, how many times you want. 你不能将它注入另一个模型,你想要多少次。

Most of the answers provided here so far are stateful, but not modular. 到目前为止,这里提供的大多数答案都是有状态的,但不是模块化的。 The issue is that using that strategy, you can't drop a component inside another without the parent knowing about the children's model . 问题是使用该策略,如果父母不知道孩子的模型 ,就不能将组件放入另一个组件中。 Ideally, that would be abstracted away - the parent shouldn't need to mention the model of the child nodes on its own model, nor manually plumbing state from parent to node should be necessary. 理想情况下,这将被抽象掉 - 父母不应该在其自己的模型上提及子节点的模型,也不需要从父节点到节点的手动管道状态。 If I want to create a list of the app above, I don't want to store the state of each child node on the parent. 如果我想创建上面的应用程序列表,我不想将每个子节点的状态存储在父节点上。

How do you create stateful, modular, self-contained web components in Elm? 如何在Elm中创建有状态的,模块化的,独立的Web组件?

Elm can satisfy each of those requirements, making your component stateful, modular, self-contained, and pure. Elm可以满足每个要求,使您的组件有状态,模块化,自包含和纯粹。 Here's an example in Elm using StartApp.Simple (pardon the inline styling): 这是Elm使用StartApp.Simple的一个例子(原谅内联样式):

import StartApp.Simple exposing (start)
import Html exposing (Html, div, span, text)
import Html.Attributes exposing (id, class, style)
import Html.Events exposing (onClick)

type alias Model =
  { elements : List String
  , selected : Maybe String
  }

init : Model
init =
  { elements = [ "Foo", "Bar", "Tot" ]
  , selected = Nothing
  }

type Action
  = Select String

update : Action -> Model -> Model
update action model =
  case action of
    Select s ->
      { model | selected = Just s }

view : Signal.Address Action -> Model -> Html
view address model =
  let 
    btn txt =
      span
        [ id txt
        , buttonStyle txt
        , onClick address <| Select txt
        ] [ text txt ]

    buttonStyle txt =
      style (
        [ ("border", "1px solid black")
        , ("cursor", "pointer")
        , ("margin", "4px")
        , ("solid", "4px")
        ] ++ (styleWhenSelected txt))

    styleWhenSelected txt =
      case model.selected of
        Nothing -> []
        Just s ->
          if s == txt then
            [ ("background-color", "#DDDDDD") ]
          else
            []
  in
    div [] <| List.map btn model.elements


main =
  start
    { model = init
    , update = update
    , view = view
    }

You have a clearly defined, statically typed model, an explicit and limited number of actions that can be performed against that model, and a type-safe html rendering engine. 您有一个明确定义的静态类型模型,可以针对该模型执行的显式且有限数量的操作,以及类型安全的html呈现引擎。

Take a look at the Elm Architecture Tutorial for more information. 有关更多信息,请查看Elm Architecture Tutorial

I just saw Chad's answer, while I was writing mine. 我刚看到乍得的答案,而我正在写我的。 This one also uses the Elm Architecture , but uses you original class names in the Html, and has a "stronger" model. 这个也使用Elm Architecture ,但在Html中使用原始类名,并且具有“更强”的模型。 The nice part about the stronger model is that you literally see the three bits like you mentioned in your question. 关于更强大的模型的好处在于你真正看到了你在问题中提到的三位。 There is also less implicit coupling between the name of the id and the actual button. id的名称和实际按钮之间的隐式耦合也较少。 But it leaves you with some duplicated names that you may or may not want. 但它会留下一些您可能想要或不想要的重复名称。 Depends on how much you want this coupling. 取决于你想要这种耦合多少。

import StartApp.Simple as StartApp
import Html as H exposing (Html)
import Html.Attributes as HA
import Html.Events as HE

type alias Model =
  { foo : Bool
  , bar : Bool
  , tot : Bool
  }

type Action
  = Foo
  | Bar
  | Tot

model : Model
model =
  { foo = False
  , bar = False
  , tot = False
  }

update : Action -> Model -> Model
update clicked _ =
  case clicked of
    Foo -> { model | foo = True }
    Bar -> { model | bar = True }
    Tot -> { model | tot = True }

view : Signal.Address Action -> Model -> Html
view addr { foo, bar, tot } =
  [ foo, bar, tot ]
  |> List.map2 (viewButton addr) buttons
  |> H.div []

buttons : List (String, Action)
buttons =
  [ ("Foo", Foo)
  , ("Bar", Bar)
  , ("Tot", Tot)
  ]

viewButton : Signal.Address Action -> (String, Action) -> Bool -> Html
viewButton addr (id, action) selected =
  H.span
    [ HA.id id
    , HA.classList
      [ ("button", True)
      , ("selected", selected)
      ]
    , HE.onClick addr action
    ]
    [ H.text id
    ]

buttonStyle =

main =
  StartApp.start
    { model = model
    , view = view
    , update = update
    }

As devdave suggest, nesting is the only way that I have found to modularise components. 正如devdave建议的那样,嵌套是我发现模块化组件的唯一方法。

I have implemented a similar example which you can see live here: http://afcastano.github.io/elm-nested-component-communication/ 我已经实现了一个类似的例子,你可以在这里看到: http//afcastano.github.io/elm-nested-component-communication/

The idea is that children expose functions to get the properties of their own model. 这个想法是孩子们公开函数来获得他们自己模型的属性。 This functions can in turn call even more nested functions for children components. 此函数可以反过来为子组件调用更多嵌套函数。

Check out the Readme.md of this repo for code examples: https://github.com/afcastano/elm-nested-component-communication 查看此repo的Readme.md以获取代码示例: https//github.com/afcastano/elm-nested-component-communication

Here is another version of the same thing :) 这是同一件事的另一个版本:)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import StartApp.Simple

type alias Model = Maybe String -- the id of the selected span

type Action = ButtonClick String

update : Action -> Model -> Model
update action model =
  case action of
    ButtonClick id ->
      Just id


view : Signal.Address Action -> Model -> Html
view address model =
  let
    renderButton id' label' =
      let
        selectedClass =
          case model of
            Just modelId -> if modelId == id' then " selected" else ""
            Nothing -> ""
      in
      span [ id id', class ("button" ++ selectedClass), onClick address (ButtonClick id') ] [ text label' ]
  in
  div []
    [ renderButton "foo" "Foo"
    , renderButton "bar" "Bar"
    , renderButton "tot" "Tot"
    ]


main =
  StartApp.Simple.start { model = Nothing, update = update, view = view }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM