點燈坊

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

使用 Either 同時檢查多個條件

Sam Xiao's Avatar 2021-04-21

實務上在檢查時一定會同時檢查多個條件,此時必須使用 chain 搭配 Either。

Version

Vue 3.0.11
Sanctuary 3.1.0

Composition API

identical000

按下 Submit 會檢查 password1 是否為空值。

identical001

按下 Submit 會檢查 password2 是否為空值。

identical002

還會檢查 password1password2 是否相等。

identical003

若通過檢查則顯示 Your password is xxx

可發現 Submit 一共檢查了 3 個條件

<template lang='pug'>
div
  span Password1:
  input(v-model='password1')
div
  span Password1:
  input(v-model='password2')
div
  button(@click='onClick') Submit
div {{ msg }}
</template>

<script setup>
ref: password1 = ''
ref: password2 = ''
ref: msg = ''

let onClick = () => {
  if (password1 === '') {
    msg = 'Password1 is empty'
    return
  }

  if (password2 === '') {
    msg = 'Password2 is empty'
    return
  }

  if (password1 !== password2) {
    msg = 'Password is not identical'
    return
  }

  msg = `Your password is ${password1}`
}
</script>

18 行

let onClick = () => {
  if (password1 === '') {
    msg = 'Password1 is empty'
    return
  }

  if (password2 === '') {
    msg = 'Password2 is empty'
    return
  }

  if (password1 !== password2) {
    msg = 'Password is not identical'
    return
  }

  msg = `Your password is ${password1}`
}

Imperative 會使用 Guard Clause,也就是每個 if 檢查若失敗則提早 return,避免使用 else

Either

identical000

結果不變,但使用 Either 改寫。

<template lang='pug'>
div
  span Password1:
  input(v-model='password1')
div
  span Password2:
  input(v-model='password2')
div
  button(@click='onClick') Submit
div {{ msg }}
</template>

<script setup>
import { ref, unref } from 'vue'
import { read, write } from 'vue3-fp'
import { pipe, isEmpty, ifElse, thunkify, map, chain, concat } from 'ramda'
import { Left, Right, either, I } from 'sanctuary'

let password1 = ref ('')
let password2 = ref ('')
let msg = ref ('')

let chkEmpty = field => ifElse (
  isEmpty,
  thunkify (Left) (`${field} is empty`),
  Right
)

let chkIdentical = ifElse (
  x => unref (password2) === x,
  Right,
  thunkify (Left) ('Password is not identical')
)

let onClick = pipe (
  read (password1),
  chkEmpty ('Password1'),
  map (read (password2)),
  chain (chkEmpty ('Password2')),
  map (read (password1)),
  chain (chkIdentical),
  map (concat ('Your password is ')),
  either (I) (I),
  write (msg)
)
</script>

35 行

let onClick = pipe (
  read (password1),
  chkEmpty ('Password1'),
  map (read (password2)),
  chain (chkEmpty ('Password2')),
  map (read (password1)),
  chain (chkIdentical),
  map (concat ('Your password is ')),
  either (I) (I),
  write (msg)
)

使用 pipe 組合出 onClick

  • read (password1):讀取 password1 state
  • chkEmpty ('Password1'):檢查 password1 state 是否為空值,無論結果如何都會回傳 Either,避免為空值時繼續後續運算
  • map (read (password2)):繼續讀取 password2 state,由於前一個 function 回傳 Either,因此 read 必須包在 map
  • chain (chkEmpty ('Password2')):繼續檢查 password2 state 是否為空值,無論結果如何都會回傳 Either,但因為已經在 Either 內,chkEmpty 再回傳 Either 會造成兩層 Either,因此使用 chain 攤平成為一層 Either
  • map (read (password1)):繼續讀取 password1 state,由於前一個 function 回傳 Either,因此 read 必須包在 map
  • chain (chkIdentical):繼續檢查 password1password2 是否相等,無論結果如何都會回傳 Either,但因為已經在 Either 內,chkIdentical 再回傳 Either 會造成兩層 Either,因此使用 chain 攤平成為一層 Either
  • map (concat ('Your password is ')):若通過檢查則顯示 Your password is xxx
  • either (I) (I):從 Left Either 與 Right Either 內取出值
  • write (msg):最後寫入 msg state

23 行

let chkEmpty = field => ifElse (
  isEmpty,
  thunkify (Left) (`${field} is empty`),
  Right
)

使用 ifElse 組合出 chkEmpty

  • isEmpty:檢查是否為空值
  • thunkify (Left) ():若為空值則回傳 Left Either
  • Right:若不是空值則回傳 Right Either

29 行

let chkIdentical = ifElse (
  x => unref (password2) === x,
  Right,
  thunkify (Left) ('Password is not identical')
)

使用 ifElse 組合出 chkIdentical

  • x => unref(password2) === x:檢查是否相等
  • Right:若相等則回傳 Right Either
  • thunkify (Left) ('Password is not identical'):若不相等則回傳 Left Either

Conclusion

  • 可發現若只有一個檢查條件回傳 Either,則後續的運算都要包在 map 內;但若有多個條件都回傳 Either,則後續的運算先使用 chain 攤平成一層 Either,然後才能使用 map 套用其他 function

Reference

Sanctuary, Either