- vừa được xem lúc

[Declarative Programming + Elm] Bài 13 - Flags & Cmd/Sub

0 0 20

Người đăng: Semi Art

Theo Viblo Asia

Trước hết chúc ta hãy thử xem định nghĩa của Browser.element có thêm yếu tố nào mới so với Browser.sandbox.

element : { init : flags -> ( model, Cmd msg ) , view : model -> Html msg , update : msg -> model -> ( model, Cmd msg ) , subscriptions : model -> Sub msg } -> Program flags model msg
  • init - lúc này có dạng tiếp nhận một tham số có tên là flags và trả về một Tuple (model, Cmd msg).
  • view - không có gì thay đổi và vẫn nhận một tham số đầu vào duy nhất là một bản ghi model để tạo ra một cấu trúc HTML msg.
  • update - vẫn nhận vào một tin nhắn sự kiện msg được gửi tới từ trình điều khiển chính Browser.element và trả về một Tuple (model, Cmd msg).
  • subscriptions - nhận vào một bản ghi model và tạo ra một chương trình theo dõi sự kiện của trình duyệt Sub msg.
  • Và cuối cùng là kết quả trả về của trình đóng gói Browser.element - là một chương trình Program nhận vào các tham số flags, model, msg.

Flags

Công dụng của CmdSub thì chúng ta đã có nhắc tới trước đó rồi, và ở đây chúng ta đang có thêm một yếu tố mới đó là flags - tạm dịch là các giá trị gắn cờ - xuất hiện ở trình khởi tạo init và trình kết quả Program. Như vậy là flags sẽ được truyền vào từ môi trường bên ngoài Program và sử dụng cho trình khởi tạo init khi người dùng tải trang web lần đầu.

Để gắn một chương trình Program đã được biên dịch thành tệp element.js vào một website đã xây dựng trước đó thì chúng ta vẫn thực hiện tương tự như khi sử dụng Browser.sandbox. Đó là tạo một phần tử HTML với id đặc định và truyền vào lệnh khởi tạo Program.

<html>
<head> <meta charset="UTF-8"> <title> Main </title> <script src="elm-time.js"> </script>
</head>
<body> <div id="elm-time"></div> <script> var app = Elm.Main.init({ node: document.getElementById("elm-time"), flags: Date.now() }); </script>
</body>
</html>

Đó là cách mà chúng ta truyền dữ liệu khởi tạo nội dung vào một Element. Điều này rất quan trọng, bởi vì thông thường thì chúng ta sẽ muốn tạo ra các Element có khả năng tái sử dụng nhiều lần. Và vì vậy nên những dữ liệu mang tính chất tạo cấu hình để Element hoạt động dựa trên đó - không nên được viết cố định vào code xử lý logic của Element.

Ví dụ bạn có thể sẽ rất muốn tạo ra một thanh điều hướng Navbar bằng Elm và dữ liệu để tạo nội dung cụ thể cho thanh Navbar sẽ được truyền vào tại thời điểm khởi tạo như trong code ví dụ ở trên. Hoặc, chúng ta sẽ chỉ cần truyền vào duy nhất một URL trỏ tới API cung cấp danh sách các đề mục Category của trang web ở dạng JSON.

Như vậy Element mà bạn xây dựng sẽ có thể được chia sẻ để sử dụng cho các trang web khác và người sử dụng code của bạn sẽ không cần phải đọc hiểu Elm. Việc tìm vị trí để gắn nội dung tĩnh trong code Elm sẽ không còn là yếu tố cần thiết nữa, và đó cũng chính là mong muốn chung khi chúng ta viết bất kì một chương trình nào, trong bất kỳ ngôn ngữ nào.

HTTP Request

Bây giờ chúng ta sẽ cùng xem xét ví dụ về một Element đơn giản thực hiện việc gửi yêu cầu HTTP request tới một server để lấy nội dung, được Elm giới thiệu trong tài liệu hướng dẫn.

Ở đây chúng ta đang có một trình hiển thị nội dung văn bản Reader với các yếu tố kiến trúc có phương thức hoạt động hơi khác một chút so với Sandboxed Buttons. Chương trình khởi đầu tại Main với tham số flags tạm thời để trống vì chúng ta chưa cần gắn Element này vào trang web nào cả.

module Main exposing (main) import Browser exposing (..)
import Http exposing (..)
import Html exposing (..) -- MAIN - - - - - - - - - - - - - - - - - - main : Program () Model Msg
main = Browser.element (Reader init update subscriptions view) type alias Reader = { init : () -> (Model, Cmd Msg) , update : Msg -> Model -> (Model, Cmd Msg) , subscriptions : Model -> Sub Msg , view : Model -> Html Msg }

