From 3d4d206ad309a077d51e3ca3a45721e3586efc9f Mon Sep 17 00:00:00 2001 From: yicheng Date: Sat, 7 May 2016 20:24:03 +0800 Subject: [PATCH 01/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf6c36a..7f71f74 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # The Elm Architecture -This tutorial outlines “The Elm Architecture” which you will see in all [Elm][] programs, from [TodoMVC][] and [dreamwriter][] to the code running in production at [NoRedInk][] and [CircuitHub][]. The basic pattern is useful whether you are writing your front-end in Elm or JS or whatever else. +這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, from [TodoMVC][] and [dreamwriter][] to the code running in production at [NoRedInk][] and [CircuitHub][]. The basic pattern is useful whether you are writing your front-end in Elm or JS or whatever else. [Elm]: http://elm-lang.org/ [TodoMVC]: https://github.com/evancz/elm-todomvc From 59de7c3be6197763666e26c515ce3d3fe9efb805 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sat, 7 May 2016 20:36:05 +0800 Subject: [PATCH 02/19] Update README.md --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7f71f74..afcc39f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # The Elm Architecture -這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, from [TodoMVC][] and [dreamwriter][] to the code running in production at [NoRedInk][] and [CircuitHub][]. The basic pattern is useful whether you are writing your front-end in Elm or JS or whatever else. +這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, 包含 [TodoMVC][] 、 [dreamwriter][]以及正式上線的 [NoRedInk][] 、 [CircuitHub][]. 了解這個設計模式將對你在 Elm 中的程式或任何其他編程都很有幫助。 [Elm]: http://elm-lang.org/ [TodoMVC]: https://github.com/evancz/elm-todomvc @@ -8,7 +8,7 @@ [NoRedInk]: https://www.noredink.com/ [CircuitHub]: https://www.circuithub.com/ -The Elm Architecture is a simple pattern for infinitely nestable components. It is great for modularity, code reuse, and testing. Ultimately, this pattern makes it easy to create complex web apps in a way that stays modular. We will run through 8 examples, slowly building on core principles and patterns: + Elm 架構中的設計模式讓你的元件可以有無限嵌套. 對於模組化、 程式碼復用、 測試很有幫助,最終讓你可以簡單的設計出一個複雜但模組化的web程式。 下面將有八個範例 1. [Counter](http://evancz.github.io/elm-architecture-tutorial/examples/1.html) 2. [Pair of counters](http://evancz.github.io/elm-architecture-tutorial/examples/2.html) @@ -19,24 +19,23 @@ The Elm Architecture is a simple pattern for infinitely nestable components. It 7. [List of GIF fetchers](http://evancz.github.io/elm-architecture-tutorial/examples/7.html) 8. [Pair of animating squares](http://evancz.github.io/elm-architecture-tutorial/examples/8.html) -This tutorial will really help! It will bring out the concepts and ideas necessary to get to make examples 7 and 8 super easy. Investing in the foundation will be worth it! -One very interesting aspect of the architecture in all these programs is that it *emerges* from Elm naturally. The language design itself leads you towards this architecture whether you have read this document and know the benefits or not. I actually discovered this pattern just using Elm and have been shocked by its simplicity and power. -**Note**: To follow along with this tutorial with code, [install Elm](http://elm-lang.org/install) and fork this repo. Each example in the tutorial gives instructions of how to run the code. +**注意**: 要運行這些範例需先, [安裝 Elm](http://elm-lang.org/install) 以及 fork this repo. 每個範例都會教導你,該如何去運行它 -## The Basic Pattern -The logic of every Elm program will break up into three cleanly separated parts: +## Elm中的基本設計模式概念 + +每個 Elm program 都有如下三個區塊架構: + * model * update * view -You can pretty reliably start with the following skeleton and then iteratively fill in details for your particular case. -> If you are new to reading Elm code, check out the [language docs](http://elm-lang.org/docs) which covers everything from syntax to getting into a “functional mindset”. The first two sections of [the complete guide](http://elm-lang.org/docs#complete-guide) will get you up to speed! +> 如果你還沒學習過elm可以先參考 [language docs](http://elm-lang.org/docs) 與[the complete guide](http://elm-lang.org/docs#complete-guide) ```elm -- MODEL From e1ed0e25de36867fb41540a772dd336147cc0800 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 07:58:59 +0800 Subject: [PATCH 03/19] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index afcc39f..060756a 100644 --- a/README.md +++ b/README.md @@ -61,24 +61,24 @@ view = ... ``` -This tutorial is all about this pattern and small variations and extensions. +這個教程都是由上面範例的這個設計模式 與一些小變化及插件所組成的。 ## Example 1: A Counter **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/1.html) / [see code](examples/1/)** -Our first example is a simple counter that can be incremented or decremented. +第一個範例是一個簡單的計數器,可以進行增加與減少 -[The code](examples/1/Counter.elm) starts with a very simple model. We just need to keep track of a single number: +[The code](examples/1/Counter.elm) 由一個很簡單的 model所組成, 我們要做的只是追蹤一個會改變的數字 ```elm type alias Model = Int ``` -When it comes to updating our model, things are relatively simple again. We define a set of actions that can be performed, and an `update` function to actually perform those actions: +當 model 更新時, 我們定義一系列 actions, 以及一個 `update` function用來接收 actions並執行相應的case -```elm +```elm type Action = Increment | Decrement update : Action -> Model -> Model @@ -88,11 +88,11 @@ update action model = Decrement -> model - 1 ``` -Notice that our `Action` [union type][] does not *do* anything. It simply describes the actions that are possible. If someone decides our counter should be doubled when a certain button is pressed, that will be a new case in `Action`. This means our code ends up very clear about how our model can be transformed. Anyone reading this code will immediately know what is allowed and what is not. Furthermore, they will know exactly how to add new features in a consistent way. +需要注意的是 `Action` [union type][] 並沒有做任何事, 他只是描述了一個動作的型態. 假設有人想讓計數器在點擊某個按鈕時可以讓數字變double, 這將需要增加一個新的 `Action`類型,這可以保持我們的Model簡潔,並且他人可以清楚的知道他可以對這個model進行何種操作 [union type]: http://elm-lang.org/learn/Union-Types.elm -Finally, we create a way to `view` our `Model`. We are using [elm-html][] to create some HTML to show in a browser. We will create a div that contains: a decrement button, a div showing the current count, and an increment button. + `view` our `Model`.我們使用 [elm-html][]來創造一個 HTML,其它包含一個 div 裡面裝有 decrement button、 a div showing the current count, and an increment button. [elm-html]: http://elm-lang.org/blog/Blazing-Fast-Html.elm From ecef2fc145c2205739670d4986e9d057ea1f1a6e Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 09:06:39 +0800 Subject: [PATCH 04/19] Update README.md --- README.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 060756a..fc680cd 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ update action model = [union type]: http://elm-lang.org/learn/Union-Types.elm - `view` our `Model`.我們使用 [elm-html][]來創造一個 HTML,其它包含一個 div 裡面裝有 decrement button、 a div showing the current count, and an increment button. + `view` our `Model`.我們使用 [elm-html][]來創造一個 HTML,其它包含一個 div 裡面裝有一個 decrement button以及a div showing the current count與 an increment button. [elm-html]: http://elm-lang.org/blog/Blazing-Fast-Html.elm @@ -109,15 +109,15 @@ countStyle : Attribute countStyle = ... ``` +在 `view` 這個函式中的 `Address`我們將會在下一節提到! 在這裡我們使用了 `Model` 並產生一些 `Html`. 我們並沒有手動去更動DOM, +這讓這個 library [有了更好的自由度及優化][elm-html] 並且讓畫面 rendering的速度更快。 以及 `view`是一個function ,我們可使用 Elm中的 module system、 test frameworks與其他 libraries 來創造 views. -The tricky thing about our `view` function is the `Address`. We will dive into that in the next section! For now, I just want you to notice that **this code is entirely declarative**. We take in a `Model` and produce some `Html`. That is it. At no point do we mutate the DOM manually, which gives the library [much more freedom to make clever optimizations][elm-html] and actually makes rendering *faster* overall. It is crazy. Furthermore, `view` is a plain old function so we can get the full power of Elm’s module system, test frameworks, and libraries when creating views. - -This pattern is the essence of architecting Elm programs. Every example we see from now on will be a slight variation on this basic pattern: `Model`, `update`, `view`. +這種設計模式是Elm中的基本架構,接著的範例中都是以此種架構去進行 `Model`, `update`, `view`. ## Starting the Program -Pretty much all Elm programs will have a small bit of code that drives the whole application. For each example in this tutorial, that code is broken out into `Main.elm`. For our counter example, the interesting code looks like this: +在elm程式中,都會有一個主體的部分,程式從此開始進行,如同以下的範例均是以 `Main.elm`為主體 ```elm import Counter exposing (update, view) @@ -127,26 +127,24 @@ main = start { model = 0, update = update, view = view } ``` -We are using the [`StartApp`](https://github.com/evancz/start-app) package to wire together our initial model with the update and view functions. It is a small wrapper around Elm's [signals](http://elm-lang.org/learn/Using-Signals.elm) so that you do not need to dive into that concept yet. +我們使用了 [`StartApp`](https://github.com/evancz/start-app) 來建造初始的 model 、 update 與 view functions. 這即是Elm's [signals](http://elm-lang.org/learn/Using-Signals.elm)的概念,所以你暫時還不用了解signals的概念。 -The key to wiring up your application is the concept of an `Address`. Every event handler in our `view` function reports to a particular address. It just sends chunks of data along. The `StartApp` package monitors all the messages coming in to this address and feeds them into the `update` function. The model gets updated and [elm-html][] takes care of rendering the changes efficiently. +其中的關鍵點在於 `Address`。 每個 event handler 於我們的 `view` function 之中會回傳一個特別的 address. It just sends chunks of data along.而 `StartApp` package 監測每個來自 address 的訊息,並轉送到 `update` function. 接著 model 被更新 ,然後[elm-html][] 把view更新 -This means values flow through an Elm program in only one direction, something like this: + Elm 程式中的單向資料流如同下圖的概念 ![Signal Graph Summary](diagrams/signal-graph-summary.png) -The blue part is our core Elm program which is exactly the model/update/view pattern we have been discussing so far. When programming in Elm, you can mostly think inside this box and make great progress. - -Notice we are not *performing* actions as they get sent back to our app. We are simply sending some data over. This separation is a key detail, keeping our logic totally separate from our view code. +圖片中的藍色部分為 Elm 程式的核心,此即為我們先前提到的 model/update/view 概念。 未來在寫Elm程式時,即可以此種架構去規劃,讓邏輯的部分完全與View分開。 ## Example 2: A Pair of Counters **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/2.html) / [see code](examples/2/)** -In example 1 we created a basic counter, but how does that pattern scale when we want *two* counters? Can we keep things modular? +在範例1中,我們建立了一個簡單的計數器。但我們該如何擴展架構,當我們需要兩個計數器時呢? -Wouldn't it be great if we could reuse all the code from example 1? The crazy thing about the Elm Architecture is that **we can reuse code with absolutely no changes**. When we created the `Counter` module in example one, it encapsulated all the implementation details so we can use them elsewhere: + Elm架構中的優點是 **我們可以不更動程式碼**來達成擴展架構。 當我們在上個範例創造 `Counter` 模組時,他把細節封裝起來,所以我們可以把他用在別處。 ```elm module Counter (Model, init, Action, update, view) where @@ -162,9 +160,11 @@ update : Action -> Model -> Model view : Signal.Address Action -> Model -> Html ``` -Creating modular code is all about creating strong abstractions. We want boundaries which appropriately expose functionality and hide implementation. From outside of the `Counter` module, we just see a basic set of values: `Model`, `init`, `Action`, `update`, and `view`. We do not care at all how these things are implemented. In fact, it is *impossible* to know how these things are implemented. This means no one can rely on implementation details that were not made public. +創造模組化的code 是很抽象的。我們把一些 functionality 的部分提供出來並隱藏一些實做細節。在 `Counter` module外面,我們只看得到 +一些基本的關於 `Model`, `init`, `Action`, `update`, 與 `view`。我們並不在乎他們是如何被實做的。 事實上,這是不可能被知道的 +. 意思是當我們選擇不把他公開時,其他人無法知道我們的實做細節。 -So we can reuse our `Counter` module, but now we need to use it to create our `CounterPair`. As always, we start with a `Model`: +所以我們可以複用 `Counter` module,並用他來創造 `CounterPair`,和先前一樣,我們先從 `Model`開始: ```elm type alias Model = @@ -179,9 +179,10 @@ init top bottom = } ``` -Our `Model` is a record with two fields, one for each of the counters we would like to show on screen. This fully describes all of the application state. We also have an `init` function to create a new `Model` whenever we want. +這個 `Model` 擁有兩個區塊分別為顯示在螢幕的top和bottom。用來完整描述我們應用程式中的 state。 以及一個 `init` function +讓我們在未來更新`Model`所用。 -Next we describe the set of `Actions` we would like to support. This time our features should be: reset all counters, update the top counter, or update the bottom counter. +接著我們將定義 `Actions` ,將包含: reset all counters、 update the top counter與 update the bottom counter. ```elm type Action From 8718fbb5b5ff2560236fdd5c5c9ac3c438c126e6 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 10:48:23 +0800 Subject: [PATCH 05/19] Update README.md --- README.md | 88 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index fc680cd..397b738 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,8 @@ type Action | Bottom Counter.Action ``` -Notice that our [union type][] refers to the `Counter.Action` type, but we do not know the particulars of those actions. When we create our `update` function, we are mainly routing these `Counter.Actions` to the right place: +注意到的是,上面的 [union type][] 指向 `Counter.Action` type,但我們並不知道這些 actions,當我們創造 `update` function時, 我們才將 +這些 `Counter.Actions` 指向正確的地方: ```elm update : Action -> Model -> Model @@ -210,7 +211,7 @@ update action model = } ``` -So now the final thing to do is create a `view` function that shows both of our counters on screen along with a reset button. +最後,我們建造一個 `view` function用來在畫面上顯示兩個 counters 與一個 reset button. ```elm view : Signal.Address Action -> Model -> Html @@ -222,24 +223,24 @@ view address model = ] ``` -Notice that we are able to reuse the `Counter.view` function for both of our counters. For each counter we create a forwarding address. Essentially what we are doing here is saying, “these counters will tag all outgoing messages with `Top` or `Bottom` so we can tell the difference.” +我們可以重複使用 `Counter.view` function 於兩個 counters. 於每個 counter 我們創造一個 forwarding address.我們在這裡做的事為,這兩個計數器會把傳出的訊息以 `Top` or `Bottom` tag起來,讓我們可以區分。 -That is the whole thing. The cool thing is that we can keep nesting more and more. We can take the `CounterPair` module, expose the key values and functions, and create a `CounterPairPair` or whatever it is we need. +接著我們還可以讓他更巢狀化,我們可以使用 `CounterPair` module,指對外展示出一些 key values 與d functions, 再創造一個 `CounterPairPair` 或是任何你想要的。 ## Example 3: A Dynamic List of Counters **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/3.html) / [see code](examples/3/)** -A pair of counters is cool, but what about a list of counters where we can add and remove counters as we see fit? Can this pattern work for that too? +接著我們要來創造一系列的計數器,我們可使用add或remove來增加與減少畫面上計數器的數量。 -Again we can reuse the `Counter` module exactly as it was in example 1 and 2! +我們一樣複用 `Counter` module ,如同上面的範例。 ```elm module Counter (Model, init, Action, update, view) ``` -That means we can just get started on our `CounterList` module. As always, we begin with our `Model`: +接著我們可以直接開始設計 `CounterList` module,和之前一樣 ,先從 `Model`開始: ```elm type alias Model = @@ -250,12 +251,14 @@ type alias Model = type alias ID = Int ``` -Now our model has a list of counters, each annotated with a unique ID. These IDs allow us to distinguish between them, so if we need to update counter number 4 we have a nice way to refer to it. (This ID also gives us something convenient to [`key`][key] on when we are thinking about optimizing rendering, but that is not the focus of this tutorial!) Our model also contains a -`nextID` which helps us assign unique IDs to each counter as we add new ones. +現在我們的model有一系列的counter,每個均擁有一個獨特的ID,讓我們可以區別他們, +(這些 ID 還帶給我們於 rendering時具有最佳化的表現[`key`][key] ) + +我們的model 還包含了一個`nextID`這可以幫助我們在新增counter時,可以分配新的ID給他 [key]: http://package.elm-lang.org/packages/evancz/elm-html/latest/Html-Attributes#key -Now we can define the set of `Actions` that can be performed on our model. We want to be able to add counters, remove counters, and update certain counters. +現在我們來定義一系列的`Actions` 用來傳給 model。包含了 add counters、 remove counters與update certain counters. ```elm type Action @@ -264,7 +267,8 @@ type Action | Modify ID Counter.Action ``` -Our `Action` [union type][] is shockingly close to the high-level description. Now we can define our `update` function. + +現在我們可以定義`update` function. ```elm update : Action -> Model -> Model @@ -291,18 +295,16 @@ update action model = { model | counters = List.map updateCounter model.counters } ``` -Here is a high-level description of each case: +可以看下面的描述: - * `Insert` — First we create a new counter and put it at the end of - our counter list. Then we increment our `nextID` so that we have a fresh - ID next time around. + * `Insert` — 首先我們創造了一個新的計數器, 放在所有以建造的計數器的最後。 + 接著增加 `nextID` 來讓下一次使用。 - * `Remove` — Drop the first member of our counter list. + * `Remove` —從許多計數器中移除最前面那個 - * `Modify` — Run through all of our counters. If we find one with - a matching ID, we perform the given `Action` on that counter. + * `Modify` — 找尋這些計數器中與其符合的 ID, 我們在這個符合的計數器上執行這個 `Action` -All that is left to do now is to define the `view`. +最後是定義我們的 `view`. ```elm view : Signal.Address Action -> Model -> Html @@ -318,24 +320,22 @@ viewCounter address (id, model) = Counter.view (Signal.forwardTo address (Modify id)) model ``` -The fun part here is the `viewCounter` function. It uses the same old -`Counter.view` function, but in this case we provide a forwarding address that annotates all messages with the ID of the particular counter that is getting rendered. +有趣的地方在於其中的 `viewCounter` function. 他使用先前的 +`Counter.view` function,但這次,我們提供了一個 forwarding address 上面標住了所有被 rendered的counter的相關訊息。 -When we create the actual `view` function, we map `viewCounter` over all of our counters and create add and remove buttons that report to the `address` directly. +當我們真正創造 `view` function時, 我們對所有counters進行了`viewCounter`的map動作,並且創造兩個按鈕:add 、remove ,直接傳遞到 the `address` 中 -This ID trick can be used any time you want a dynamic number of subcomponents. Counters are very simple, but the pattern would work exactly the same if you had a list of user profiles or tweets or newsfeed items or product details. +上面這種創造 ID的方法,可以用於任何你想要於子組件創造動態數字時,也可應用在其他範例,例如: list of user profiles 、 tweets 或是 newsfeed items 以及 product details中。 ## Example 4: A Fancier List of Counters **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/4.html) / [see code](examples/4/)** -Okay, keeping things simple and modular on a dynamic list of counters is pretty cool, but instead of a general remove button, what if each counter had its own specific remove button? Surely *that* will mess things up! - -Nah, it works. +但是,如果我們想要不只是有一個移除按鈕,而是想讓每個按鈕擁有自己專屬的移除按鈕呢? -In this case our goals mean that we need a new way to view a `Counter` that adds a remove button. Interestingly, we can keep the `view` function from before and add a new `viewWithRemoveButton` function that provides a slightly different view of our underlying `Model`. This is pretty cool. We do not need to duplicate any code or do any crazy subtyping or overloading. We just add a new function to the public API to expose new functionality! +要這麼做的話,我們先保持原本的`view` function 不變,並且可以新增一個`viewWithRemoveButton`,如以下範例: ```elm module Counter (Model, init, Action, update, view, viewWithRemoveButton, Context) where @@ -357,9 +357,10 @@ viewWithRemoveButton context model = ] ``` -The `viewWithRemoveButton` function adds one extra button. Notice that the increment/decrement buttons send messages to the `actions` address but the delete button sends messages to the `remove` address. The messages we send along to `remove` are essentially saying, “hey, whoever owns me, remove me!” It is up to whoever owns this particular counter to do the removing. +`viewWithRemoveButton` function 增加了一個額外的按鈕,其中 increment/decrement 按紐送出訊息到 `actions` address 中,但移除按鈕 +送出訊息到 `remove` address中。 在 `remove` 訊息中寫了類似下面的文字, “任何擁有我的人請移除我!” -Now that we have our new `viewWithRemoveButton`, we can create a `CounterList` module which puts all the individual counters together. The `Model` is the same as in example 3: a list of counters and a unique ID. +現在我們有了新的 `viewWithRemoveButton`,我們可以建造一個 `CounterList` module 用來放置每個獨立的計數器, 其中 `Model` 和範例3的相同 a ```elm type alias Model = @@ -370,7 +371,7 @@ type alias Model = type alias ID = Int ``` -Our set of actions is a bit different. Instead of removing any old counter, we want to remove a specific one, so the `Remove` case now holds an ID. +但這裡的 actions 有一些改變,這裡我們想讓他可以移除特定的按鈕,所以 `Remove` case 現在擁有一個 ID。 ```elm type Action @@ -378,8 +379,7 @@ type Action | Remove ID | Modify ID Counter.Action ``` - -The `update` function is pretty similar to example 3 as well. +其中的`update` function 和範例 3的相同。 ```elm update : Action -> Model -> Model @@ -405,9 +405,10 @@ update action model = { model | counters = List.map updateCounter model.counters } ``` -In the case of `Remove`, we take out the counter that has the ID we are supposed to remove. Otherwise, the cases are quite close to how they were before. +其中的 `Remove`, 我們過濾出了想要的ID + -Finally, we put it all together in the `view`: +最後是 `view`: ```elm view : Signal.Address Action -> Model -> Html @@ -426,25 +427,26 @@ viewCounter address (id, model) = Counter.viewWithRemoveButton context model ``` -In our `viewCounter` function, we construct the `Counter.Context` to pass in all the necessary forwarding addresses. In both cases we annotate each `Counter.Action` so that we know which counter to modify or remove. +在 `viewCounter` function中,我們建造了 `Counter.Context` 用來傳遞所有必要的 forwarding addresses。在兩個情況,我們均標註了 `Counter.Action` 讓我們知道該移除或修改哪個counter、 -## Big Lessons So Far +## Big Lessons So Far(到目前為止) -**Basic Pattern** — Everything is built around a `Model`, a way to `update` that model, and a way to `view` that model. Everything is a variation on this basic pattern. +**Basic Pattern** — 所有東西都是圍繞著 `Model`所建構的,包含 `更新` model, 以及將model轉為 `view` ,都是根據這個設計模式在變動。 -**Nesting Modules** — Forwarding addresses makes it easy to nest our basic pattern, hiding implementation details entirely. We can nest this pattern arbitrarily deep, and each level only needs to know about what is going on one level lower. +**Nesting Modules** — Forwarding addresses 讓巢狀化更加簡單,把實做細節整個隱藏起來。 我們可以繼續往下一層深入實做,而每一層只需要知道在他的上一層發生了什麼事就好。 -**Adding Context** — Sometimes to `update` or `view` our model, extra information is needed. We can always add some `Context` to these functions and pass in all the additional information we need without complicating our `Model`. +**Adding Context** — 有時在 `更新` 或是 `檢視` 我們的 model時,額外的資訊是很必要的。我們可以增加一些 `Context`到這些 functions 在避免 `Model`變得更複雜的情況下,將這些資訊傳遞進去。 ```elm update : Context -> Action -> Model -> Model view : Context' -> Model -> Html ``` -At every level of nesting we can derive the specific `Context` needed for each submodule. +在巢狀結構下的每一層, 我們可以導出一些特定的 `Context`給子模組使用。 -**Testing is Easy** — All of the functions we have created are [pure functions][pure]. That makes it extremely easy to test your `update` function. There is no special initialization or mocking or configuration step, you just call the function with the arguments you would like to test. +**Testing is Easy** — 我們在這建立的所有function都是所謂的 [pure functions][pure].這讓你在測試你的 `update` function時 +變得很簡單。 在這之中沒有特定的步驟,你可以直接呼叫你想測試的函式與參數來進行測試。 [pure]: http://en.wikipedia.org/wiki/Pure_function @@ -453,11 +455,11 @@ At every level of nesting we can derive the specific `Context` needed for each s **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/5.html) / [see code](examples/5/)** -So we have covered how to create infinitely nestable components, but what happens when we want to do an HTTP request from somewhere in there? Or talk to a database? This example starts using [the `elm-effects` package][fx] to create a simple component that fetches random gifs from giphy.com with the topic “funny cats”. +我們展示了如何建立巢狀的元件, 但,我們該如何執行一個HTTP request呢?如何跟 database 取得資料?在這個範例中使用了 [the `elm-effects` package][fx]來建立一個簡單的元件,可以從 giphy.com取得隨機的 gifs 其中包含 “funny cats” topic。 [fx]: http://package.elm-lang.org/packages/evancz/elm-effects/latest -As you look through [the implementation](examples/5/RandomGif.elm), notice that it is pretty much the same code as the counter in example 1. The `Model` is very typical: +在這個範例[the implementation](examples/5/RandomGif.elm),與範例1很類似, `Model` 如下(非常典型)l: ```elm type alias Model = From 0cb59630b3f4c7ff68607e96972f75265e53cb95 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 11:34:27 +0800 Subject: [PATCH 06/19] Update README.md --- README.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 397b738..6aac1d5 100644 --- a/README.md +++ b/README.md @@ -468,7 +468,7 @@ type alias Model = } ``` -We need to know what the `topic` of the finder is and what `gifUrl` we are showing right this second. The only new thing in this example is that `init` and `update` have slightly fancier types: +我們需要知道要找尋的 `topic` 以及 `gifUrl`。這個範例特別點在於`init` 與 `update`是一個暫時為空想的types: ```elm init : String -> (Model, Effects Action) @@ -476,7 +476,7 @@ init : String -> (Model, Effects Action) update : Action -> Model -> (Model, Effects Action) ``` -Instead of returning just a new `Model` we also give back some effects that we would like to run. So we will be using [the `Effects` API][fx_api], which looks something like this: +相較於直接返回一個新的 `Model`,為了讓我們可以執行一些效果[the `Effects` API][fx_api],可參考如下範例 [fx_api]: http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects @@ -492,7 +492,7 @@ task : Task Never a -> Effects a -- request a task, do HTTP and database stuff ``` -The `Effects` type is essentially a data structure holding a bunch of independent tasks that will get run at some later point. Let’s get a better feeling of how this works by checking out how `update` works in this example: +其中的 `Effects` type為一個 擁有許多不同 tasks的資料結構,將會在未來去執行,我們用下面的範例來展示 `update` 是如何執行的: ```elm type Action @@ -516,17 +516,17 @@ update msg model = -- getRandomGif : String -> Effects Action ``` -So the user can trigger a `RequestMore` action by clicking the “More Please!” button, and when the server responds it will give us a `NewGif` action. We handle both these scenarios in our `update` function. +使用者可以觸發一個 `RequestMore` action 於點擊“More Please!” 按鈕之後, 當server 回覆時將返回一個 `NewGif` action,這兩個情景都寫在`update` function中。 -In the case of `RequestMore` first return the existing model. The user just clicked a button, there is nothing to change right now. We also create an `Effects Action` using the `getRandomGif` function. We will get to how `getRandomGif` is defined soon. For now we just need to know that when an `Effects Action` is run, it will produce a bunch of `Action` values that will be routed throughout the application. So `getRandomGif model.topic` will eventually result in an action like this: +其中 `RequestMore` 將會先返回一個已存在的 model, 並且我們還使用了 `getRandomGif` function建造了一個 `Effects Action`。 在後面會講到`getRandomGif`是如何定義的。現在我們只需要知道,當`Effects Action` 在執行時,他會產生大量的 `Action` values並且被導向到程式的各個部分。最後 `getRandomGif model.topic` 將產生向下面這樣的action: ```elm NewGif (Just "http://s3.amazonaws.com/giphygifs/media/ka1aeBvFCSLD2/giphy.gif") ``` -It returns a `Maybe` because the request to the server may fail. That `Action` will get fed right back into our `update` function. So when we take the `NewGif` route we just update the current `gifUrl` if possible. If the request failed, we just stick with the current `model.gifUrl`. +並且返回一個`Maybe` 因為這個送往server的request可能會失敗。 `Action`會被傳回 `update` function. 所以假設向server的請求失敗時,我們依然保持原有的 `model.gifUrl`. -We see the same kind of thing happening in `init` which defines the initial model and asks for a GIF in the correct topic from giphy.com’s API. +在 `init`將會發生相同的事。他定義了一個初始的 model並且使用特定的topic向giphy.com’s API請求 GIF 圖片。 ```elm init : String -> (Model, Effects Action) @@ -538,11 +538,12 @@ init topic = -- getRandomGif : String -> Effects Action ``` -Again, when the random GIF effect is complete, it will produce an `Action` that gets routed to our `update` function. +當 random GIF effect 完成時, 他將會產生一個 `Action` 並且導向 `update` function. -> **Note:** So far we have been using the `StartApp.Simple` module from [the start-app package](http://package.elm-lang.org/packages/evancz/start-app/latest), but now upgrade to the `StartApp` module. It is able to handle the complexity of more realistic web apps. It has [a slightly fancier API](http://package.elm-lang.org/packages/evancz/start-app/latest/StartApp). The crucial change is that it can handle our new `init` and `update` types. +> **Note:** 目前為止我們使用了 `StartApp.Simple` module 來源為[the start-app package](http://package.elm-lang.org/packages/evancz/start-app/latest)接著我們需要更新 `StartApp` module. 它可以用來處理更複雜的應用 +. It has [a slightly fancier API](http://package.elm-lang.org/packages/evancz/start-app/latest/StartApp)他將可以處理 new `init` 與 `update` types. -One of the crucial aspects of this example is the `getRandomGif` function that actually describes how to get a random GIF. It uses [tasks][] and [the `Http` package][http], and I will try to give an overview of how these things are being used as we go. Let’s look at the definition: +其中需要注意的一點是 `getRandomGif` function 用來定義如何取得 random GIF,他使用了 [tasks][] 與 [the `Http` package][http], 下面的範例將會教導如何使用他們: [tasks]: http://elm-lang.org/guide/reactivity#tasks [http]: http://package.elm-lang.org/packages/evancz/elm-http/latest @@ -555,17 +556,17 @@ getRandomGif topic = |> Task.map NewGif |> Effects.task --- The first line there created an HTTP GET request. It tries to --- get some JSON at `randomUrl topic` and decodes the result --- with `decodeImageUrl`. Both are defined below! +-- 第一行創造了一個 HTTP GET request. 他試著 +-- 去從 `randomUrl topic`取得JSON +-- 並且使用 `decodeImageUrl`解析它,下方可以看到他們的定義 -- --- Next we use `Task.toMaybe` to capture any potential failures and --- apply the `NewGif` tag to turn the result into a `Action`. --- Finally we turn it into an `Effects` value that can be used in our --- `init` or `update` functions. +-- 接著我們使用了 `Task.toMaybe`來抓取任何潛在的錯誤 +-- 並且讓 `NewGif` tag 的結果轉為 `Action`. +-- 最後我們將它轉為 `Effects` value 讓它可以用在接下來的 +-- `init` 或是 `update` functions中. --- Given a topic, construct a URL for the giphy API. +-- 給入一個 topic,與 URL 傳給 giphy API. randomUrl : String -> String randomUrl topic = Http.url "http://api.giphy.com/v1/gifs/random" @@ -574,27 +575,33 @@ randomUrl topic = ] --- A JSON decoder that takes a big chunk of JSON spit out by --- giphy and extracts the string at `json.data.image_url` +-- JSON decoder 將會接受大批的 JSON 並且 +-- 取出其中的 `json.data.image_url` decodeImageUrl : Json.Decoder String decodeImageUrl = Json.at ["data", "image_url"] Json.string ``` -Once we have written this up, we are able to reuse `getRandomGif` in our `init` and `update` functions. +當我們寫好這些後,未來,我們將可以使用 `getRandomGif` 於 `init`與 `update` functions中 -One of the interesting things about the task returned by `getRandomGif` is that it can `Never` fail. The idea is that any potential failure *must* be handled explicitly. We do not want any tasks failing silently. +其中有趣的一點是, `getRandomGif`所返回的task將永遠不會失敗,因為我們希望所有可能發生的失敗都要被明確的處理,下面將會解釋這點。 + +每個`Task` 有兩個型態,分別為 failure type 與 success type。 舉例來說:一個 HTTP task 可能有一個type 類似 `Task Http.Error String` +以此範例來說,它可能會失敗於`Http.Error` 或是成功返回 `String`, 這讓我們可以處理一系列的task並且不用處理錯誤發生。 + +現在,假設我們有一個 component 要求了一個 task, 但是這個 task 失敗了。接下來會發生什麼事? 誰會被通知? 該如何復原它? +我們創造了一個發生錯誤時的錯誤型態 `Never` 我們讓任何可能發生的錯誤,都轉到了 success type ,讓他們可以明確地被處理。 +在範例中,我們使用了 `Task.toMaybe : Task x a -> Task y (Maybe a)` 所以我們的 `update` function 可以精準的處理 HTTP failures的情況。 -I am going to try to explain exactly how that works, but it is not crucial to get every piece of this to use things! Okay, so every `Task` has a failure type and a success type. For example, an HTTP task may have a type like this `Task Http.Error String` such that we can fail with an `Http.Error` or succeed with a `String`. This makes it nice to chain a bunch of tasks together without worrying too much about errors. Now lets say our component requests a task, but the task fails. What happens then? Who gets notified? How do we recover? By making the failure type `Never` we force any potential errors into the success type such that they can be handled explicitly by the component. In our case, we use `Task.toMaybe : Task x a -> Task y (Maybe a)` so our `update` function must explicitly handle HTTP failures. This means tasks cannot silently fail, you always handle potential errors explicitly. ## Example 6: Pair of random GIF viewers **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/6.html) / [see code](examples/6/)** -Alright, effects can be done, but what about *nested* effects? Did you think about that?! This example reuses the exact code from the GIF viewer in example 5 to create a pair of independent GIF viewers. +一般的effects 可以被處理,但如果是*巢狀的* effects呢?這個範例使用了和範例 5同樣的程式,用來建立一對獨立的GIF viewers. -As you look through [the implementation](examples/6/RandomGifPair.elm), notice that it is pretty much the same code as the pair of counters in example 2. The `Model` is defined as two `RandomGif.Model` values: +在你看完 [the implementation](examples/6/RandomGifPair.elm)後,你會發現他和範例二有點類似, 其中的 `Model`定義了兩個 `RandomGif.Model` 的值: ```elm type alias Model = @@ -603,7 +610,7 @@ type alias Model = } ``` -This lets us keep track of each independently. Our actions are just routing messages to the appropriate subcomponent. +這讓我們可以分別的處理它們。 其中的actions指是負責用來傳遞訊息到子組件用的。 ```elm type Action @@ -611,7 +618,7 @@ type Action | Right RandomGif.Action ``` -The interesting thing is that we actually use the `Left` and `Right` tags a bit in our `update` and `init` functions. +我們使用 `Left` 與 `Right` 在我們的 `update` 與 `init` functions中 ```elm -- Effects.map : (a -> b) -> Effects a -> Effects b From 3c305a5c7d0e2b5e136bdd3ce725803800c8ff05 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:04:19 +0800 Subject: [PATCH 07/19] Update README.md --- README.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6aac1d5..37f0800 100644 --- a/README.md +++ b/README.md @@ -643,9 +643,11 @@ update action model = ) ``` -So in each branch we call the `RandomGif.update` function which is returning a new model and some effects we are calling `fx`. We return an updated model like normal, but we need to do some extra work on our effects. Instead of returning them directly, we use [`Effects.map`](http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects#map) function to turn them into the same kind of `Action`. This works very much like `Signal.forwardTo`, letting us tag the values to make it clear how they should be routed. +在每個分支中,我們使用了 `RandomGif.update` function 其可以回傳一個 model 以及(effect) `fx`。和先前一樣先回傳一個普通的model, +但我們需要在effects中做一些不同的事。我們這次不讓他直接return,我們將使用 [`Effects.map`](http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects#map) function +將他們轉為同類型的`Action`,和 `Signal.forwardTo`類似,讓我們對values進行 tag的動作,讓我們清楚的知道它該被導向哪裡。 -The same thing happens in the `init` function. We provide a topic for each random GIF viewer and get back an initial model and some effects. +而`init` function也做一樣的事, 我們提供一個topic 給每個 random GIF viewer 並且得到回傳的初始 model 與 effects。 ```elm init : String -> String -> (Model, Effects Action) @@ -664,31 +666,35 @@ init leftTopic rightTopic = -- Effects.batch : List (Effects a) -> Effects a ``` -In this case we not only use `Effects.map` to tag results appropriately, we also use the [`Effects.batch`](http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects#batch) function to lump them all together. All of the requested tasks will get spawned off and run independently, so the left and right effects will be in progress at the same time. +在這個範例,我們不止使用 `Effects.map` 來 tag 結果,我們還會使用 [`Effects.batch`](http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects#batch) function 來讓他們混在一起。 + 每個 requested tasks 將會獨立的運行,所以 left 與 right effects將會同一時間一起運作。 ## Example 7: List of random GIF viewers **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/7.html) / [see code](examples/7/)** -This example lets you have a list of random GIF viewers where you can create the topics yourself. Again, we reuse the core `RandomGif` module exactly as is. +這個範例讓你有一系列的 random GIF viewers 你可以創造你自己的topics,並且,我們仍然使用之前的 `RandomGif` module。 -When you look through [the implementation](examples/7/RandomGifList.elm) you will see that it exactly corresponds to example 3. We put all of our submodels in a list associated with an ID and do our operations based on those IDs. The only thing new is that we are using `Effects` in the `init` and `update` function, putting them together with `Effects.map` and `Effects.batch`. +當你看完 [the implementation](examples/7/RandomGifList.elm) 你會發現他和 範例 3是相對應的。我們將所有子model放在相同list , +並用ID來分辨與連結它們, 唯一改變的地方在於:在 `init` 與 `update` function中的 `Effects` , 將使用 `Effects.map` 與 `Effects.batch`把他們放在一起。 -Please open an issue if this section should go into more detail about how things work! + +如果你對下面這個章節有任何的問題,你可以在這個repo創造一個issue來解決你的問題。 ## Example 8: Animation **[demo](http://evancz.github.io/elm-architecture-tutorial/examples/8.html) / [see code](examples/8/)** -Now we have seen components with tasks that can be nested in arbitrary ways, but how does it work for animation? +現在我們知道擁有tasks的 components 可以被巢狀的使用, 但要怎麼把它應用在動畫中呢? -Interestingly, it is pretty much exactly the same! (Or perhaps it is no longer surprising that the same pattern as in all the other examples works here too... Seems like a pretty good pattern!) +有趣的是, 它的做法和先前的做法類似! -This example is a pair of clickable squares. When you click a square, it rotates 90 degrees. Overall the code is an adapted form of example 2 and example 6 where we keep all the logic for animation in `SpinSquare.elm` which we then reuse multiple times in `SpinSquarePair.elm`. +這個範例是兩個可以點擊的四方體, 當你點擊後, 它會旋轉90度。總體來說這裡的code 源自於 範例 2 和 範例 6 +我們將所有跟動畫相關的邏輯放在`SpinSquare.elm` 我們重複使用的部分放在 `SpinSquarePair.elm`. -So all the new and interesting stuff is happening [in `SpinSquare`](examples/8/SpinSquare.elm), so we are going to focus on that code. The first thing we need is a model: +根據這個範例 [in `SpinSquare`](examples/8/SpinSquare.elm) 第一件要做的事為處理 model: ```elm type alias Model = @@ -705,7 +711,8 @@ rotateStep = 90 duration = second ``` -So our core model is the `angle` that the square is currently at and then some `animationState` to track what is going on with any ongoing animation. If there is no animation it is `Nothing`, but if something is happening it holds: +我們核心的model為 `angle` 指出了四方體的當前角度,而`animationState` 用來指出目前的動畫,假如現在沒有動畫,它將是 `Nothing` +但假如現在有動畫,它將會是: * `prevClockTime` — The most recent clock time which we will use for calculating time diffs. It will help us know exactly how many milliseconds have passed since last frame. * `elapsedTime` — A number between 0 and `duration` that tells us how far we are in the animation. From da01a3fd69c994e3d6c00659c38f18af39bd5161 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:29:53 +0800 Subject: [PATCH 08/19] Update README.md --- README.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 37f0800..71e19f7 100644 --- a/README.md +++ b/README.md @@ -714,12 +714,12 @@ duration = second 我們核心的model為 `angle` 指出了四方體的當前角度,而`animationState` 用來指出目前的動畫,假如現在沒有動畫,它將是 `Nothing` 但假如現在有動畫,它將會是: - * `prevClockTime` — The most recent clock time which we will use for calculating time diffs. It will help us know exactly how many milliseconds have passed since last frame. - * `elapsedTime` — A number between 0 and `duration` that tells us how far we are in the animation. + * `prevClockTime` — 可以讓我們知道與上次的影格相距多少毫秒(milliseconds) 。 + * `elapsedTime` —指出 數字0 與 `已經過時間`兩者之差距,用來了解動畫進行了多久。 -The `rotateStep` constant is just declaring how far it turns on each click. You can mess with that and everything should keep working. +而`rotateStep` 用來宣告每次點擊他轉了多少角度,你可以隨意去改變它,不會對你程式造成影響。 -Now the interesting stuff all happens in `update`: +有趣的事情發生在 `update`中: ```elm type Action @@ -762,16 +762,22 @@ update msg model = ) ``` -There are two kinds of `Action` we need to handle: +我們需要處理兩種 `Action` : - - `Spin` indicates that a user clicked the shape, requesting a spin. So in the `update` function, we request a clock tick if there is no animation going and just let things stay as is if one is already going. - - `Tick` indicates that we have gotten a clock tick so we need to take an animation step. In the `update` function this means we need to update our `animationState`. So first we check if there is an animation in progress. If so, we just figure out what the `newElapsedTime` is by taking the current `elapsedTime` and adding a time diff to it. If the now elapsed time is greater than `duration` we stop animating and stop requesting new clock ticks. Otherwise we update the animation state and request another clock tick. + - `Spin` 當一個 user 點擊shape並且 requesting a spin。 接著在 `update` function中,假設現在沒有動畫在進行,我們會request 一個 clock tick,並且保持原來的狀態,但假裝已經有事情在作用了。 + - `Tick` 當我們取得了 clock tick 我們將需要取得 animation step.在 `update` function中 ,我們需要去更新 `animationState`. + -第一件事,我們會去檢查當下是否有動畫在進行, 假如有,我們會使用當下的 `elapsedTime` 去確認 `newElapsedTime`的值。 +並且在其中加入 `time diff`。 -Again, I think we can cut this code down as we write more code like this and start seeing the general pattern. Should be exciting to find! +假設現在的elapsed time 大於 `duration` 我們將會停止動畫,並且暫停 requesting new clock ticks. +假設小於, 我們會更新現在的 animation state 並且 request another clock tick。 -Finally we have a somewhat interesting `view` function! This example gets a nice bouncy animation, but we are just incrementing our `elapsedTime` in linear chunks. How is that happening? -The `view` code itself is totally standard [`elm-svg`](http://package.elm-lang.org/packages/evancz/elm-svg/latest/) to make some fancier clickable shapes. The cool part of the view code is `toOffset` which calculates the rotation offset for the current `AnimationState`. + +最後,我們會有`view` function! 在這個範例有一個不錯的動畫 ,但其實我們只是以線性的方式去遞增`elapsedTime`。這是如何發生的? + +其中 `view` 的程式碼是源自 [`elm-svg`](http://package.elm-lang.org/packages/evancz/elm-svg/latest/) 為了做出其他更有趣並且可點擊產生動畫的形狀. +在view程式碼中 `toOffset` 將會計算目前所旋轉的偏移量 `AnimationState`. ```elm -- import Easing exposing (ease, easeOutBounce, float) @@ -786,9 +792,9 @@ toOffset animationState = ease easeOutBounce float 0 rotateStep duration elapsedTime ``` -We are using [@Dandandan](https://github.com/Dandandan)’s [easing package](http://package.elm-lang.org/packages/Dandandan/Easing/latest) which makes it easy to do [all sorts of cool easings](http://easings.net/) on numbers, colors, points, and any other crazy thing you want. +我們使用的是[@Dandandan](https://github.com/Dandandan)’s [easing package](http://package.elm-lang.org/packages/Dandandan/Easing/latest) 讓我們可以簡單的做到一些動畫 [all sorts of cool easings](http://easings.net/)包含數字,顏色,點,或是任何其他你想要的東西。. -So the `ease` function is taking a number between 0 and `duration`. It then turns that into a number between 0 and `rotateStep` which we set to 90 degrees up at the top of our program. You also provide an easing. In our case we gave `easeOutBounce` which means as we slide from 0 to `duration`, we will get a number between 0 and 90 with that easing added. Pretty crazy! Try swapping `easeOutBounce` out for [other easings](http://package.elm-lang.org/packages/Dandandan/Easing/latest/Easing) and see how it looks! +`ease` function 會取得一個數字在 0 與 `duration`間。 It then turns that into a number between 0 and `rotateStep` which we set to 90 degrees up at the top of our program. You also provide an easing. In our case we gave `easeOutBounce` which means as we slide from 0 to `duration`, we will get a number between 0 and 90 with that easing added. Pretty crazy! Try swapping `easeOutBounce` out for [other easings](http://package.elm-lang.org/packages/Dandandan/Easing/latest/Easing) and see how it looks! From here, we wire everything together in `SpinSquarePair`, but that code is pretty much exactly the same as in example 2 and example 6. From 48ca276fef626d789f45c8af5fb9281cffa28d3f Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:38:23 +0800 Subject: [PATCH 09/19] Update README.md --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 71e19f7..7d2cac4 100644 --- a/README.md +++ b/README.md @@ -794,10 +794,14 @@ toOffset animationState = 我們使用的是[@Dandandan](https://github.com/Dandandan)’s [easing package](http://package.elm-lang.org/packages/Dandandan/Easing/latest) 讓我們可以簡單的做到一些動畫 [all sorts of cool easings](http://easings.net/)包含數字,顏色,點,或是任何其他你想要的東西。. -`ease` function 會取得一個數字在 0 與 `duration`間。 It then turns that into a number between 0 and `rotateStep` which we set to 90 degrees up at the top of our program. You also provide an easing. In our case we gave `easeOutBounce` which means as we slide from 0 to `duration`, we will get a number between 0 and 90 with that easing added. Pretty crazy! Try swapping `easeOutBounce` out for [other easings](http://package.elm-lang.org/packages/Dandandan/Easing/latest/Easing) and see how it looks! +`ease` function 會取得一個數字在 0 與 `duration`間。 接著把它轉換為 0 與 `rotateStep` 間的數字 +並且提供一個 easing, 在範例中,我們提供了 `easeOutBounce` 意思為我們slide from 0 to `duration`,我們將會獲得一個數字在 0 and 90 之間,並且具有easing 效果。 -From here, we wire everything together in `SpinSquarePair`, but that code is pretty much exactly the same as in example 2 and example 6. +接著在下面的連結試試 `easeOutBounce` 吧 [other easings](http://package.elm-lang.org/packages/Dandandan/Easing/latest/Easing) + + +我們使用 `SpinSquarePair`將大部分的東西結合再一起,但他們其實和範例 2 與範例 6 類似。 + +以上即是我們使用這個 library所進行的animation ! 如果有任何不清楚的地方請提出來讓我們知道! -Okay, so that is the basics of doing animation with this library! It is not clear if we nailed everything here, so let us know how things go as you get more experience. Hopefully we can make it even easier! -> **Note:** I expect we can build some abstractions on top of the core ideas here. This example does some lower level stuff, but I bet we can find some nice patterns to make this easier as we work with it more. If you find it weird now, try to make something better and tell us about it! From 893abe0d64a38fb62dd34a71ea1c73103f47ea37 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:41:55 +0800 Subject: [PATCH 10/19] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d2cac4..7f2d70c 100644 --- a/README.md +++ b/README.md @@ -802,6 +802,6 @@ toOffset animationState = 我們使用 `SpinSquarePair`將大部分的東西結合再一起,但他們其實和範例 2 與範例 6 類似。 -以上即是我們使用這個 library所進行的animation ! 如果有任何不清楚的地方請提出來讓我們知道! - +以上即是我們使用這個 library所進行的animation ! 如果有任何不清楚的地方請歡迎提出來! +>Note: 我期待我們可以於這個核心概念上做一些更酷的東西 。 如果你有任何更好的想法,請讓我們知道! From d5834c5d4f574ab81f4cae499f356b83a9575e08 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:45:37 +0800 Subject: [PATCH 11/19] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f2d70c..fa890e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# The Elm Architecture +#The Elm Architecture + +你可以查看抱含安裝等步驟的完整版於:[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md#the-elm-architecture) + 這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, 包含 [TodoMVC][] 、 [dreamwriter][]以及正式上線的 [NoRedInk][] 、 [CircuitHub][]. 了解這個設計模式將對你在 Elm 中的程式或任何其他編程都很有幫助。 From 994cd909584148951f6e72ec01fbf2407099d111 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:46:49 +0800 Subject: [PATCH 12/19] Update and rename README.md to README-zh.md --- README.md => README-zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename README.md => README-zh.md (99%) diff --git a/README.md b/README-zh.md similarity index 99% rename from README.md rename to README-zh.md index fa890e8..6ad29a0 100644 --- a/README.md +++ b/README-zh.md @@ -1,6 +1,6 @@ #The Elm Architecture -你可以查看抱含安裝等步驟的完整版於:[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md#the-elm-architecture) +##開始之前:建議你可以查看包含安裝等步驟的完整版於:[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md#the-elm-architecture) 這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, 包含 [TodoMVC][] 、 [dreamwriter][]以及正式上線的 [NoRedInk][] 、 [CircuitHub][]. 了解這個設計模式將對你在 Elm 中的程式或任何其他編程都很有幫助。 From d759184319f43e17ee2e1e63f45bcb80812343eb Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:47:06 +0800 Subject: [PATCH 13/19] Update README-zh.md --- README-zh.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README-zh.md b/README-zh.md index 6ad29a0..29dc0cc 100644 --- a/README-zh.md +++ b/README-zh.md @@ -1,6 +1,8 @@ #The Elm Architecture -##開始之前:建議你可以查看包含安裝等步驟的完整版於:[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md#the-elm-architecture) +##開始之前:建議你可以查看包含安裝等步驟的完整版於: + +[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md#the-elm-architecture) 這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, 包含 [TodoMVC][] 、 [dreamwriter][]以及正式上線的 [NoRedInk][] 、 [CircuitHub][]. 了解這個設計模式將對你在 Elm 中的程式或任何其他編程都很有幫助。 From 62510302ef2a520462f0046853be35b003a019c0 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:47:56 +0800 Subject: [PATCH 14/19] Update README-zh.md --- README-zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-zh.md b/README-zh.md index 29dc0cc..8c79f32 100644 --- a/README-zh.md +++ b/README-zh.md @@ -2,7 +2,7 @@ ##開始之前:建議你可以查看包含安裝等步驟的完整版於: -[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md#the-elm-architecture) +[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md) 這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, 包含 [TodoMVC][] 、 [dreamwriter][]以及正式上線的 [NoRedInk][] 、 [CircuitHub][]. 了解這個設計模式將對你在 Elm 中的程式或任何其他編程都很有幫助。 From 020922613e75ac82c299e5b98208c202a80e46aa Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:48:55 +0800 Subject: [PATCH 15/19] Create README --- README | 781 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 781 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..bf6c36a --- /dev/null +++ b/README @@ -0,0 +1,781 @@ +# The Elm Architecture + +This tutorial outlines “The Elm Architecture” which you will see in all [Elm][] programs, from [TodoMVC][] and [dreamwriter][] to the code running in production at [NoRedInk][] and [CircuitHub][]. The basic pattern is useful whether you are writing your front-end in Elm or JS or whatever else. + +[Elm]: http://elm-lang.org/ +[TodoMVC]: https://github.com/evancz/elm-todomvc +[dreamwriter]: https://github.com/rtfeldman/dreamwriter#dreamwriter +[NoRedInk]: https://www.noredink.com/ +[CircuitHub]: https://www.circuithub.com/ + +The Elm Architecture is a simple pattern for infinitely nestable components. It is great for modularity, code reuse, and testing. Ultimately, this pattern makes it easy to create complex web apps in a way that stays modular. We will run through 8 examples, slowly building on core principles and patterns: + + 1. [Counter](http://evancz.github.io/elm-architecture-tutorial/examples/1.html) + 2. [Pair of counters](http://evancz.github.io/elm-architecture-tutorial/examples/2.html) + 3. [List of counters](http://evancz.github.io/elm-architecture-tutorial/examples/3.html) + 4. [List of counters (variation)](http://evancz.github.io/elm-architecture-tutorial/examples/4.html) + 5. [GIF fetcher](http://evancz.github.io/elm-architecture-tutorial/examples/5.html) + 6. [Pair of GIF fetchers](http://evancz.github.io/elm-architecture-tutorial/examples/6.html) + 7. [List of GIF fetchers](http://evancz.github.io/elm-architecture-tutorial/examples/7.html) + 8. [Pair of animating squares](http://evancz.github.io/elm-architecture-tutorial/examples/8.html) + +This tutorial will really help! It will bring out the concepts and ideas necessary to get to make examples 7 and 8 super easy. Investing in the foundation will be worth it! + +One very interesting aspect of the architecture in all these programs is that it *emerges* from Elm naturally. The language design itself leads you towards this architecture whether you have read this document and know the benefits or not. I actually discovered this pattern just using Elm and have been shocked by its simplicity and power. + +**Note**: To follow along with this tutorial with code, [install Elm](http://elm-lang.org/install) and fork this repo. Each example in the tutorial gives instructions of how to run the code. + + +## The Basic Pattern + +The logic of every Elm program will break up into three cleanly separated parts: + + * model + * update + * view + +You can pretty reliably start with the following skeleton and then iteratively fill in details for your particular case. + +> If you are new to reading Elm code, check out the [language docs](http://elm-lang.org/docs) which covers everything from syntax to getting into a “functional mindset”. The first two sections of [the complete guide](http://elm-lang.org/docs#complete-guide) will get you up to speed! + +```elm +-- MODEL + +type alias Model = { ... } + + +-- UPDATE + +type Action = Reset | ... + +update : Action -> Model -> Model +update action model = + case action of + Reset -> ... + ... + + +-- VIEW + +view : Model -> Html +view = + ... +``` + +This tutorial is all about this pattern and small variations and extensions. + + +## Example 1: A Counter + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/1.html) / [see code](examples/1/)** + +Our first example is a simple counter that can be incremented or decremented. + +[The code](examples/1/Counter.elm) starts with a very simple model. We just need to keep track of a single number: + +```elm +type alias Model = Int +``` + +When it comes to updating our model, things are relatively simple again. We define a set of actions that can be performed, and an `update` function to actually perform those actions: + +```elm +type Action = Increment | Decrement + +update : Action -> Model -> Model +update action model = + case action of + Increment -> model + 1 + Decrement -> model - 1 +``` + +Notice that our `Action` [union type][] does not *do* anything. It simply describes the actions that are possible. If someone decides our counter should be doubled when a certain button is pressed, that will be a new case in `Action`. This means our code ends up very clear about how our model can be transformed. Anyone reading this code will immediately know what is allowed and what is not. Furthermore, they will know exactly how to add new features in a consistent way. + +[union type]: http://elm-lang.org/learn/Union-Types.elm + +Finally, we create a way to `view` our `Model`. We are using [elm-html][] to create some HTML to show in a browser. We will create a div that contains: a decrement button, a div showing the current count, and an increment button. + +[elm-html]: http://elm-lang.org/blog/Blazing-Fast-Html.elm + +```elm +view : Signal.Address Action -> Model -> Html +view address model = + div [] + [ button [ onClick address Decrement ] [ text "-" ] + , div [ countStyle ] [ text (toString model) ] + , button [ onClick address Increment ] [ text "+" ] + ] + +countStyle : Attribute +countStyle = + ... +``` + +The tricky thing about our `view` function is the `Address`. We will dive into that in the next section! For now, I just want you to notice that **this code is entirely declarative**. We take in a `Model` and produce some `Html`. That is it. At no point do we mutate the DOM manually, which gives the library [much more freedom to make clever optimizations][elm-html] and actually makes rendering *faster* overall. It is crazy. Furthermore, `view` is a plain old function so we can get the full power of Elm’s module system, test frameworks, and libraries when creating views. + +This pattern is the essence of architecting Elm programs. Every example we see from now on will be a slight variation on this basic pattern: `Model`, `update`, `view`. + + +## Starting the Program + +Pretty much all Elm programs will have a small bit of code that drives the whole application. For each example in this tutorial, that code is broken out into `Main.elm`. For our counter example, the interesting code looks like this: + +```elm +import Counter exposing (update, view) +import StartApp.Simple exposing (start) + +main = + start { model = 0, update = update, view = view } +``` + +We are using the [`StartApp`](https://github.com/evancz/start-app) package to wire together our initial model with the update and view functions. It is a small wrapper around Elm's [signals](http://elm-lang.org/learn/Using-Signals.elm) so that you do not need to dive into that concept yet. + +The key to wiring up your application is the concept of an `Address`. Every event handler in our `view` function reports to a particular address. It just sends chunks of data along. The `StartApp` package monitors all the messages coming in to this address and feeds them into the `update` function. The model gets updated and [elm-html][] takes care of rendering the changes efficiently. + +This means values flow through an Elm program in only one direction, something like this: + +![Signal Graph Summary](diagrams/signal-graph-summary.png) + +The blue part is our core Elm program which is exactly the model/update/view pattern we have been discussing so far. When programming in Elm, you can mostly think inside this box and make great progress. + +Notice we are not *performing* actions as they get sent back to our app. We are simply sending some data over. This separation is a key detail, keeping our logic totally separate from our view code. + + +## Example 2: A Pair of Counters + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/2.html) / [see code](examples/2/)** + +In example 1 we created a basic counter, but how does that pattern scale when we want *two* counters? Can we keep things modular? + +Wouldn't it be great if we could reuse all the code from example 1? The crazy thing about the Elm Architecture is that **we can reuse code with absolutely no changes**. When we created the `Counter` module in example one, it encapsulated all the implementation details so we can use them elsewhere: + +```elm +module Counter (Model, init, Action, update, view) where + +type Model + +init : Int -> Model + +type Action + +update : Action -> Model -> Model + +view : Signal.Address Action -> Model -> Html +``` + +Creating modular code is all about creating strong abstractions. We want boundaries which appropriately expose functionality and hide implementation. From outside of the `Counter` module, we just see a basic set of values: `Model`, `init`, `Action`, `update`, and `view`. We do not care at all how these things are implemented. In fact, it is *impossible* to know how these things are implemented. This means no one can rely on implementation details that were not made public. + +So we can reuse our `Counter` module, but now we need to use it to create our `CounterPair`. As always, we start with a `Model`: + +```elm +type alias Model = + { topCounter : Counter.Model + , bottomCounter : Counter.Model + } + +init : Int -> Int -> Model +init top bottom = + { topCounter = Counter.init top + , bottomCounter = Counter.init bottom + } +``` + +Our `Model` is a record with two fields, one for each of the counters we would like to show on screen. This fully describes all of the application state. We also have an `init` function to create a new `Model` whenever we want. + +Next we describe the set of `Actions` we would like to support. This time our features should be: reset all counters, update the top counter, or update the bottom counter. + +```elm +type Action + = Reset + | Top Counter.Action + | Bottom Counter.Action +``` + +Notice that our [union type][] refers to the `Counter.Action` type, but we do not know the particulars of those actions. When we create our `update` function, we are mainly routing these `Counter.Actions` to the right place: + +```elm +update : Action -> Model -> Model +update action model = + case action of + Reset -> init 0 0 + + Top act -> + { model | + topCounter = Counter.update act model.topCounter + } + + Bottom act -> + { model | + bottomCounter = Counter.update act model.bottomCounter + } +``` + +So now the final thing to do is create a `view` function that shows both of our counters on screen along with a reset button. + +```elm +view : Signal.Address Action -> Model -> Html +view address model = + div [] + [ Counter.view (Signal.forwardTo address Top) model.topCounter + , Counter.view (Signal.forwardTo address Bottom) model.bottomCounter + , button [ onClick address Reset ] [ text "RESET" ] + ] +``` + +Notice that we are able to reuse the `Counter.view` function for both of our counters. For each counter we create a forwarding address. Essentially what we are doing here is saying, “these counters will tag all outgoing messages with `Top` or `Bottom` so we can tell the difference.” + +That is the whole thing. The cool thing is that we can keep nesting more and more. We can take the `CounterPair` module, expose the key values and functions, and create a `CounterPairPair` or whatever it is we need. + + +## Example 3: A Dynamic List of Counters + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/3.html) / [see code](examples/3/)** + +A pair of counters is cool, but what about a list of counters where we can add and remove counters as we see fit? Can this pattern work for that too? + +Again we can reuse the `Counter` module exactly as it was in example 1 and 2! + +```elm +module Counter (Model, init, Action, update, view) +``` + +That means we can just get started on our `CounterList` module. As always, we begin with our `Model`: + +```elm +type alias Model = + { counters : List ( ID, Counter.Model ) + , nextID : ID + } + +type alias ID = Int +``` + +Now our model has a list of counters, each annotated with a unique ID. These IDs allow us to distinguish between them, so if we need to update counter number 4 we have a nice way to refer to it. (This ID also gives us something convenient to [`key`][key] on when we are thinking about optimizing rendering, but that is not the focus of this tutorial!) Our model also contains a +`nextID` which helps us assign unique IDs to each counter as we add new ones. + +[key]: http://package.elm-lang.org/packages/evancz/elm-html/latest/Html-Attributes#key + +Now we can define the set of `Actions` that can be performed on our model. We want to be able to add counters, remove counters, and update certain counters. + +```elm +type Action + = Insert + | Remove + | Modify ID Counter.Action +``` + +Our `Action` [union type][] is shockingly close to the high-level description. Now we can define our `update` function. + +```elm +update : Action -> Model -> Model +update action model = + case action of + Insert -> + let newCounter = ( model.nextID, Counter.init 0 ) + newCounters = model.counters ++ [ newCounter ] + in + { model | + counters = newCounters, + nextID = model.nextID + 1 + } + + Remove -> + { model | counters = List.drop 1 model.counters } + + Modify id counterAction -> + let updateCounter (counterID, counterModel) = + if counterID == id + then (counterID, Counter.update counterAction counterModel) + else (counterID, counterModel) + in + { model | counters = List.map updateCounter model.counters } +``` + +Here is a high-level description of each case: + + * `Insert` — First we create a new counter and put it at the end of + our counter list. Then we increment our `nextID` so that we have a fresh + ID next time around. + + * `Remove` — Drop the first member of our counter list. + + * `Modify` — Run through all of our counters. If we find one with + a matching ID, we perform the given `Action` on that counter. + +All that is left to do now is to define the `view`. + +```elm +view : Signal.Address Action -> Model -> Html +view address model = + let counters = List.map (viewCounter address) model.counters + remove = button [ onClick address Remove ] [ text "Remove" ] + insert = button [ onClick address Insert ] [ text "Add" ] + in + div [] ([remove, insert] ++ counters) + +viewCounter : Signal.Address Action -> (ID, Counter.Model) -> Html +viewCounter address (id, model) = + Counter.view (Signal.forwardTo address (Modify id)) model +``` + +The fun part here is the `viewCounter` function. It uses the same old +`Counter.view` function, but in this case we provide a forwarding address that annotates all messages with the ID of the particular counter that is getting rendered. + +When we create the actual `view` function, we map `viewCounter` over all of our counters and create add and remove buttons that report to the `address` directly. + +This ID trick can be used any time you want a dynamic number of subcomponents. Counters are very simple, but the pattern would work exactly the same if you had a list of user profiles or tweets or newsfeed items or product details. + + +## Example 4: A Fancier List of Counters + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/4.html) / [see code](examples/4/)** + +Okay, keeping things simple and modular on a dynamic list of counters is pretty cool, but instead of a general remove button, what if each counter had its own specific remove button? Surely *that* will mess things up! + +Nah, it works. + +In this case our goals mean that we need a new way to view a `Counter` that adds a remove button. Interestingly, we can keep the `view` function from before and add a new `viewWithRemoveButton` function that provides a slightly different view of our underlying `Model`. This is pretty cool. We do not need to duplicate any code or do any crazy subtyping or overloading. We just add a new function to the public API to expose new functionality! + +```elm +module Counter (Model, init, Action, update, view, viewWithRemoveButton, Context) where + +... + +type alias Context = + { actions : Signal.Address Action + , remove : Signal.Address () + } + +viewWithRemoveButton : Context -> Model -> Html +viewWithRemoveButton context model = + div [] + [ button [ onClick context.actions Decrement ] [ text "-" ] + , div [ countStyle ] [ text (toString model) ] + , button [ onClick context.actions Increment ] [ text "+" ] + , div [ countStyle ] [] + , button [ onClick context.remove () ] [ text "X" ] + ] +``` + +The `viewWithRemoveButton` function adds one extra button. Notice that the increment/decrement buttons send messages to the `actions` address but the delete button sends messages to the `remove` address. The messages we send along to `remove` are essentially saying, “hey, whoever owns me, remove me!” It is up to whoever owns this particular counter to do the removing. + +Now that we have our new `viewWithRemoveButton`, we can create a `CounterList` module which puts all the individual counters together. The `Model` is the same as in example 3: a list of counters and a unique ID. + +```elm +type alias Model = + { counters : List ( ID, Counter.Model ) + , nextID : ID + } + +type alias ID = Int +``` + +Our set of actions is a bit different. Instead of removing any old counter, we want to remove a specific one, so the `Remove` case now holds an ID. + +```elm +type Action + = Insert + | Remove ID + | Modify ID Counter.Action +``` + +The `update` function is pretty similar to example 3 as well. + +```elm +update : Action -> Model -> Model +update action model = + case action of + Insert -> + { model | + counters = ( model.nextID, Counter.init 0 ) :: model.counters, + nextID = model.nextID + 1 + } + + Remove id -> + { model | + counters = List.filter (\(counterID, _) -> counterID /= id) model.counters + } + + Modify id counterAction -> + let updateCounter (counterID, counterModel) = + if counterID == id + then (counterID, Counter.update counterAction counterModel) + else (counterID, counterModel) + in + { model | counters = List.map updateCounter model.counters } +``` + +In the case of `Remove`, we take out the counter that has the ID we are supposed to remove. Otherwise, the cases are quite close to how they were before. + +Finally, we put it all together in the `view`: + +```elm +view : Signal.Address Action -> Model -> Html +view address model = + let insert = button [ onClick address Insert ] [ text "Add" ] + in + div [] (insert :: List.map (viewCounter address) model.counters) + +viewCounter : Signal.Address Action -> (ID, Counter.Model) -> Html +viewCounter address (id, model) = + let context = + Counter.Context + (Signal.forwardTo address (Modify id)) + (Signal.forwardTo address (always (Remove id))) + in + Counter.viewWithRemoveButton context model +``` + +In our `viewCounter` function, we construct the `Counter.Context` to pass in all the necessary forwarding addresses. In both cases we annotate each `Counter.Action` so that we know which counter to modify or remove. + + +## Big Lessons So Far + +**Basic Pattern** — Everything is built around a `Model`, a way to `update` that model, and a way to `view` that model. Everything is a variation on this basic pattern. + +**Nesting Modules** — Forwarding addresses makes it easy to nest our basic pattern, hiding implementation details entirely. We can nest this pattern arbitrarily deep, and each level only needs to know about what is going on one level lower. + +**Adding Context** — Sometimes to `update` or `view` our model, extra information is needed. We can always add some `Context` to these functions and pass in all the additional information we need without complicating our `Model`. + +```elm +update : Context -> Action -> Model -> Model +view : Context' -> Model -> Html +``` + +At every level of nesting we can derive the specific `Context` needed for each submodule. + +**Testing is Easy** — All of the functions we have created are [pure functions][pure]. That makes it extremely easy to test your `update` function. There is no special initialization or mocking or configuration step, you just call the function with the arguments you would like to test. + +[pure]: http://en.wikipedia.org/wiki/Pure_function + + +## Example 5: Random GIF Viewer + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/5.html) / [see code](examples/5/)** + +So we have covered how to create infinitely nestable components, but what happens when we want to do an HTTP request from somewhere in there? Or talk to a database? This example starts using [the `elm-effects` package][fx] to create a simple component that fetches random gifs from giphy.com with the topic “funny cats”. + +[fx]: http://package.elm-lang.org/packages/evancz/elm-effects/latest + +As you look through [the implementation](examples/5/RandomGif.elm), notice that it is pretty much the same code as the counter in example 1. The `Model` is very typical: + +```elm +type alias Model = + { topic : String + , gifUrl : String + } +``` + +We need to know what the `topic` of the finder is and what `gifUrl` we are showing right this second. The only new thing in this example is that `init` and `update` have slightly fancier types: + +```elm +init : String -> (Model, Effects Action) + +update : Action -> Model -> (Model, Effects Action) +``` + +Instead of returning just a new `Model` we also give back some effects that we would like to run. So we will be using [the `Effects` API][fx_api], which looks something like this: + +[fx_api]: http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects + +```elm +module Effects where + +type Effects a + +none : Effects a + -- don't do anything + +task : Task Never a -> Effects a + -- request a task, do HTTP and database stuff +``` + +The `Effects` type is essentially a data structure holding a bunch of independent tasks that will get run at some later point. Let’s get a better feeling of how this works by checking out how `update` works in this example: + +```elm +type Action + = RequestMore + | NewGif (Maybe String) + + +update : Action -> Model -> (Model, Effects Action) +update msg model = + case msg of + RequestMore -> + ( model + , getRandomGif model.topic + ) + + NewGif maybeUrl -> + ( Model model.topic (Maybe.withDefault model.gifUrl maybeUrl) + , Effects.none + ) + +-- getRandomGif : String -> Effects Action +``` + +So the user can trigger a `RequestMore` action by clicking the “More Please!” button, and when the server responds it will give us a `NewGif` action. We handle both these scenarios in our `update` function. + +In the case of `RequestMore` first return the existing model. The user just clicked a button, there is nothing to change right now. We also create an `Effects Action` using the `getRandomGif` function. We will get to how `getRandomGif` is defined soon. For now we just need to know that when an `Effects Action` is run, it will produce a bunch of `Action` values that will be routed throughout the application. So `getRandomGif model.topic` will eventually result in an action like this: + +```elm +NewGif (Just "http://s3.amazonaws.com/giphygifs/media/ka1aeBvFCSLD2/giphy.gif") +``` + +It returns a `Maybe` because the request to the server may fail. That `Action` will get fed right back into our `update` function. So when we take the `NewGif` route we just update the current `gifUrl` if possible. If the request failed, we just stick with the current `model.gifUrl`. + +We see the same kind of thing happening in `init` which defines the initial model and asks for a GIF in the correct topic from giphy.com’s API. + +```elm +init : String -> (Model, Effects Action) +init topic = + ( Model topic "assets/waiting.gif" + , getRandomGif topic + ) + +-- getRandomGif : String -> Effects Action +``` + +Again, when the random GIF effect is complete, it will produce an `Action` that gets routed to our `update` function. + +> **Note:** So far we have been using the `StartApp.Simple` module from [the start-app package](http://package.elm-lang.org/packages/evancz/start-app/latest), but now upgrade to the `StartApp` module. It is able to handle the complexity of more realistic web apps. It has [a slightly fancier API](http://package.elm-lang.org/packages/evancz/start-app/latest/StartApp). The crucial change is that it can handle our new `init` and `update` types. + +One of the crucial aspects of this example is the `getRandomGif` function that actually describes how to get a random GIF. It uses [tasks][] and [the `Http` package][http], and I will try to give an overview of how these things are being used as we go. Let’s look at the definition: + +[tasks]: http://elm-lang.org/guide/reactivity#tasks +[http]: http://package.elm-lang.org/packages/evancz/elm-http/latest + +```elm +getRandomGif : String -> Effects Action +getRandomGif topic = + Http.get decodeImageUrl (randomUrl topic) + |> Task.toMaybe + |> Task.map NewGif + |> Effects.task + +-- The first line there created an HTTP GET request. It tries to +-- get some JSON at `randomUrl topic` and decodes the result +-- with `decodeImageUrl`. Both are defined below! +-- +-- Next we use `Task.toMaybe` to capture any potential failures and +-- apply the `NewGif` tag to turn the result into a `Action`. +-- Finally we turn it into an `Effects` value that can be used in our +-- `init` or `update` functions. + + +-- Given a topic, construct a URL for the giphy API. +randomUrl : String -> String +randomUrl topic = + Http.url "http://api.giphy.com/v1/gifs/random" + [ "api_key" => "dc6zaTOxFJmzC" + , "tag" => topic + ] + + +-- A JSON decoder that takes a big chunk of JSON spit out by +-- giphy and extracts the string at `json.data.image_url` +decodeImageUrl : Json.Decoder String +decodeImageUrl = + Json.at ["data", "image_url"] Json.string +``` + +Once we have written this up, we are able to reuse `getRandomGif` in our `init` and `update` functions. + +One of the interesting things about the task returned by `getRandomGif` is that it can `Never` fail. The idea is that any potential failure *must* be handled explicitly. We do not want any tasks failing silently. + +I am going to try to explain exactly how that works, but it is not crucial to get every piece of this to use things! Okay, so every `Task` has a failure type and a success type. For example, an HTTP task may have a type like this `Task Http.Error String` such that we can fail with an `Http.Error` or succeed with a `String`. This makes it nice to chain a bunch of tasks together without worrying too much about errors. Now lets say our component requests a task, but the task fails. What happens then? Who gets notified? How do we recover? By making the failure type `Never` we force any potential errors into the success type such that they can be handled explicitly by the component. In our case, we use `Task.toMaybe : Task x a -> Task y (Maybe a)` so our `update` function must explicitly handle HTTP failures. This means tasks cannot silently fail, you always handle potential errors explicitly. + + +## Example 6: Pair of random GIF viewers + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/6.html) / [see code](examples/6/)** + +Alright, effects can be done, but what about *nested* effects? Did you think about that?! This example reuses the exact code from the GIF viewer in example 5 to create a pair of independent GIF viewers. + +As you look through [the implementation](examples/6/RandomGifPair.elm), notice that it is pretty much the same code as the pair of counters in example 2. The `Model` is defined as two `RandomGif.Model` values: + +```elm +type alias Model = + { left : RandomGif.Model + , right : RandomGif.Model + } +``` + +This lets us keep track of each independently. Our actions are just routing messages to the appropriate subcomponent. + +```elm +type Action + = Left RandomGif.Action + | Right RandomGif.Action +``` + +The interesting thing is that we actually use the `Left` and `Right` tags a bit in our `update` and `init` functions. + +```elm +-- Effects.map : (a -> b) -> Effects a -> Effects b + +update : Action -> Model -> (Model, Effects Action) +update action model = + case action of + Left msg -> + let + (left, fx) = RandomGif.update msg model.left + in + ( Model left model.right + , Effects.map Left fx + ) + + Right msg -> + let + (right, fx) = RandomGif.update msg model.right + in + ( Model model.left right + , Effects.map Right fx + ) +``` + +So in each branch we call the `RandomGif.update` function which is returning a new model and some effects we are calling `fx`. We return an updated model like normal, but we need to do some extra work on our effects. Instead of returning them directly, we use [`Effects.map`](http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects#map) function to turn them into the same kind of `Action`. This works very much like `Signal.forwardTo`, letting us tag the values to make it clear how they should be routed. + +The same thing happens in the `init` function. We provide a topic for each random GIF viewer and get back an initial model and some effects. + +```elm +init : String -> String -> (Model, Effects Action) +init leftTopic rightTopic = + let + (left, leftFx) = RandomGif.init leftTopic + (right, rightFx) = RandomGif.init rightTopic + in + ( Model left right + , Effects.batch + [ Effects.map Left leftFx + , Effects.map Right rightFx + ] + ) + +-- Effects.batch : List (Effects a) -> Effects a +``` + +In this case we not only use `Effects.map` to tag results appropriately, we also use the [`Effects.batch`](http://package.elm-lang.org/packages/evancz/elm-effects/latest/Effects#batch) function to lump them all together. All of the requested tasks will get spawned off and run independently, so the left and right effects will be in progress at the same time. + + +## Example 7: List of random GIF viewers + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/7.html) / [see code](examples/7/)** + +This example lets you have a list of random GIF viewers where you can create the topics yourself. Again, we reuse the core `RandomGif` module exactly as is. + +When you look through [the implementation](examples/7/RandomGifList.elm) you will see that it exactly corresponds to example 3. We put all of our submodels in a list associated with an ID and do our operations based on those IDs. The only thing new is that we are using `Effects` in the `init` and `update` function, putting them together with `Effects.map` and `Effects.batch`. + +Please open an issue if this section should go into more detail about how things work! + + +## Example 8: Animation + +**[demo](http://evancz.github.io/elm-architecture-tutorial/examples/8.html) / [see code](examples/8/)** + +Now we have seen components with tasks that can be nested in arbitrary ways, but how does it work for animation? + +Interestingly, it is pretty much exactly the same! (Or perhaps it is no longer surprising that the same pattern as in all the other examples works here too... Seems like a pretty good pattern!) + +This example is a pair of clickable squares. When you click a square, it rotates 90 degrees. Overall the code is an adapted form of example 2 and example 6 where we keep all the logic for animation in `SpinSquare.elm` which we then reuse multiple times in `SpinSquarePair.elm`. + +So all the new and interesting stuff is happening [in `SpinSquare`](examples/8/SpinSquare.elm), so we are going to focus on that code. The first thing we need is a model: + +```elm +type alias Model = + { angle : Float + , animationState : AnimationState + } + + +type alias AnimationState = + Maybe { prevClockTime : Time, elapsedTime: Time } + + +rotateStep = 90 +duration = second +``` + +So our core model is the `angle` that the square is currently at and then some `animationState` to track what is going on with any ongoing animation. If there is no animation it is `Nothing`, but if something is happening it holds: + + * `prevClockTime` — The most recent clock time which we will use for calculating time diffs. It will help us know exactly how many milliseconds have passed since last frame. + * `elapsedTime` — A number between 0 and `duration` that tells us how far we are in the animation. + +The `rotateStep` constant is just declaring how far it turns on each click. You can mess with that and everything should keep working. + +Now the interesting stuff all happens in `update`: + +```elm +type Action + = Spin + | Tick Time + + +update : Action -> Model -> (Model, Effects Action) +update msg model = + case msg of + Spin -> + case model.animationState of + Nothing -> + ( model, Effects.tick Tick ) + + Just _ -> + ( model, Effects.none ) + + Tick clockTime -> + let + newElapsedTime = + case model.animationState of + Nothing -> + 0 + + Just {elapsedTime, prevClockTime} -> + elapsedTime + (clockTime - prevClockTime) + in + if newElapsedTime > duration then + ( { angle = model.angle + rotateStep + , animationState = Nothing + } + , Effects.none + ) + else + ( { angle = model.angle + , animationState = Just { elapsedTime = newElapsedTime, prevClockTime = clockTime } + } + , Effects.tick Tick + ) +``` + +There are two kinds of `Action` we need to handle: + + - `Spin` indicates that a user clicked the shape, requesting a spin. So in the `update` function, we request a clock tick if there is no animation going and just let things stay as is if one is already going. + - `Tick` indicates that we have gotten a clock tick so we need to take an animation step. In the `update` function this means we need to update our `animationState`. So first we check if there is an animation in progress. If so, we just figure out what the `newElapsedTime` is by taking the current `elapsedTime` and adding a time diff to it. If the now elapsed time is greater than `duration` we stop animating and stop requesting new clock ticks. Otherwise we update the animation state and request another clock tick. + +Again, I think we can cut this code down as we write more code like this and start seeing the general pattern. Should be exciting to find! + +Finally we have a somewhat interesting `view` function! This example gets a nice bouncy animation, but we are just incrementing our `elapsedTime` in linear chunks. How is that happening? + +The `view` code itself is totally standard [`elm-svg`](http://package.elm-lang.org/packages/evancz/elm-svg/latest/) to make some fancier clickable shapes. The cool part of the view code is `toOffset` which calculates the rotation offset for the current `AnimationState`. + +```elm +-- import Easing exposing (ease, easeOutBounce, float) + +toOffset : AnimationState -> Float +toOffset animationState = + case animationState of + Nothing -> + 0 + + Just {elapsedTime} -> + ease easeOutBounce float 0 rotateStep duration elapsedTime +``` + +We are using [@Dandandan](https://github.com/Dandandan)’s [easing package](http://package.elm-lang.org/packages/Dandandan/Easing/latest) which makes it easy to do [all sorts of cool easings](http://easings.net/) on numbers, colors, points, and any other crazy thing you want. + +So the `ease` function is taking a number between 0 and `duration`. It then turns that into a number between 0 and `rotateStep` which we set to 90 degrees up at the top of our program. You also provide an easing. In our case we gave `easeOutBounce` which means as we slide from 0 to `duration`, we will get a number between 0 and 90 with that easing added. Pretty crazy! Try swapping `easeOutBounce` out for [other easings](http://package.elm-lang.org/packages/Dandandan/Easing/latest/Easing) and see how it looks! + +From here, we wire everything together in `SpinSquarePair`, but that code is pretty much exactly the same as in example 2 and example 6. + +Okay, so that is the basics of doing animation with this library! It is not clear if we nailed everything here, so let us know how things go as you get more experience. Hopefully we can make it even easier! + +> **Note:** I expect we can build some abstractions on top of the core ideas here. This example does some lower level stuff, but I bet we can find some nice patterns to make this easier as we work with it more. If you find it weird now, try to make something better and tell us about it! From b5f76b81837e7eec6e9d35365e22ad8032e6e09e Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:49:13 +0800 Subject: [PATCH 16/19] Rename README to README.md --- README => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md From dd9db1505df90b41fccd5c5d4106b5330d87956d Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:50:03 +0800 Subject: [PATCH 17/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf6c36a..f1f4747 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This tutorial outlines “The Elm Architecture” which you will see in all [Elm [NoRedInk]: https://www.noredink.com/ [CircuitHub]: https://www.circuithub.com/ -The Elm Architecture is a simple pattern for infinitely nestable components. It is great for modularity, code reuse, and testing. Ultimately, this pattern makes it easy to create complex web apps in a way that stays modular. We will run through 8 examples, slowly building on core principles and patterns: +The Elm Architecture is a simple pattern for infinitely nestable components. It is great for modularity, code reuse, and testing. Ultimately, this pattern makes it easy to create complex web apps in a way that stays modular. We will run through 8 examples, slowly building on core principles and patterns: 1. [Counter](http://evancz.github.io/elm-architecture-tutorial/examples/1.html) 2. [Pair of counters](http://evancz.github.io/elm-architecture-tutorial/examples/2.html) From 67e0c431641df2e571f0d5bfe246b7cdd6a4eff9 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:50:26 +0800 Subject: [PATCH 18/19] Update README-zh.md --- README-zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-zh.md b/README-zh.md index 8c79f32..05f61e3 100644 --- a/README-zh.md +++ b/README-zh.md @@ -2,7 +2,7 @@ ##開始之前:建議你可以查看包含安裝等步驟的完整版於: -[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md) +##[elm-lang 的中文手冊](https://github.com/EasonWang01/elm-lang-chinese-manual/blob/master/README.md) 這個教程 “The Elm Architecture” 你將會看到許多跟 [Elm][] 相關的程式, 包含 [TodoMVC][] 、 [dreamwriter][]以及正式上線的 [NoRedInk][] 、 [CircuitHub][]. 了解這個設計模式將對你在 Elm 中的程式或任何其他編程都很有幫助。 From 7d0def1759619e157ba1cefd4d225d5324e1f309 Mon Sep 17 00:00:00 2001 From: yicheng Date: Sun, 8 May 2016 12:55:40 +0800 Subject: [PATCH 19/19] Update README-zh.md --- README-zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-zh.md b/README-zh.md index 05f61e3..b181c7c 100644 --- a/README-zh.md +++ b/README-zh.md @@ -365,7 +365,7 @@ viewWithRemoveButton context model = `viewWithRemoveButton` function 增加了一個額外的按鈕,其中 increment/decrement 按紐送出訊息到 `actions` address 中,但移除按鈕 送出訊息到 `remove` address中。 在 `remove` 訊息中寫了類似下面的文字, “任何擁有我的人請移除我!” -現在我們有了新的 `viewWithRemoveButton`,我們可以建造一個 `CounterList` module 用來放置每個獨立的計數器, 其中 `Model` 和範例3的相同 a +現在我們有了新的 `viewWithRemoveButton`,我們可以建造一個 `CounterList` module 用來放置每個獨立的計數器, 其中 `Model` 和範例3的相同 ```elm type alias Model =