Kami terus berbicara tentang Elm 0,18 .
Elm Nyaman dan canggung
Elm Nyaman dan canggung. Json.Encoder dan Json.Decoder
Elm Nyaman dan canggung. Http, Tugas
Pada artikel ini, kami akan mempertimbangkan masalah arsitektur aplikasi Elm dan opsi yang mungkin untuk menerapkan pendekatan pengembangan komponen.
Sebagai tugas, pertimbangkan penerapan jendela tarik-turun yang memungkinkan pengguna terdaftar untuk menambahkan pertanyaan. Dalam kasus pengguna anonim, ia menyarankan untuk masuk atau mendaftar terlebih dahulu.
Kami juga berasumsi bahwa selanjutnya mungkin perlu untuk menerapkan penerimaan jenis konten pengguna lain, tetapi logika bekerja dengan pengguna yang berwenang dan anonim akan tetap sama.
Komposisi canggung
Kode sumber implementasi yang naif . Sebagai bagian dari implementasi ini, kami akan menyimpan semuanya dalam satu model.
Semua data yang diperlukan untuk otorisasi dan polling pengguna berada pada level yang sama dalam model. Situasi yang sama dengan pesan (Pesan).
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
Tipe antarmuka digambarkan sebagai tipe serikat Ui, yang digunakan dengan tipe Maybe.
type Ui = LoginUi -- Popup shown with authentication form | QuestionUi -- Popup shown with textarea to leave user question
Jadi, ui = Tidak ada yang menjelaskan tidak adanya jendela pop-up, dan popup Just terbuka dengan antarmuka tertentu.
Dalam fungsi pembaruan, pasangan dicocokkan, pesan dan data pengguna. Tindakan berbeda dilakukan tergantung pada pasangan ini.
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.user) of
Misalnya, ketika Anda mengklik tombol "Open popup", pesan OpenPopup dihasilkan. Pesan OpenPopup dalam fungsi pembaruan ditangani dengan berbagai cara. Untuk pengguna anonim, formulir otorisasi dihasilkan, dan untuk pengguna yang berwenang, formulir di mana Anda dapat meninggalkan pertanyaan.
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.user) of
Jelas, pendekatan ini mungkin memiliki masalah dengan pertumbuhan fungsi aplikasi:
- Tidak ada pengelompokan data dalam model dan pesan. Semuanya terletak pada satu bidang. Dengan demikian, tidak ada batas komponen, perubahan logika satu bagian kemungkinan besar akan mempengaruhi sisanya;
- penggunaan kembali kode dimungkinkan berdasarkan prinsip copy-paste dengan semua konsekuensi berikutnya.
Komposisi yang nyaman
Kode sumber adalah implementasi yang mudah . Dalam kerangka implementasi ini, kami akan mencoba membagi proyek menjadi komponen independen. Ketergantungan yang diizinkan antar komponen.
Struktur proyek:
- di folder Jenis jenis kustom dinyatakan;
- komponen khusus dinyatakan dalam folder Komponen;
- Titik masuk proyek file Main.elm;
- File login.json dan questions.json digunakan masing-masing sebagai data uji untuk respons server terhadap otorisasi dan menyimpan informasi tentang pertanyaan tersebut.
Komponen khusus
Setiap komponen, berdasarkan arsitektur bahasa, harus mengandung:
- Model
- Pesan (Pesan)
- hasil eksekusi (Pengembalian);
- fungsi inisialisasi (init);
- fungsi mutasi (pembaruan);
- lihat fungsi.
Setiap komponen dapat berisi langganan jika perlu.

