3秒でわかる
関数が戻り値を返す以外に外の世界を書き換える処理。通信・タイマー・DOM操作がこれにあたり、React では useEffect にまとめます。
もう少し詳しく
どういうものか
副作用とは、関数が値を計算して返す以外に、関数の外側の状態を書き換えたり外の世界へ働きかけたりする処理のことです。ファイルへの書き込み、HTTP通信、console.log の出力、グローバル変数の更新、DOMの直接操作、タイマーの登録がすべて副作用にあたります。逆に、引数だけから戻り値が決まり外に何も影響を与えない関数を純粋関数と呼びます。
なぜ必要か
副作用のない関数はテストが簡単です。引数を渡して戻り値を比べるだけで検証が終わります。しかし副作用が混ざると、実行の順番やそのときのネットワーク状態で結果が変わり、同じテストが通ったり落ちたりするようになります。だから副作用を消すのではなく、置き場所を決めて隔離するという発想が要ります。計算は純粋関数に、外界とのやり取りは決まった場所に、と分けておくと不具合の切り分けが一気に楽になります。
具体例
// 副作用あり。cart を書き換えるので何度呼ぶかで結果が変わる
let cart = [];
function addItem(item) {
cart.push(item);
localStorage.setItem("cart", JSON.stringify(cart));
}
// 副作用なし。何度呼んでも同じ配列が返り、cart は変わらない
function withItem(cart, item) {
return [...cart, item];
}Reactではレンダリング関数の中を純粋に保ち、通信や購読は useEffect に寄せます。
useEffect(() => {
const id = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(id); // 後片付けまでが副作用の管理
}, []);つまずきやすいところ
useEffect の戻り値で後片付けを書き忘れる例が最も多く、コンポーネントを開き直すたびにタイマーや購読が積み上がり、時計が倍速で進むといった症状になります。もうひとつは依存配列の空指定です。空にすると初回だけ実行されるので、あとから状態を参照しても初回の古い値を掴んだままになります。
似た用語との違い
| 語 | 指すもの |
|---|---|
| 副作用 | 外の状態を変える処理そのもの |
| 純粋関数 | 引数だけで戻り値が決まり副作用を持たない関数 |
| 冪等 | 同じ操作を何度実行しても結果が変わらない性質 |
副作用があっても冪等な処理はあります。同じキーで上書き保存するAPIは、副作用ありかつ冪等です。