副作用の例
実行するタイミングによって何の値が返ってくるかわからない処理が記述してある。
- 時刻を取得する処理
- ファイル読み込み
- DB読み込み
- 環境変数の参照
- グローバル変数参照
- 乱数生成
- JavaScriptのconsole.log(なぜ副作用があるかはよくわからない。)
classであれば保持しているデータを外部からいつでも変更できる。(セッターメソッドなど)
副作用は全て排除すれば良いのか?
では、画面入力やファイル入力などプログラミングには状態を持たせることが不可欠なので、以下の思想が重要になる。
「副作用を持たない部分」(純粋関数)と「副作用を持つ部分」を分離することが重要。
副作用を持つ関数から副作用を持たない関数を呼び出して良いが、逆に副作用を持たない関数から副作用を持つ関数を呼び出してはいけない。
副作用を持たない関数
引数に日付を受け取って処理する関数
副作用を持つ関数
関数の内部で日付を生成して処理する関数
副作用の対策
実行するタイミングによって何の結果が返ってくるかわからない。
外部から値をもらうように記述する。
副作用があるソースのデメリット
テストコードを書きづらくなる。
実行するたびに結果が変わるのでテストコードは書けないです。
フロントエンドはなぜイミュータブルが良いのか?
そもそもイミュータブルとは?
オブジェクトや配列の状態が変わらないことです。
なぜ、昨今のフロント開発ではイミュータブルなことが推奨されているか
動的に画面を再描画させるため
昨今のWeb開発では、時間経過やユーザーのインタラクションによって状態を変化させることでシームレス(途切れなく)に描画を変化させる必要がある。
JavaScriptは配列やオブジェクト、クラスインスタンスの値を変更した場合は、その変更は変更元の変数を破壊的な影響を及ぼしてしまいます。
フロントエンドでは動的に画面を描画するには、動的に画面を描画するには状態を変更する必要があるので、副作用の影響が及びやすいです。
例えば、ReactやReduxのstateはオブジェクトの差分でチェックしており、ミュータブルに値を更新してしまうと変更前の値も書き変わってしまい同一とみなされて再描画されないためです。(shallow equality checking(参照元のメモリの番地が変わっているかを見ている。中身が変わっても参照元が同じだと変化がないとみなされる。)という仕組みを採用しています。)
予測できないstateの更新を妨げられる。
いつでも誰でもstateを更新できてしまうと現在の状態が分からなくなってしまう。しかも、バグではないからエラーも出ないので誰が更新したかを追うことも難しいです。
フロントはデータ数が少ないので、複製コストが低い
フロントは描画コストの都合上データ数が少なくなっています。なので、仮にイミュータブルを保つために複製したとしても大したコストにはなりません。
対策
スプレット構文を使って値をイミュータブルにすることです。新しいオブジェクトを生成します。
1 2 3 4 |
const objA = {a: 10 }; let objB = { ...objA, b: 20 }; console.log(objA); // -> { a: 10 } console.log(objB); // -> { a:10,b: 20 } |
ただし、イミュータブルにしすぎるとソースが冗長になる。
ただ、イミュータブルであることを守りすぎると「...」が増えすぎてソースが読みづらくなります。
Immerを使う。
Immerというライブラリを使うのが良いです。概要だけ説明すると、破壊的に思いっきり書いたとしてもこのライブラリが普通にイミュータブルなコードに勝手に変換してくれます。うしたスプレット構文を排除してスッキリ書こうというのを目指したのが、immerというライブラリです。
この記事へのコメントはありません。