Ngay sau đó thì init sẽ được kích hoạt để tạo ra bản ghi trạng thái đầu tiên kèm theo lệnh gửi yêu cầu truy vấn nội dung văn bản qua HTTP.

-- INIT - - - - - - - - - - - - - - - - - - type Model = Loading | Failure | Success String type Msg = GotResponse (Result Http.Error String) init : () -> (Model, Cmd Msg)
init _ = let command = Http.get { url = "https://elm-lang.org/assets/public-opinion.txt" , expect = Http.expectString GotResponse } in ( Loading, command )

Như vậy là giá trị bản ghi mà init khởi tạo ban đầu sẽ là một giá trị bất kỳ thuộc kiểu Loading và sẽ được Program truyền vào view. Ngay sau khi khởi tạo giá trị Loading thì init cũng đồng thời gửi yêu cầu truy vấn nội dung qua HTTP. Ngay khi có kết quả truy vấn thì Program sẽ chuyển một giá trị thuộc kiểu Cmd Msg tới cho trình update.

-- VIEW - - - - - - - - - - - - - - - - - - view : Model -> Html Msg
view model = case model of Loading -> text "Loading..." Failure -> text "I was unable to load your book." Success fullText -> pre [] [ text fullText ]

Trong ví dụ này thì view sẽ tạo ra cấu trúc HTML bằng cách Pattern Matching kiểu bản ghi đầu vào. Ban đầu khi nhận được kiểu Loading thì sẽ chỉ hiện dòng chữ "Loading...". Các trường hợp pattern dữ liệu khác sẽ có thể xuất hiện khi trình update được kích hoạt.

-- UPDATE - - - - - - - - - - - - - - - - - - update : Msg -> Model -> (Model, Cmd Msg)
update msg model = case msg of GotResponse (Err _) -> ( Failure, Cmd.none ) GotResponse (Ok txt) -> ( Success txt, Cmd.none )

Ở đây chúng ta chỉ có một trường hợp trình update được kích hoạt đó là khi nhận được thông báo GotResponse của thao tác gửi yêu cầu HTTP Request. Sau khi tạo ra bản ghi Model mới là Failure hoặc Success txt thì chúng ta không cần kích hoạt thêm lệnh tương tác nào Cmd.none. Bản ghi Model mới theo chu trình xử lý mà chúng ta đã biết sẽ được gửi tới view để cập nhật nội dung hiển thị.

-- SUBSCRIPTIONS - - - - - - - - - - - - - - - - - - subscriptions : Model -> Sub Msg
subscriptions model = Sub.none

Yếu tố kiến trúc cuối cùng là subscriptions do chưa cần sử dụng tới nên được biểu thị bằng giá trị Sub.none vô nghĩa đối với trình điều khiển Program.

cd Documents && cd learn-elm
elm reactor

http://localhost:8000/src/Main.elm

Hiện tại thì tệp public-opinion.txt được cung cấp tại URL do Elm cung cấp ở trình init vẫn đang khả dụng và bạn sẽ thấy dòng chữ "Loading..." sẽ chỉ được hiển thị trong một khoảng thời gian ngắn ngay sau khi trang web được tải về. Sau đó thì nội dung hiển thị sẽ được cập nhật như trong ảnh chụp màn hình.

Cmd & Sub

Như chúng ta đã thấy thì yếu tố tin nhắn mệnh lệnh Cmd Msg đã khiến cho logic hoạt động của chương trình Program tạo ra bởi Browser.element trở nên linh động hơn rất nhiều so với Browser.sandbox.

Tin nhắn sự kiện Msg có thể được gửi tới từ rất nhiều điểm khác nhau trong chương trình - khi kết thúc thao tác khởi tạo bản ghi Model đầu tiên, khi người dùng tương tác với cấu trúc Html Msg được tạo ra bởi view, khi kết thúc mỗi case xử lý của chính chương trình update, và khi Sub Msg nhận biết được một sự kiện xảy ra trong tổng quan môi trường trình duyệt web.

Như vậy yếu tố còn lại mà chúng ta cần tìm hiểu là subscriptions - trình khởi tạo các Sub Msg theo dõi các sự kiện xảy ra trong môi trường trình duyệt web tổng quan. Vậy có những kiểu sự kiện nào có thể được gắn Sub Msg? Tất cả đều được liệt kê tại đây: Browser.Events

  • onClick - khi có một click chuột xảy ra ở bất kỳ đâu trong trang web
  • onMouseMove - khi người dùng di chuyển con trỏ chuột ở bất kỳ đâu
  • onMouseDown - khi người dùng nhấn xuống nút click chuột bên trái/phải ở bất kỳ đâu
  • onMouseUp - khi người dùng nhả nút click chuột ở bất kỳ đâu
  • onKeyPress - khi người dùng nhấn một phím bất kỳ và nhả ra
  • onKeyDown - khi người dùng nhấn một phím bất kỳ
  • onKeyUp - khi người dùng nhả một phím bất kỳ
  • onAnimationFrame - khi có animation hoạt động
  • onResize - khi người dùng thu hẹp hoặc mở rộng cửa sổ trình duyệt
  • onVisibilityChange - khi trạng thái hiển thị của cửa sổ trình duyệt thay đổi

