點燈坊

失くすものさえない今が強くなるチャンスよ

always() 與 thunkify() 比較

Sam Xiao's Avatar 2021-04-28

由於 pipe() 要求每個 Argument 都是 Function,因此實務上常遇到需組合出 () -> a 的需求,always()thunkify() 皆可完成,但兩者觀念不太一樣。

Version

Vue 3.0.5
Ramda 0.27.1

Point-free

effect000

按下 Get Book 會從 API 獲得 titleprice

<template>
  <div>
    <button @click="onClick">Get Book</button>
  </div>
  <div>
    {{ title }} / {{ price }}
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { effect } from 'vue3-fp'
import { pipe, andThen as then, prop, otherwise } from 'ramda'
import getBook from '/src/api/getBook'

let title = ref('')
let price = ref('')

let onClick = pipe(
  () => getBook(1),
  then(prop('data')),
  then(effect('title', title)),
  then(effect('price', price)),
  otherwise(console.error)
)
</script>

19 行

let onClick = pipe(
  () => getBook(1),
  then(prop('data')),
  then(effect('title', title)),
  then(effect('price', price)),
  otherwise(console.error)
)

使用 pipe() 組合 onClick()

  • () => getBook(1)getBook() 為 API function,由於 pipe() 要求每個 argument 都是 function,因此原本 fetchBook(1) 要改成 () => fetchBook(1) 成為 function
  • then(prop('data'))getBook() 回傳為 Promise,因此要在 then() 取得 data prop
  • then(effect('title', title)):將 title prop 寫入 title state,並回傳 Object
  • then(effect('price', price)):將 price prop 寫入 price state,並回傳 Object
  • otherwise(console.error):處理 Rejected Promise

always()

effect000

結果不變,但使用 always() 改寫。

<template>
  <div>
    <button @click="onClick">Get Book</button>
  </div>
  <div>
    {{ title }} / {{ price }}
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { effect } from 'vue3-fp'
import { pipe, andThen as then, prop, otherwise, always } from 'ramda'
import getBook from '/src/api/getBook'

let title = ref('')
let price = ref('')

let onClick = pipe(
  always(1),
  getBook,
  then(prop('data')),
  then(effect('title', title)),
  then(effect('price', price)),
  otherwise(console.error)
)
</script>

19 行

let onClick = pipe(
  always(1),
  getBook,
  then(prop('data')),
  then(effect('title', title)),
  then(effect('price', price)),
  otherwise(console.error)
)

使用 pipe() 組合 onClick()

  • always(1):準備 getBook 的參數
  • getBook:傳入 getBook()

thunkify()

effect000

結果不變,但使用 thunkify() 改寫。

<template>
  <div>
    <button @click="onClick">Get Book</button>
  </div>
  <div>
    {{ title }} / {{ price }}
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { effect } from 'vue3-fp'
import { pipe, andThen as then, prop, otherwise, thunkify } from 'ramda'
import getBook from '/src/api/getBook'

let title = ref('')
let price = ref('')

let onClick = pipe(
  thunkify(getBook)(1),
  then(prop('data')),
  then(effect('title', title)),
  then(effect('price', price)),
  otherwise(console.error)
)
</script>

19 行

let onClick = pipe(
  thunkify(getBook)(1),
  then(prop('data')),
  then(effect('title', title)),
  then(effect('price', price)),
  otherwise(console.error)
)

使用 pipe() 組合 onClick()

  • thunkify(getBook)(1):將 fetchBook() 轉成 Thunk,也就是 () -> a 的 function,如此則符合 pipe() 要求

Conclusion

  • always()thunkify() 皆能產生 () -> a,只是 always() 從 data 下手,而 thunkify() 是從 function 下手
  • thunkify() 在 Vue 3 使用有時會喪失 reactivity,如 watch(),使用 always() 則正常

Reference

Ramda, always()
Ramda, thunkify()