新的 Vue Function-based API 当中的看到的 Clojure 的影子

2019-06-12 admin

这次 Vue 大会看到了 Vue 新的 API 设计, 中间有一些觉得眼熟的写法, 后面也看到了工业聚的一些解读, 大致知道是什么样的用法吧… 当然现场演讲过 Vue 具体实现的优化是更复杂的, 比这个 API 要多…

其中比较让我觉得眼熟的是 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) 为内部的值。

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 了.

有点特别吧, 在 Clojure 里面数据(value)和状态(state)是不同的表示, 一般的都是 value, 比如数字, 字符串, 数组, 哈希表, 都是数据, 而且默认不可以修改. 跟 js 很不一样的, 比如说 [1 2 3], 数组, 这个是不可以修改的, 如果修改其中元素了比如说加一个 4 得到 [1 2 3 4] 就一定是新的引用了.

如果在 ClojureScript 当中要表示一个状态, 就需要使用 Atom(来自 Atomic, 原子性), 因为是引用而不是数据, Clojure 里的习惯是用 * 作为前缀来标示的, 通过 atom 函数可以定义一个 Atom, 是一个引用, 里面包裹了一个数据, 包裹在里面的数据可以是简单的值(1, true, “str”), 也可以是复合的数据(HashMap, Vector),

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, 或者直接用 @ 前缀:

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).

https://github.com/vuejs/rfcs…

这个行为跟 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 的.

[转载]原文链接:https://segmentfault.com/a/1190000019455310

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-67119.html

文章标题:新的 Vue Function-based API 当中的看到的 Clojure 的影子

相关文章
一些前端学习中好的书籍,整理
一、Javascript方面的书籍: 1 JavaScript权威指南(第6版):号称javascript圣经,前端必备;前端程序员学习核心JavaScript语言和由Web浏览器定义的JavaScript API的指南和综合参考手册; 2...
2015-11-12
vue 数组遍历方法forEach和map的原理解析和实际应用
一、前言 forEach和map是数组的两个方法,作用都是遍历数组。在vue项目的处理数据中经常会用到,这里介绍一下两者的区别和具体用法示例。 二、代码 1. 相同点 都是数组的方法 都用来遍历数组 两个函数都有4个参数:匿名函数中可传3...
2018-11-15
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
10个强大的纯CSS3动画案例分享
我们的网页外观主要由CSS控制,编写CSS代码可以任意改变我们的网页布局以及网页内容的样式。CSS3的出现,更是可以让网页增添了不少动画元素,让我们的网页变得更加生动有趣,并且更易于交互。本文分享了10个非常炫酷的CSS3动画案例,希望大家...
2015-11-16
v-charts | 饿了么团队开源的基于 Vue 和 ECharts 的图表工具
在使用echarts生成图表时,经常需要做繁琐的数据类型转化、修改复杂的配置项,v-charts的出现正是为了解决这个 痛点。基于Vue2.0和echarts封装的v-charts图表组件,只需要统一提供一种对前后端都友好的数据格式 设置简...
2018-05-24
Vue获取DOM元素样式和样式更改示例
在 vue 中用 document 获取 dom 节点进行节点样式更改的时候有可能会出现 ‘style’ is not definde的错误,这时候可以在 mounted 里用 $refs 来获取样式,并进行更改: &lt;template...
2017-03-13
Vue.js组件tab实现选项卡切换
本文实例为大家分享了vue插件tab选项卡的具体代码,供大家参考,具体内容如下 效果图: 代码如下: &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; ...
2017-03-13
从2014年的发展来展望JS的未来将会如何
&lt;font face=&quot;寰�杞�闆呴粦, Arial, sans-serif &quot;&gt;2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
ajax为什么令人惊异?ajax的优缺点
使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 Ajax不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。就像DHT...
2015-11-12
回到顶部