Составление программ в архитектуре вяза - PullRequest
0 голосов
/ 03 июля 2018

Предположим, я хочу создать веб-страницу с двумя компонентами, например, Navbar и Body. Эти два компонента не взаимодействуют друг с другом и могут разрабатываться независимо. Итак, у меня есть два файла elm, в каждом из которых есть следующие компоненты:

type Model = ...

type Msg = ...

init : (Model, Cmd Msg)

update : Msg -> Model -> (Model, Cmd Msg)

view : Model -> Html Msg

Если предположить, что они оба работают нормально, как мы их составим, чтобы создать программу, которая имеет оба этих компонента?

Я пытался написать что-то вроде этого:

type Model = {body : Body.Model , navbar : Navbar.Model}
type Msg = BodyMsg Body.Msg | NavbarMsg Navbar.Msg

view : Model -> Html Msg
view model = div [] [Body.view model.body, Navbar.view model.navbar]

update : Msg -> Model -> (Model, Cmd Msg)
update = ...

Вышеизложенное быстро становится ужасным, когда я пытаюсь написать эту функцию обновления. В частности, как только я извлекаю сообщения Msg из функций обновления Cmd из Navbar.update или Body.update, как мне извлечь их и снова передать их этим функциям? Кроме того, представленная выше функция просмотра выглядит не совсем идиоматично.

Каким образом elm-архитектура рекомендует способ решения этой проблемы? Этот шаблон идиоматичен в архитектуре вязов?

Ответы [ 3 ]

0 голосов
/ 04 июля 2018

Да, вы на правильном пути.

В представлении вам нужно использовать Html.map.

view : Model -> Html Msg
view model =
  div []
    [ Html.map BodyMsg (Body.view model.body)
    , Html.map NavbarMsg (Navbar.view model.navbar)
    ]

Body.view model.body имеет тип Html Body.Msg, что требует от нас использования Html.map для получения правильного типа Html Msg. Аналогично для Navbar.view model.navbar.

И для функции update вы бы написали это так:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    BodyMsg bodyMsg ->
      let
        (newBody, newCmd) = Body.update bodyMsg model.body
      in
        { model | body = newBody } ! [ Cmd.map BodyMsg newCmd ]

    NavbarMsg navbarMsg ->
      let
        (newNavbar, newCmd) = Navbar.update navbarMsg model.navbar
      in
        { model | navbar = newNavbar } ! [ Cmd.map NavbarMsg newCmd ]

В случае BodyMsg, newBody имеет тип Body.Model, поэтому мы можем установить для него поле body в model. Однако newCmd имеет тип Cmd Body.Msg, поэтому прежде чем мы сможем вернуться, нам нужно использовать Cmd.map, чтобы получить правильный тип возврата Cmd Msg.

Аналогичные рассуждения можно использовать для случая NavbarMsg.

Кроме того, представленная выше функция просмотра выглядит не совсем идиоматично.

Что вас беспокоит в коде просмотра?

N.B. В этом ответе предполагается, что вы используете Elm 0.18 .

0 голосов
/ 05 июля 2018

Я думаю, что @dwaynecrooks охватил технические аспекты вопроса. Но я считаю, что ваша проблема подразумевает и дизайн.


Как вырастить код вяза?

Как отмечали другие: мышление в терминах компонентов почти наверняка приведет вас к не очень привлекательному пути с Elm. (Многие люди начинают там. Я и моя команда начинали там более 2 лет назад, и нам потребовалось 3 приложения / основные редизайны, чтобы достичь точки, где, я думаю, мы можем быть довольны хотя бы основами.)

Вместо компонентов я предлагаю вам подумать о приложении Elm как о дереве. Каждый узел вашего дерева представляет уровень абстракции и описывает поведение вашего приложения на этом уровне. Когда вы чувствуете, что для данного уровня слишком много деталей, вы можете начать думать о том, как новые, более низкие уровни абстракции могут быть представлены как дочерние узлы.

На практике каждый узел реализован в своем собственном модуле Elm: родители импортируют своих детей. Вы также можете подумать, что вам не нужно придерживаться обычных model/update/view подписей, скорее вам следует сосредоточиться на особенностях домена вашего приложения. Это то, что - в моем прочтении - Ричард Фельдман делает в своем приложении Real World SPA . И Жизнь Эвана из файлового разговора тоже связана с этим вопросом.


Дело navbar + body

Что касается вашего конкретного случая - это не редкий случай - вот мой опыт. Если мы говорим, что в нашем веб-приложении есть навигационная панель, а затем какое-то тело, это довольно статичное описание приложения. Этот вид описания может соответствовать мышлению, основанному на компонентах, но он менее полезен, если вы хотите получить элегантное приложение Elm.

Вместо этого стоит попытаться описать поведение вашего приложения на этом уровне абстракции, которое может звучать примерно так: Пользователь может выбрать x, y, z предметов в навигационной панели. Нажатие на эти предметы повлияет на предмет q, а также на тело - a или b. Он также может щелкнуть v на панели навигации, чтобы отобразить всплывающее окно, или выполнить команду w, которая выводит его из приложения.

Если вы берете это описание и применяете логику, которую я описал выше, вы, вероятно, должны в конечном итоге создать какую-то схему, в которой большая часть вашей панели навигации описана в вашем самом высоком уровне абстракции. Это включает в себя предметы x, y, z, v и поведение a, b, w. Теперь поведение a может означать, что должна отображаться конкретная обогащенная страница, которая имеет свое собственное подробное поведение, описанное на более низком уровне абстракции, тогда как поведение b может означать, что в зависимости от выбора некоторый контент должен быть загружен, и снова детали этого процесса загрузки прорабатываются на более низком уровне абстракции. И так далее.

Когда мы начали подходить к проблеме таким образом, стало гораздо проще выяснить, как разделить логику и как справляться с особыми случаями. Мы поняли, например, что когда кто-то сказал, что страница хочет отображать некоторые вещи «в панели навигации», она действительно имела в виду, что панель навигации должна сворачиваться (или преобразовываться) для конкретной страницы, чтобы страница могла отображать свой собственный заголовок. в этой области.

В этом нам помогло сосредоточиться на поведении приложения, а не на областях статического содержимого.

0 голосов
/ 03 июля 2018

Это в основном путь, да. На GitHub есть популярный пример более крупного SPA в Elm. Здесь вы можете увидеть Main.elm, который заботится о отображении сообщений от каждой страницы: https://github.com/rtfeldman/elm-spa-example/blob/master/src/Main.elm

Одна вещь, которой не хватает в вашем примере, - это отображение типа сообщения, которое абсолютно необходимо. Я предполагаю, что вы оставили это, чтобы иметь меньший пост, но по моему опыту, это реальная часть, где приходит образец.

Однако вы должны стараться не эмулировать компонентный подход, такой как React. Просто используйте функции. Отдельные страницы в SPA являются одним из примеров, где имеет смысл иметь выделенный тип сообщения и соответствующие функции, как если бы вы работали с программой .

В этой статье объясняется общий подход к масштабированию более крупного приложения Elm, а также упоминается о том, что не нужно выделять сообщения для каждого компонента.

...