此次 Vue 大會看到了 Vue 新的 API 設計, 中間有一些以爲眼熟的寫法,
後面也看到了工業聚的一些解讀, 大體知道是什麼樣的用法吧..
固然現場演講過 Vue 具體實現的優化是更復雜的, 比這個 API 要多..html
其中比較讓我以爲眼熟的是 value(0)
還有特別是 state({count: 0})
的用法,前端
function useMouse() { const x = value(0) const y = value(0) const update = e => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } }
value()
返回的是一個 value wrapper (包裝對象)。一個包裝對象只有一個屬性:.value
,該屬性指向內部被包裝的值。這是由於當包裝對象被暴露給模版渲染上下文,或是被嵌套在另外一個響應式對象中的時候,它會被自動展開 (unwrap) 爲內部的值。vue
const count = value(0) const obj = state({ count }) console.log(obj.count) // 0 obj.count++ console.log(obj.count) // 1 console.log(count.value) // 1 count.value++ console.log(obj.count) // 2 console.log(count.value) // 2
做爲一個 ClojureScript 用戶我就想着大體對應 Clojure Atom 了.react
有點特別吧, 在 Clojure 裏面數據(value)和狀態(state)是不一樣的表示,
通常的都是 value, 好比數字, 字符串, 數組, 哈希表, 都是數據, 並且默認不能夠修改.
跟 js 很不同的, 好比說 [1 2 3]
, 數組, 這個是不能夠修改的,
若是修改其中元素了好比說加一個 4
獲得 [1 2 3 4]
就必定是新的引用了.git
若是在 ClojureScript 當中要表示一個狀態, 就須要使用 Atom(來自 Atomic, 原子性),
由於是引用而不是數據, Clojure 裏的習慣是用 *
做爲前綴來標示的,
經過 atom
函數能夠定義一個 Atom, 是一個引用, 裏面包裹了一個數據,
包裹在裏面的數據能夠是簡單的值(1, true, "str"), 也能夠是複合的數據(HashMap, Vector),github
cljs.user=> (def *a (atom [1 2 3])) #'cljs.user/*a cljs.user=> *a #object [cljs.core.Atom {:val [1 2 3]}]
這只是一個引用, 並且沒有 js 裏面那種賦值語法能夠直接去修改當中的數據,
須要操做數據的時候, 要經過特定的函數, 好比 reset!
或者 swap!
編程
cljs.user=> (swap! *a conj 4) [1 2 3 4] cljs.user=> (reset! *a [1 2 3 4]) [1 2 3 4]
你也不能直接讀取數據了, 直接去讀, 拿到的是一個引用, 而不是實際的值,
這時候須要一個 "dereference" 的操做, 就是函數 deref
, 或者直接用 @
前綴:api
cljs.user=> *a #object [cljs.core.Atom {:val [1 2 3 4]}] cljs.user=> @*a [1 2 3 4] cljs.user=> (deref *a) [1 2 3 4]
參考: https://www.braveclojure.com/...數組
再回來看 js 這邊, js 對象沒有專門的語法來區分引用不引用的概念, 對象上的 key 都是引用,
不過, 經過 Proxy 劫持掉賦值操做, 能夠在內部插入一系列的邏輯.
而這個例子當中的 state
函數, 就跟 Clojure 當中的 atom
比較像了.緩存
import { state } from 'vue' const object = state({ count: 0 }) object.count++
這個狀態會發生修改, 也就須要 watch
的操做來處理數據更新的狀況,
watch( // getter () => count.value + 1, // callback (value, oldValue) => { console.log('count + 1 is: ', value) } ) // -> count + 1 is: 1 count.value++ // -> count + 1 is: 2
以及取消監聽:
const stop = watch(...) // stop watching stop()
在 Clojure 當中 atom 也有對應的 API 來添加監聽和取消監聽,
取消通常用一個 Keyword 來標記的, 好比這個例子經過 :logger
來取消.
(def a (atom nil)) ;; The key of the watch is `:logger` (add-watch a :logger #(println %4)) (reset! a [1 2 3]) ;; Deactivate the watch by its assigned key (remove-watch a :logger)
另外, 雖然 js 數據自己是可變的, 在 state 當中也是容許賦值的,
可是我注意到部分場景, 框架做者並不但願用戶任意去修改數據, 特別是傳遞過的數據
Note this props object is reactive - i.e. it is updated when new props are passed in, and can be observed and reacted upon using the watch function introduced later in this RFC. However, for userland code, it is immutable during development (will emit warning if user code attempts to mutate it).
這個行爲跟 Clojure 最初的設計思路比較類似, Clojure 要求數據是不可的.
ClojureScript 的不可變數據雖然在前端用使用場景恰好適用, 特別是 React 當中.
可是最初 Clojure 選擇了不可變數據, 設計了 Atom 的概念, 是爲了併發編程考慮的.
狀態可能會被多個線程共享, 因此須要 ref(引用)的改變, 讓多個線程能修改這個狀態.
咱們知道 value(數據)被一個進程拿到, 是不該該被另外一個進程偷偷修改掉的.
在 js 當中咱們也會遇到, 一個對象若是傳給另外一方, 最好先拷貝一份,
若是把原對象傳過去, 別人隨意修改了, 己方的邏輯可能遇到異常狀況而出錯.
Clojure 選擇的方案是, 把數據設計成不可變的, 這樣任意傳遞, 都不會遇到不一致的問題.
若是須要能被修改, 那就是 state(狀態)了, 就須要用 atom
封裝了, 而後再傳過去.
這樣一比較, 就會以爲 Clojure 對比 JavaScript 數據, 就是故意反過來設計的,
js 當中對象和數組默認就是能夠任意被修改的, 須要的時候 freeze 掉, 或者加上 watch 監聽.
Clojure 當中 HashMap 跟 Vector 默認是不可變的, 須要狀態的時候放在 Atom 裏去,
而 Atom 就是能夠經過修改引用來修改的, 也能被監聽. 只是說裏邊的數據依然是不可變的.
除了這種共享狀態是用 Atom 的, Clojure 也把 Atom 用在性能優化的地方,
好比計算 fibonacci 的時候, 須要緩存, 就會用 atom
存一個能夠隨時修改的數據,
(defn memoize [f] (let [mem (atom {})] (fn [& args] (if-let [e (find @mem args)] (val e) (let [ret (apply f args)] (swap! mem assoc args ret) ret))))) (defn fib [n] (if (<= n 1) n (+ (fib (dec n)) (fib (- n 2))))) (time (fib 35)) ; user=> "Elapsed time: 941.445 msecs" (def fib (memoize fib)) (time (fib 35)) ; user=> "Elapsed time: 941.445 msecs"
https://clojure.org/reference...
這種可變狀態在 Clojure 當中通常被放在局部使用,
這也是函數式編程慣用的套路, 函數式編程認爲狀態就應該是被隔離的.
這個習慣跟 js 那邊也是不同...
js 你們用 Vue 或者 Mobx 習慣了, 就會習慣處處用 observable 解決問題.
好比 Svelte 3 當中有一個例子, doubled = count * 2
,
這是一個 Reactive 的值, count
改變, doubled
跟着改變, 最後界面也改變,
Svelte 當中用這樣的代碼來表示:
<script> let count = 0; $: doubled = count * 2; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button> <p>{count} doubled is {doubled}</p>
參考: https://svelte.dev/tutorial/r...
函數式編程當中值是不會發生改變的, 因此無法對值進行監聽,
那麼, 函數式編程就會引入一個 wrapper 的概念, 好比說用 Monad 設計一個...
在 Clojure 當中, Atom 是一個, 或者用 Channel 來包裹這個隨時間改變的數據...
在新的 Vue API 當中有了一個 computed
的概念,
這個 computed
封裝過的數據, 就有個 .value
的引用, 表示最新的值:
import { value, computed } from 'vue' const count = value(0) const countPlusOne = computed(() => count.value + 1) console.log(countPlusOne.value) // 1 count.value++ console.log(countPlusOne.value) // 2
這個寫法看着就跟 Clojure 當中的 ref, 用 Atom 包裹一個值很像了.
因此就感受想法可能愈來愈像了.. 特別是引入不可變數據又須要數據被監聽同時被傳遞的時候...
js 原始的 Object 模擬的就是一塊內存, 內存某個位置能夠被修改,
對於 Reactive System 來講, 這個結構過於簡單了... 業務當中又但願不能被隨便修改, 又要能替換又能監聽...
函數式編程在這方面有很多思考... 相互借鑑...
留一個我廠(積夢)招聘的連接 https://github.com/jimengio/h... 咱們前端總體都是用 Immer 優化 React 的.