無限大な夢のあと

テニスとアニメが大好きな厨二病SEのブログ

stripeでfp-tsを活用した例 #1 決済入力項目のクリア

最近、TypeScript/ReactでWebフロントエンドをやっています。
stripeの接続をやっている中で、とにかくundefinedかnullかを判定したりなどでとても面倒になり、本来はTypeScriptの機能だけを活用するべきですが、fp-tsを導入しました。
今までSwiftやScalaをやっていく中でOptionとか、for式楽だなぁと思っていたので、そこもうまく活用してみた感じです。
一旦、備忘録的に書いていきます。

今回紹介するのは画面でstripeで決済した際に、入力していたクレジットカード番号、有効期限、CVCなどの項目のクリアを行っています。

大枠の処理自体はgistに貼り付けました。
もうちょい見やすくかけるかもしれないので、ご意見欲しいです。

今回はpipe、fromNullable、chainNullableKを使っています。
それぞれのメソッドについて、簡単に紹介していきます。

pipe

正確なすごい簡単にいうと、関数を適用していく際に入れ子ではなく、パイプラインのように記述できます。
ライブラリ側のソースコードのドキュメントコメントにはこのような感じでわかりやすい例で書いています。

Pipes the value of an expression into a pipeline of functions.
See also flow.
Example:
import { pipe } from 'fp-ts/function'  

const len = (s: string): number => s.length  
const double = (n: number): number => n * 2 
assert.strictEqual(double(len('aaa')), 6)    // without pipe  
assert.strictEqual(pipe('aaa', len, double), 6)  // with pipe  

Since:2.6.3

個人的には、こっちの方が読む立場から見るとわかりやすい派です。

pipe(
  'aaa',
  len,      //ここのメソッドの引数に'aaa'が渡って来て、len('aaa')が実行される
  double  //ここのメソッドの引数に、lenメソッドに'aaa'を実行した結果が渡されて、double(lenメソッドに'aaa'を実行した結果)が実行される
)

Option.fromNullable

https://gcanti.github.io/fp-ts/modules/Option.ts.html#fromnullable

これはnullやundefinedを引数に渡すと値がない概念の型のnoneが返り、それ以外だった場合は、値がある概念の型のsomeにラップされた値が返ります。
関数型的にはいわゆるMaybeモナドという概念ですね。

ここも同じく、ライブラリ側のソースコードのドキュメントコメントにはこのような感じでわかりやすい例で書いています。

Constructs a new Option from a nullable type. If the value is null or undefined, returns None, otherwise returns the value wrapped in a Some.
Example:
import { none, some, fromNullable } from 'fp-ts/Option'   

assert.deepStrictEqual(fromNullable(undefined), none)  
assert.deepStrictEqual(fromNullable(null), none)  
assert.deepStrictEqual(fromNullable(1), some(1)) 

Since:2.0.0

これは本当の便利な概念だと思っていて、例えばJavaScript/TypeScriptのfindとかって、値がない場合ってundefinedが返りますよね。
developer.mozilla.org

それが型レベルとかでわからないですし、その後にメソッド呼び出しするとエラーになって落ちてしまいます。
また、nullだったり、undefinedだったりの判定も面倒ですよね。

これを型レベルで防いでくれるのが嬉しかったりします。
findとかで返り値がOptionになったりします。
https://gcanti.github.io/fp-ts/modules/Array.ts.html#findfirst
https://gcanti.github.io/fp-ts/modules/Array.ts.html#findfirstmap

Option.chainNullableK

https://gcanti.github.io/fp-ts/modules/Option.ts.html#chainnullablek

Optionの値があった場合(someだった場合)に渡した関数で処理をして、値がなかった場合(noneだった場合)には何もしないです。
pipeと組み合わせて使うと強力に使えます。

ここも同じく、ライブラリ側のソースコードのドキュメントコメントにはこのような感じでわかりやすい例で書いています。

 import { some, none, fromNullable, chainNullableK } from 'fp-ts/Option'
 import { pipe } from 'fp-ts/function'
 
 interface Employee {
   company?: {
     address?: {
       street?: {
         name?: string
       }
     }
   }
  }
 
 const employee1: Employee = { company: { address: { street: { name: 'high street' } } } }
 
 assert.deepStrictEqual(
   pipe(
     fromNullable(employee1.company),
     chainNullableK(company => company.address),
     chainNullableK(address => address.street),
     chainNullableK(street => street.name)
   ),
   some('high street')
  )
 
 const employee2: Employee = { company: { address: { street: {} } } }
 
 assert.deepStrictEqual(
   pipe(
     fromNullable(employee2.company),
     chainNullableK(company => company.address),
     chainNullableK(address => address.street),
     chainNullableK(street => street.name)
   ),
   none
 )
 
since 2.9.0

stipe+fp-ts活用

こんな感じでメソッドを組み合わせて、以下のような形で書いてみました。

今回の大きなモチベーションとしては、stripeで準備している以下のメソッドを呼んだときに、nullで返ってくる可能性があります。

element.getElement(CardNumberElement)
element.getElement(CardExpiryElement)
element.getElement(CardCvcElement)

getElement(component: CardNumberElementComponent): stripeJs.StripeCardNumberElement | null;
        /**
         * Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_element?type=card) for the `CardCvcElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
         * Returns `null` if no `CardCvcElement` is rendered in the current `Elements` provider tree.
         */
getElement(component: CardCvcElementComponent): stripeJs.StripeCardCvcElement | null;
        /**
         * Returns the underlying [element instance](https://stripe.com/docs/js/elements_object/create_element?type=card) for the `CardExpiryElement` component in the current [Elements](https://stripe.com/docs/stripe-js/react#elements-provider) provider tree.
         * Returns `null` if no `CardExpiryElement` is rendered in the current `Elements` provider tree.
         */
getElement(component: CardExpiryElementComponent): stripeJs.StripeCardExpiryElement | null;

値があった場合は都度チェックしてclearするというのが面倒だったので、これをスマートに実現したくて、fromNullable、chainNullableKを使ってみた感じです。

また、以下の中でまたpipeが入ると入れ子が深くなるので、返り値の関数としてO.fromNullableを渡しています。

O.chainNullableK((e: stripeJs.StripeCardNumberElement) => e.clear())(O.fromNullable(element.getElement(CardNumberElement)))