iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

[Day27] 從ClojureScript 到 Reagent (4) Atom / swap! / deref

在看ClojureScript CheatsSheet時,發現有個目前還沒介紹到的區塊(紅色處)

那就是我們一直很好奇的Atoms / State

immutable data structure

為什麼需要Atoms / State呢?

因為我們在鐵人賽一開始就強調,Clojure(Script)裡的data structure 是immutable不可變的!

當我們改變data,Clojure會回傳一個新的變數

但在實務上,我們還是會有改變原本的值的需要

JavaScript更新State的方式

JavaScript可以直接對值進行修改:

// 宣告state
var state = {
  count: 0
};


// 更新state :
state.count += 1;

state.count // 1

來看看Clojure(Script)能不能改變原本的值

我們來def 名為 projectsMap{} data structure的binding

(def projects {})
;;=> #'tutorial.core/projects

輸入projects會回傳 {}空Map

projects
;;=> {}

那要如何更新Map的值呢?翻了一下手冊,覺得可以參考assoc

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
{}

atom

其實我們都知道,在實務上的確還是會有去改變狀態的需求,因此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!的用途,是幫我們回傳換進來的值,

(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}

deref / @

那我們要如何取值呢?

;; 用@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文件說明:


(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裡,關於前端顯示最重要的某些問題,例如:

  • How do we store and update state?
  • How do we ensure efficient view updates?

在reagent裡,都要自己設計解決方案~

因此,

在剩下最後三天鐵人賽,讓我們試著簡單了解Reframe這個聽起來很深奧又好像很厲害的前端框架(front-end framework)吧!


上一篇
[Day26] 從ClojureScript 到 Reagent (3之3) 路由 route簡介. 設計文章列表
下一篇
[Day28] 從ClojureScript, Reagent到 re-frame (1) 建立re-frame專案
系列文
後端Developer實戰ClojureScript: Reagent與前端框架 Reframe30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言