Fig. 1. Diagram aktivitas komponen
Inisialisasi
Setiap komponen harus dipicu, mis. harus diterima:
- model
- Perintah atau daftar perintah yang harus menginisialisasi keadaan komponen
- hasil eksekusi. Hasil eksekusi pada saat inisialisasi mungkin diperlukan untuk memvalidasi otorisasi pengguna, seperti dalam contoh artikel ini.
Daftar argumen dari fungsi inisialisasi (init) tergantung pada logika komponen dan dapat arbitrer. Mungkin ada beberapa fungsi inisialisasi. Misalkan, untuk komponen otorisasi, dua opsi inisialisasi dapat disediakan: dengan token sesi dan dengan data pengguna.
Kode yang menggunakan komponen, setelah inisialisasi, harus meneruskan perintah ke elm runtime menggunakan fungsi Cmd.map .
Mutasi
Fungsi komponen pembaruan harus dipanggil untuk setiap pesan komponen. Sebagai hasil dari eksekusi, fungsi mengembalikan tiga:
- model baru atau negara baru (Model);
- perintah atau daftar perintah untuk runtime Elm (Cmd Msg). Perintah dapat berupa perintah untuk mengeksekusi permintaan HTTP, berinteraksi dengan port, dan lainnya;
- hasil eksekusi (Mungkin Kembali). Mungkin tipe memiliki dua status Nothing dan Just a. Dalam kasus kami, Tidak ada - tidak ada hasil, Hanya a - ada hasilnya. Misalnya, untuk otorisasi, hasilnya mungkin Just (Authentified UserData) - pengguna login dengan data UserData.
Setelah mutasi, kode yang menggunakan komponen harus memperbarui model komponen dan meneruskan perintah ke Elm runtime menggunakan fungsi Cmd.map .
Argumen yang diperlukan untuk fungsi pembaruan, sesuai dengan arsitektur aplikasi Elm:
- Pesan (Pesan)
- model (Model).
Jika perlu, daftar argumen dapat ditambahkan.
Kiriman
Fungsi tampilan dipanggil bila perlu untuk memasukkan tampilan komponen ke tampilan aplikasi umum.
Argumen yang diperlukan untuk fungsi tampilan harus menjadi model komponen. Jika perlu, daftar argumen dapat ditambahkan.
Hasil dari mengeksekusi fungsi tampilan harus diteruskan ke fungsi Html.map .
Integrasi aplikasi
Contoh tersebut menjelaskan dua komponen: Auth dan Question . Komponen prinsip yang dijelaskan di atas. Pertimbangkan bagaimana mereka dapat diintegrasikan ke dalam aplikasi .
Pertama, mari kita tentukan bagaimana aplikasi kita seharusnya bekerja. Ada tombol di layar, ketika Anda mengkliknya:
- untuk pengguna yang tidak sah, formulir otorisasi ditampilkan, setelah otorisasi - formulir posting pertanyaan;
- Untuk pengguna yang berwenang, formulir penempatan pertanyaan ditampilkan.
Untuk menggambarkan aplikasi yang Anda butuhkan:
- Model
- Pesan (Pesan)
- titik awal aplikasi (utama);
- fungsi inisialisasi (init);
- fungsi mutasi;
- fungsi presentasi;
- fungsi berlangganan.
Model
type alias Model = { user: User , ui: Maybe Ui } type Ui = AuthUi Component.Auth.Model | QuestionUi Component.Question.Model
Model ini berisi informasi tentang pengguna (pengguna) dan jenis antarmuka saat ini (ui). Antarmuka dapat berupa status default (Tidak Ada) atau salah satu dari komponen Just a.
Untuk menjelaskan komponen, kami menggunakan tipe Ui, yang mengaitkan (menandai) setiap model komponen dengan varian spesifik dari serangkaian jenis. Misalnya, tag AuthUi mengaitkan model otorisasi (Component.Auth.Model) dengan model aplikasi.
Pesan
type Msg = OpenPopup | AuthMsg Component.Auth.Msg | QuestionMsg Component.Question.Msg
Dalam pesan, Anda harus menandai semua pesan komponen dan memasukkannya ke dalam pesan aplikasi. Tag AuthMsg dan QuestionMsg masing-masing menghubungkan pesan dari komponen otorisasi dan pertanyaan pengguna.
Pesan OpenPopup diperlukan untuk memproses permintaan untuk membuka antarmuka.
Fungsi utama
main : Program Never Model Msg main = Html.program { init = init , update = update , subscriptions = subscriptions , view = view }
Titik masuk aplikasi dijelaskan secara khusus untuk aplikasi Elm.
Fungsi inisialisasi
init : ( Model, Cmd Msg ) init = ( initModel, Cmd.none ) initModel : Model initModel = { user = Anonymous , ui = Nothing }
Fungsi inisialisasi membuat model awal dan tidak memerlukan eksekusi perintah.
Fungsi mutasi
Kode sumber fungsi 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 )
Karena model dan pesan ke aplikasi terhubung, kami akan memproses beberapa pesan (Msg) dan jenis antarmuka (model.ui: Ui).
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case (msg, model.ui) of
Logika kerja
Jika pesan OpenPopup diterima dan antarmuka default ditentukan dalam model (model.ui = Tidak Ada), maka kita menginisialisasi komponen Auth. Jika komponen Auth melaporkan bahwa pengguna diotorisasi, kami menginisialisasi komponen Pertanyaan dan menyimpannya dalam model aplikasi. Kalau tidak, kami menyimpan model komponen dalam model aplikasi.
(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 )
Jika pesan dengan tag AuthMsg diterima dan antarmuka otorisasi ditentukan dalam model (model.ui = Just (AuthUi authModel)), maka kami meneruskan pesan komponen dan model komponen ke fungsi Auth.update. Sebagai hasilnya, kami mendapatkan model komponen baru, perintah, dan hasil.
Jika pengguna diotorisasi, kami menginisialisasi komponen Pertanyaan, jika tidak, kami memperbarui data antarmuka dalam model aplikasi.
(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 )
Demikian pula dengan komponen Auth, pesan untuk komponen Pertanyaan diproses. Jika pertanyaan berhasil diposting, antarmuka berubah ke default (model.ui = Tidak Ada).
(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 )
Semua kasus lainnya diabaikan.
_ -> ( model, Cmd.none )
Lihat fungsi
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
Bergantung pada tipe antarmuka (model.ui), fungsi presentasi menghasilkan antarmuka default atau memanggil fungsi presentasi komponen dan memetakan tipe pesan komponen ke tipe pesan aplikasi (Html.map).
Fungsi berlangganan
subscriptions : Model -> Sub Msg subscriptions model = Sub.none
Tidak ada berlangganan.
Selanjutnya
Contoh ini, meski sedikit lebih nyaman, tetapi cukup naif. Apa yang hilang:
- memblokir interaksi dengan aplikasi selama proses pengunduhan;
- validasi data. Membutuhkan percakapan terpisah;
- jendela benar-benar pop-up dengan kemampuan untuk menutup.