TL;DR полный рабочий пример

Многие фреймворки используют подход запекания в маршрутизации/навигации как фундаментальную часть архитектуры, но действительно ли это хорошая идея? Некоторым приложениям, возможно, даже не нужны концепции маршрутов или URL-адресов, и они все равно вынуждены впихивать свое приложение в эти идеи. С веб-приложениями мы часто зацикливаемся на этих идеях и думаем о нескольких страницах, между которыми мы можем перемещаться, но на самом деле мы имеем дело с переходами между различными состояниями нашего приложения. Это может или не должно быть представлено в URL-адресе в адресной строке. Если мы подумаем о простом примере, таком как теперь вездесущий Счетчик, его можно представить как наличие нескольких страниц (текущее значение счетчика), между которыми вы перемещаетесь, увеличивая и уменьшая.

Итак, давайте попробуем реализовать концепцию навигации для существующего примера Counter (здесь сжато, но, пожалуйста, следуйте руководству по стилю!).

Наша цель в этой реализации будет заключаться в том, чтобы все было максимально просто с архитектурной точки зрения. Но как на самом деле будет работать навигация и какое место она занимает в архитектуре Elm (TEA)? Навигация явно является побочным эффектом, она может произойти только в результате взаимодействия пользователя с навигацией в браузере (назад, вперед и т. д.) или нашим приложением Elm, требующим такого эффекта с помощью команд. Итак, для нашего примера со счетчиком нам нужно обработать оба этих случая:

  1. Когда наш счетчик изменит значение, обновите URL
  2. Когда пользователь переходит, обновите нашу модель, чтобы отразить URL-адрес.

Существует несколько библиотек, связанных с навигацией, но я обнаружил, что elm-route-url имеет чистый подход и хорошо придерживается TEA. Вам даже не нужно думать о подписках или командах, просто разберитесь с обновлениями моделей и сообщениями!

Давайте сначала установим то, что нам нужно (я включаю elm-lang/navigation, чтобы мы могли включить тип Location в одну из сигнатур, но это не обязательно).

elm-package install rgrempel/elm-route-url
elm-package install elm-lang/navigation

Чтобы навигация работала, elm-route-url просит нас реализовать две функции: delta2url и location2messages. Первый будет запускаться всякий раз, когда ваша модель обновляется, и вы должны вернуть Maybe UrlChange (если вы не знакомы с типом Maybe, ознакомьтесь с руководством). Он получит предыдущее и текущее состояние модели, из которых вы можете выполнить любые необходимые проверки, прежде чем вычислять новый URL-адрес. В нашем контрпримере мы собираемся создать новую запись в истории для каждого изменения URL-адреса и, чтобы сделать его еще проще, использовать хэши, чтобы обновление страницы работало должным образом при запуске через elm-reactor.

delta2url : Model -> Model -> Maybe UrlChange
delta2url previous current =
  Just (UrlChange NewEntry ("#" ++ toString current))

Вторая функция, location2messages, будет вызываться всякий раз, когда наше местоположение изменяется из-за событий, происходящих в браузере, а не в нашей модели. Вместо непосредственного обновления модели, как в update, мы отправляем сообщения, чтобы создать состояние, которого мы хотим достичь для нашего текущего местоположения. Обратите внимание, что elm-route-url гарантирует, что delta2url не сработает после отправки сообщения через location2messages. Любые возможные циклы и повторяющиеся записи в истории автоматически исключаются.

location2messages : Location -> List Msg
location2messages location =
  case String.toInt <| String.dropLeft 1 location.hash of
    Ok count ->
      [Set count]
    _ ->
      []

Учитывая новое местоположение, мы берем хеш-часть без # и пытаемся преобразовать ее в целое число, которое мы можем использовать в качестве значения счетчика. В любой другой ситуации мы вообще не отправляем никакого сообщения, а вместо этого будет использоваться значение инициализации счетчика. В этом примере я анализирую хэш вручную для простоты, но elm-route-url предоставляет помощников для создания и анализа местоположений с помощью RouteUrl.Builder. Обязательно загляните позже.

Последнее, что нам нужно сделать, это подключить все в новой RouteUrl.program, эти функции, конечно, не будут вызывать себя волшебным образом :)

main : Program Never
main =
  RouteUrl.program
    { init = (0, Cmd.none)
    , subscriptions = (\_ -> Sub.none)
    , update = update
    , view = view
    , delta2url = delta2url
    , location2messages = location2messages
    }

Это все! Мы реализовали навигацию, не затрагивая нашу модель, определение сообщений или функцию обновления. Этот очень простой, но мощный API также работает для более реалистичных сценариев SPA, но это для другой статьи. А пока, может быть, взглянем на приведенные ниже упражнения?

/J

Упражнения:

  • Мой пример создает много записей истории. Можете ли вы ограничить это, скажем, числами, делящимися на 10?
  • Вместо ручного анализа местоположения используйте RouteUrl.Builder.fromHash
  • Можете ли вы реализовать URL-адреса путей вместо хэшей?

Окончательный полностью рабочий пример: