在看ClojureScript CheatsSheet時,發現有個目前還沒介紹到的區塊(紅色處)
那就是我們一直很好奇的Atoms / State
為什麼需要Atoms / State
呢?
因為我們在鐵人賽一開始就強調,Clojure(Script)裡的data structure 是immutable不可變的!
當我們改變data,Clojure會回傳一個新的變數
但在實務上,我們還是會有改變原本的值的需要
JavaScript可以直接對值進行修改:
// 宣告state
var state = {
count: 0
};
// 更新state :
state.count += 1;
state.count // 1
我們來def
名為 projects
的 Map{}
data structure的binding
(def projects {})
;;=> #'tutorial.core/projects
輸入projects會回傳 {}
空Map
projects
;;=> {}
那要如何更新Map的值呢?翻了一下手冊,覺得可以參考assoc
https://clojuredocs.org/clojure.core/assoc
語法長這樣
tutorial.core=> (assoc {} :key1 "value" :key2 "another value")
{:key1 "value", :key2 "another value"}
雖然看起來好像已經把Map內指定為另一個值了
但看到手冊說 When applied to a map, returns a new map of the same (hashed map/sorted map) type
其實是回傳新的Map
assoc usage
(assoc map key val)(assoc map key val & kvs)
assoc[iate].
When applied to a map, returns a new map of the same (hashed/sorted) type, that contains the mapping of key(s) to val(s).
When applied to a vector, returns a new vector that contains val at index. Note - index must be <= (count vector).
如果不死心的話,再來試試看語法:
(def ironprojects {})
;;=> #'tutorial.core/ironprojects
ironprojects
;;=> 輸入ironprojects會回傳 `{}`空Map
;;=> {}
{assoc ironprojects :day 27}
{#object[clojure.core$assoc__5416 0x549fe763 "clojure.core$assoc__5416@549fe763"] {}, :day 27}
ironprojects
本身還是一樣沒有改變~
tutorial.core=> ironprojects
{}
其實我們都知道,在實務上的確還是會有去改變狀態的需求,因此ClojureScript提供了一種reference type
的方式: atom 來管理狀態變更。
剛剛舉出了JavaScript可以直接對值進行修改的例子:
// 宣告state
var state = {
count: 0
};
// 更新state :
state.count += 1;
state.count // 1
在ClojureScript我們的做法是加入:
;; 宣告state時加入atom
(def state (atom {:count 0}))
;;=> #'tutorial.core/state
;;=> 更新state
(swap! state #(update % :count inc))
;;=> {:count 1}
來看看atom是何方神聖~為什麼這麼神奇呢?
atom
(atom x)(atom x & options)
Creates and returns an Atom with an initial value of x and zero or more options (in any order):
:meta metadata-map
:validator validate-fn
If metadata-map is supplied, it will become the metadata on the atom.
validate-fn must be nil or a side-effect-free fn of one argument, which will be passed the intended new state on any state change.
If the new state is unacceptable, the validate-fn should return false or throw an exception.
atom會幫我們建立和回傳有初始值的state影分身,而這個分身可以記錄state的變化
至於swap!的用途,是幫我們回傳換進來的值,
(swap! atom f)
(swap! atom f x)
(swap! atom f x y)
(swap! atom f x y & args)
Atomically swaps the value of atom to be:
(apply f current-value-of-atom args).
Note that f may be called multiple times,
and thus should be free of side effects.
Returns the value that was swapped in.
因此,看起來的效果好像賦值一樣!
;;=> 更新state
(swap! state #(update % :count inc))
;;=> {:count 1}
那我們要如何取值呢?
;; 用@state取值
@state
;;=> {:count 1}
;; 也可以用deref取值
(deref state)
;;=> {:count 1}
;; 來看看deref state本人是什麼
tutorial.core=> deref state
#object[clojure.core$deref 0x3f09c984 "clojure.core$deref@3f09c984"]
#object[clojure.lang.Atom 0x7ec33cd4 {:status :ready, :val {:count 1}}]
(deref ref)
(deref ref timeout-ms timeout-val)
(Also reader macro: @ref/@agent/@var/@atom/@delay/@future/@promise)
Within a transaction,
returns the in-transaction-value of ref,
else returns the most-recently-committed value of ref.
When applied to a var, agent or atom, returns its current state.
When applied to a delay, forces it if not already forced.
When applied to a future, will block if computation not complete.
When applied to a promise, will block until a value is delivered.
The variant taking a timeout can be used for blocking references (futures and promises),
and will return timeout-val if the timeout (in milliseconds) is reached before a
value is available.
從文件裡可以知道deref的不同階段,例如我們今天舉例的atom: return current state。此外還有delay
, future
, promise
等。
Reagent的Atoms在使用上也是一樣的概念~。但 deref
最困難地方是的是掌握狀態改變的時間點?怎麼判斷什麼deref時間點是太早還是太晚呢?
在做研究時發現了一個標題很吸睛的文章Reagent looks terrific. Why do I need re-frame?主要是在指控投訴 在Reagent裡,關於前端顯示最重要的某些問題,例如:
在reagent裡,都要自己設計解決方案~
因此,
在剩下最後三天鐵人賽,讓我們試著簡單了解Reframe這個聽起來很深奧又好像很厲害的前端框架(front-end framework)吧!