なぜreducerで副作用を起こしてはならないか
Tags: react, redux, japanese
Published: 2018-07-25 14:02:03

---
title: なぜreducerで副作用を起こしてはならないか
published: true
description: Why "Keep PURE function in reducer"?
tags: react, redux, japanese
---

# TL;DR
reducerで副作用を起こすと、最悪の場合、コンポーネントのチューニングが不可能になる

# 本題
こんにちは。まずこのようなコードがありました。

```js
class Account {
    constructor(id, name){
        this.id = id;
        this.name = name;
        this.aka = [];
    }
    
    pushAka(name){
        this.aka.push(name);
        return this;
    }
}
```

このインスタンスをreducerで管理したい。そんなことありますよね。たとえば次のように…（redux-actionsを暗黙的に使用している点はご容赦ください）  

```js
const initState = new Account('Donald Trump');

export default handleActions({
  "ADD_AKA": (state, action) => (state.pushAka(action.payload.aka))
}, initState);

```

これは、特に何も考えなければ、とりあえずはうまく動きます。しかし、[Reduxの3つの基本概念](https://redux.js.org/introduction/three-principles)である大前提**副作用を起こしてはならない**という点において、間違っています。  
問題は`Account`クラスの`pushAka(name)`メソッドにあります。結局のところ、これは***自分自身のメンバ変数を変更した上で自分自身を返している***という事そのものに問題があります。しかし、今の所うまく動きます。  
[この時点でのStackblitzのサンプルです](https://stackblitz.com/edit/reducer-not-pure-anti-pattern)。  
  
さて、今の所は動いています。すでに大問題ですが、ここから取り返しのつかないことが起きてきます。  
Reactは高速ですが、それでもチューニングが必要になる場合が多々あります。コンポーネントの無駄な再レンダリングを防止するために、状況に合わせて主に以下の3つのチューニングが行われます。

* `componentShouldUpdate(prevState, nextState)`の使用
* `React.Component`ではなく`React.PureComponent`を使用
* Stateless Functional Componentsでは`recompose/pure`,`recompose/onlyUpdateForKeys`を使う
* 自分でpure HoCを書く

さて、この場合でもチューニングを行ってみましょう。今回は先程のサンプルにおいては`components/AkaList.js`がStateless Functional Componentsですから、試しに`pure`を使ってコンポーネントのチューニングを行ってみます。単に次のように書き換えるだけです…

```jsx
import React, {Fragment}from 'react';
import {pure} from 'recompose';

const AkaList = (props) => (
  <Fragment>
    {props.account.aka.map((v, i) => (<p key={i}>{v}</p>))}
  </Fragment>
)

export default pure(AkaList);
```

recomposeの`pure`でコンポーネントの再レンダリングを抑えようとしています(例として少し極端ですが、ご容赦ください。時間がなかったんだ でも動かないサンプルがこちらにある)  
  
すると、リストがあるはずの場所に、何もレンダリングされなくなります。もっと具体的に言うと、コンポーネントがマウントされ、最初のレンダリングがされてからは一切の再レンダリングが行われなくなります。  
ある意味では最高のパフォーマンスを得たわけですが、全ての場合においてこれは問題です。  
  
[この時点のStackblitzのサンプルです](https://stackblitz.com/edit/reducer-not-pure-anti-pattern-tuenning)。  


# どうするべきなのか
副作用があるような設計が悪いとしか言いようがありません。  
ここでは、一番はじめのコードで示した`Account`クラスの`pushAka(name)`メソッドが悪いです。そこで、次のようなコードに置き換えます。  

```js
class Account {
    constructor(name){
        this.id = Date.now();
        this.name = name;
        this.aka = [];
    }
    
    pushAka(name){
        const r =  Object.assign(Object.create(Object.getPrototypeOf(this)), this);
        r.aka = [...r.aka, name];
        return r;
    }
}
```

`r`に自分自身を浅いコピーを行い、その上で新しい配列を作っています。とりあえず、これで動きます。  
[そしてStackblitzのサンプルです](https://stackblitz.com/edit/reducer-not-pure-anti-pattern-tuenning-successed)。  
  
なお、この場合はこれでうまく動きましたが、さらに複雑なインスタンス、オブジェクト、配列ではこれだけでは解消しないこともあるでしょう。ただし、そのような複雑なデータ構造はそもそも設計が悪い可能性があります。  

# Conclution
__**KEEP PURE FUNCTION**, **SIMPLE DATA STRUCTURE**__!

## 余談
これまでのすべてのStackblitzサンプルには`redux-logger`が導入されています。特に、副作用が起きている時の[1個め](https://stackblitz.com/edit/reducer-not-pure-anti-pattern-tuenning)と[2個め](https://stackblitz.com/edit/reducer-not-pure-anti-pattern-tuenning)で開発ツールを開き、実際にプログラムを動かして、Donald Trumpの二つ名を追加してやりましょう。  
何度か試しているうちに、loggerがとても興味深い挙動を記録していることがわかってきます。  

![](https://thepracticaldev.s3.amazonaws.com/i/bhstbm3hn4te0xaymdhb.png)

なんと`prev state`と`next state`が同一のものになっています。それだけではありません。  
  
![](https://thepracticaldev.s3.amazonaws.com/i/wey83ohpegx13v72yqpj.png)

過去の出力までもが改変されています――とても興味深く、面白い話ですが、この現象を解説するには私は`redux`と`redux-logger`の実装に精通していません。誰かこの解説記事を書いてください。私はこれが限界です。