Thực tế thì Sub Msg còn có thể được tạo ra từ những chương trình khác nữa ví dụ như Time.every được cung cấp bởi package: elm/time. Và vì vậy nên Program lúc này sẽ có khá nhiều khả năng để tương tác với môi trường bên ngoài.

Bây giờ chúng ta sẽ thử xem xét một ví dụ khác mà Elm đặt trên trang chủ để hiểu rõ hơn về các yếu tố Cmd/Sub/Msg.

elm install elm/time
module Main exposing (main) import Browser exposing (..)
import Time exposing (..)
import Task exposing (..)
import Html exposing (..) -- MAIN - - - - - - - - - - - - - - - - - -
main : Program () Model Msg
main = Browser.element (Clock init view update subscriptions) type alias Clock = { init : () -> (Model, Cmd Msg) , view : Model -> Html Msg , update : Msg -> Model -> (Model, Cmd Msg) , subscriptions : Model -> Sub Msg }

Ở đây chúng ta sẽ tạo ra một chiếc đồng hồ đơn giản bằng cách truy vấn thông tin thời gian từ hệ thống sau đó cập nhật trạng thái hiển thị mỗi giây. Do không cần sử dụng tới tham số đầu vào khi khởi tạo Program nên chúng ta vẫn sẽ để trống tham số flags.

-- INIT - - - - - - - - - - - - - - - - - - type alias Model = { zone : Time.Zone , time : Time.Posix } type Msg = Tick Time.Posix | AdjustTimeZone Time.Zone init : () -> (Model, Cmd Msg)
init _ = ( Model Time.utc (Time.millisToPosix 0) , Task.perform AdjustTimeZone Time.here )

Ngoài module Time đã nói tới ở trên thì trình init còn sử dụng thêm module Task trong package: elm/core, để thực hiện một tác vụ được hẹn trễ lại một chút sau khi đã khởi tạo bản ghi Model đầu tiên. Đó là thao tác điều chỉnh múi giờ cho phù hợp với vị trí hiện tại của người dùng trên trái đất.

-- VIEW - - - - - - - - - - - - - - - - - - view : Model -> Html Msg
view model = let hour = String.fromInt (Time.toHour model.zone model.time) minute = String.fromInt (Time.toMinute model.zone model.time) second = String.fromInt (Time.toSecond model.zone model.time) in h1 [] [ text (hour ++ ":" ++ minute ++ ":" ++ second) ]

Cấu trúc Html Msg đơn giản được view tạo ra với một thẻ <h1> duy nhất có chứa thông tin về thời gian từ bản ghi Model được truyền vào.

-- UPDATE - - - - - - - - - - - - - - - - - - update : Msg -> Model -> (Model, Cmd Msg)
update msg model = case msg of AdjustTimeZone newZone -> ( { model | zone = newZone } , Cmd.none ) Tick time -> ( { model | time = time } , Cmd.none )

Tác vụ điều chỉnh múi giờ AdjustTimeZone được phát động bởi init sau khi thực hiện xong sẽ gửi Cmd Msg tới Program và sau đó Msg được gửi tới update để cập nhật thông tin hiển thị. Tới đây thì update sẽ tạo ra bản ghi Model mới và dừng vòng lặp lệnh tương tác Cmd Msg với Cmd.none.

Ngoài ra thì update còn có một trường hợp Msg được gửi tới thuộc kiểu Tick time được tạo ra bởi subscriptions.

-- SUBSCRIPTIONS - - - - - - - - - - - - - - - - - - subscriptions : Model -> Sub Msg
subscriptions _ = Time.every 1000 Tick

Sau mỗi giây 1000 ms thì Sub Msg được tạo ra bởi Tick sẽ gửi Msg tới trình điều khiển chính ProgramMsg tiếp tục được chuyển tới trình update để thực hiện case còn lại.

http://localhost:8000/src/Main.elm

Như vậy là chúng ta đã điểm tra một lượt những điểm mới mẻ trong kiến trúc của Browser.element. Đây cũng là những yếu tố cần thiết để chúng ta có thể tạo ra những Element hữu ích cho trang web đã xây dựng trước đó.

(chưa đăng tải) [Declarative Programming + Elm] Bài 14 - Wiki Element (mở đầu)

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 66

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 47

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 41

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 72

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 280

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 50