如何在Elm中创建有状态的,模块化的,自包含的Web组件?

时间:2015-12-01 10:39:56

标签: javascript elm

假设您要创建一个包含3个按钮的UI。当您单击其中一个时,其他人将被释放。在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>
  

这是有状态的,但不是模块化的,自包含的,也不是纯粹的。事实上,状态(三元位)甚至不是很明显。你不能将它注入另一个模型,你想要多少次。

到目前为止,这里提供的大多数答案都是有状态的,但不是模块化的。问题在于,使用该策略时,如果父母不知道孩子的模型,则不能将组件放在另一个中。理想情况下,这将被抽象掉 - 父母不应该在其自己的模型上提及子节点的模型,也不需要从父节点到节点的手动管道状态。如果我想创建上面的应用程序列表,我不想将每个子节点的状态存储在父节点上。

如何在Elm中创建有状态,模块化,自包含的Web组件?

4 个答案:

答案 0 :(得分:3)

Elm可以满足这些要求,使您的组件具有状态,模块化,自包含和纯粹。这是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
    }

您有一个明确定义的静态类型模型,可以针对该模型执行的显式且有限数量的操作,以及类型安全的html呈现引擎。

请查看Elm Architecture Tutorial了解详情。

答案 1 :(得分:1)

在我写作的时候,我只看到了查德的回答。这个也使用了Elm Architecture,但在Html中使用了原始的类名,并且具有更强的&#34;模型。关于更强大的模型的好处在于你真正看到了你在问题中提到的三位。 id的名称和实际按钮之间的隐式耦合也较少。但它会留下一些您可能想要或不想要的重复名称。取决于你想要这种耦合的程度。

public static ArrayList<SimilarDogs> buildDogGroups(ArrayList<Dog> dogs){
    ArrayList<SimilarDogs> similarDogGroups = new ArrayList<SimilarDogs>();

    //Generate a KeyList
    ArrayList<String> uniqueKeys = new ArrayList<>();

    for(Dog dataRecord : dogs){
        if(!uniqueKeys.contains(dataRecord.getType() + dataRecord.getFurColor() + dataRecord.getCountry())){
            uniqueKeys.add(dataRecord.getType() + dataRecord.getFurColor() + dataRecord.getCountry()); 
        }
    }

    //Generate SimilarDogGroups
    for(String uniKey : uniqueKeys){
        SimilarDogs smlDog = new SimilarDogs();
        ArrayList<Dog> similarDogRecords = new ArrayList<Dog>();
            for(DataRecord dataRecord : dogs){
                if(uniKey.equals(dataRecord.getType() + dataRecord.getFurColor() + dataRecord.getCountry())){
                    similarDogRecords.add(dataRecord);

                }
            }
            smlDog.setSameJobGroup(similarDogRecords);
        similarDogGroups.add(smlDog);
    }

    return similarDogGroups;
}

答案 2 :(得分:1)

正如devdave建议的那样,嵌套是我发现模块化组件的唯一方法。

我已经实现了一个类似的示例,您可以在此处看到:http://afcastano.github.io/elm-nested-component-communication/

这个想法是孩子们公开函数来获取他们自己模型的属性。这个函数可以反过来调用子组件的更多嵌套函数。

查看此repo的Readme.md以获取代码示例: https://github.com/afcastano/elm-nested-component-communication

答案 3 :(得分:0)

这是同一件事的另一个版本:)

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 }