React×Reduxで初心者がカウンターを作るまで

# 概要

- 最近ReactとReduxの組み合わせを耳にすることがあったので簡単なWebアプリを作ってみようと思いました
- 僕自身ReactもReduxも初心者のため、何もわからないところからstep by stepで動くものが完成するまでを書いてみようと思います
- 正確さよりもReactやReduxがどんな感じか理解しやすいように記述しようと思います
- 公式ではNodejsを使用していますが、ReactとReduxに焦点を絞るためにNodejsは使用しないで進めます

# 使用ライブラリ

使用するライブラリと投稿時点のバージョンです
※ReactでJSXを使用するためbabelが必要です

| library | version |
| ------- | ------- |
| react       | 4.0.5
| redux       | 16.13.1
| react-redux | 7.2.1
| babel       | 7.11.6

# 1. 準備

## ReactとReduxの役割

大雑把な役割は以下の通りです

- React: UIの操作
- Redux: データの管理

Reactだけでデータの管理もできるのですが、Reduxを併用することでデータの管理を楽にしようという思想（のはず）です
※詳細はこの後触れます

ということでReactだけでアプリは作れるので、
まずはReactだけでアプリを作ってみます。
そのあとReduxを使用してアプリをリファクタリングしていきます

## 作成するアプリ

ボタンをクリックするとカウントアップしていくカウンターを作成してみます

![counter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/0b586058-d503-c6f9-103a-f09be40e5609.png)

## ベースのHTML

以下のHTMLをベースに実装していきます

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">

    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
    
    <script src="https://unpkg.com/react@latest/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
    <!--<script src="https://unpkg.com/react@latest/umd/react.production.min.js"></script>-->
    <!--<script src="https://unpkg.com/react-dom@latest/umd/react-dom.production.min.js"></script>-->

    <script src="https://unpkg.com/react-redux@7.2.1/dist/react-redux.js"></script>

    <!--JSXのブラウザでの単独実行のために必要-->
    <script src="https://unpkg.com/babel-standalone@latest/babel.min.js"></script>

    <title>Counter</title>
</head>
<body>

<h1>Counter</h1>
<div id="app"></div>

<script type="text/babel">
    // 以降、コードはこの部分のみを編集していきます
</script>
</body>
</html>
```

- Reactのライブラリは`development`と`production`の２つがあり、開発中は`development`の方を使用するとエラーがより詳細に表示されるためやりやすいと思います
- scriptを記述していくことで`<div id="app"></div>`の部分にコンポーネントがレンダリングされます
- scriptタグには`type="text/babel"`が必要です（なぜかはこのあと）

# 2. Reactで実装してみる

## 2-1. Hello World

まずはReactを使用してHello Worldを表示してみます

```javascript
ReactDOM.render(    // ①
    <span>Hello World</span>,  // ②
    document.getElementById('app')  // ③
)
```

ポイント

- レンダリングは`ReactDOM.render`を実行することで実施されます（①）
- renderの第一引数でHTMLを指定します（②）
    - ここがJSXとよばれる記法（？）で以下の２つが必要です
    - babelを使用すること（他のライブラリでも可）
    - scriptタグに`type="text/babel`を指定すること
- renderの第二引数でレンダリング対象のElementを指定します（③）
    - ここでは`id="app"`が指定されたElementを指定しています

## 2-2. Reactのコンポーネントを使ってみる

ReactではUIの各要素をコンポーネントと呼ばれる単位で定義していきます。
先ほどのHello Worldを表示するHTMLをコンポーネントに定義しなおしてみます

```javascript
<script type="text/babel">
class CounterApp extends React.Component { // ①
    render() { // ②
        return (
            <span>Hello World</span>
        )
    }
}

ReactDOM.render(
    <CounterApp/>,  // ③
    document.getElementById('app')
)
</script>
```

ポイント

- コンポーネントは`React.Component`を実装したクラスにより定義します（①）
- コンポーネントには「引数なし」で「HTMLを返す」`render`メソッドを実装します（②）
- 作成したコンポーネントはHTMLタグのように用いることができます（③）
    - `render`で返却されたHTMLが埋め込まれるイメージです

## 2-3. 変数を埋め込んでみる

ここで完成形のUIとなるように修正を加えます。
同時にUIでボタンを押すごとに更新されるカウンタ情報について変数であつかってみます

```javascript
<script type="text/babel">
class CounterApp extends React.Component {
    constructor(props) {  // ①
        super(props)      // ②
        this.state = {    // ③
            count: 0
        }
    }

    render() {
        return (
            <button>{this.state.count}</button>  // ④
        )
    }
}

ReactDOM.render(
    <CounterApp/>,
    document.getElementById('app')
)
</script>
```

ポイント

- 変数を扱うために`state`というフィールドを使用する（③）
- `state`の初期化は`props`を引数に取るコンストラクタ内で定義する（①）
- コンストラクタでは必ず`super`で`props`を引き渡す（②）
- 変数は`{}`で囲ってあげることで展開される（④）

## 2-4. クリック時の動作を追加する

クリックしたときにボタンに表示されている数字が１ずつカウントアップするようにしてみます。
コンポーネントにカウントアップ用の関数`countUp`を追加し、buttonタグにonClickを追加します。

```javascript
<script type="text/babel">
class CounterApp extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
        this.countUp = this.countUp.bind(this)  // ①
    }
    
    countUp() {
        let count = this.state.count + 1   // ②
        this.setState({                    // ③
            count
        })
    }

    render() {
        return (
            <button onClick={this.countUp}>{this.state.count}</button>  // ④
        )
    }
}

ReactDOM.render(
    <CounterApp/>,
    document.getElementById('app')
)
</script>
```

ポイント

- `state`の更新は`this.setState`により行う（③）
- `setState`の中で`this.state`は行わず、外で実施（②）
    - `setState`が非同期でstateを更新するためのようです（詳細まで理解できてません…）
    - どうしてもな場合は`this.setState(state => ({count: state.count + 1}))`というような記述も可能です（が複雑なのでここでは扱いません）
- 独自の関数（`countUp`）で`this.state`や`this.setState`を使用する場合はコンストラクタ内でbindしておく（①）
    - ここはあまりJavascriptに詳しくないのでおまじないだと思っています
- onClick時の動作を`onClick={関数名}`というように指定します（④）
    - （関数も変数も同じように引き渡せるんですね）

ここまででいったん目標としていたWebアプリは完成しました！！！
ここからいろいろリファクタリングしていきます

## 2-5. コンポーネントの分離

ここはReduxを使用することを想定したリファクタリングになります
React×Reduxを使用するにあたって、コンポーネントは以下の２つに分類します。

- `Presentational Components` ※以後コンポーネント
    - UIに関わることに特化したコンポーネント
    - propsによってデータ（count）や関数（countUp）を受け取る
    - 基本的には関数で記述する（※このあと） 
- `Container Components`　※以後コンテナ
    - 動作やデータを他のコンポーネントに提供するコンポーネント

ということで先ほどの`CounterApp`をコンテナとし、その中からUIに関することを`Counter`コンポーネントとして分離してみます

```javascript
<script type="text/babel">
class Counter extends React.Component {
    render() {
        return (
            <button onClick={this.props.handleClick}>{this.props.count}</button>
        )
    }
}

class CounterApp extends React.Component {
    ...

    render() {
        return (
            <Counter handleClick={this.countUp} count={this.state.count} />
        )
    }
}
...
```

ポイント

- コンテナ`CounterApp`はコンポーネント`Counter`に関数や変数を渡すためにHTMLでいう属性を使用します
- コンポーネント`Counter`では関数や変数を`props`から受け取ります
    - propsはconstructorで登場したやつです

さらに、Counterを関数型に変更してみます
propsを引数にとり、HTMLを返す関数を定義することでクラスを定義せずともコンポーネントを記述可能です（先ほども記載した通りこちらが推奨です）

```javascript
//class Counter extends React.Component {
//    render() {
//        return (
//            <button onClick={this.props.handleClick}>{this.props.count}</button>
//        )
//    }
//}
function Counter(props) {
    return <button onClick={props.handleClick}>{props.count}</button>
}
```

注意点としては、propsがクラスのフィールドからメソッドの引数に変わったため、`this`が不要になります
またアロー関数を使うことでさらに記述を簡略化することができます

```javascript
const Counter = ({handleClick, count}) => <button onClick={handleClick}>{count}</button>
```

いったんここまでで以下のようのscriptとなりました

```javascript
<script type="text/babel">
const Counter = ({handleClick, count}) => <button onClick={handleClick}>{count}</button>

class CounterApp extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
        this.countUp = this.countUp.bind(this)
    }

    countUp() {
        let count = this.state.count + 1
        this.setState({
            count
        })
    }

    render() {
        return (
            <Counter handleClick={this.countUp} count={this.state.count} />  // ④
        )
    }
}

ReactDOM.render(
    <CounterApp/>,
    document.getElementById('app')
)
</script>
```

# 3. なぜRedux？

いったんReactのみで実装してみましたが、Reactのみで十分な実装ができているように思えます。
しかし、より複雑なUIを実装しようとすると課題が出てきます。
それはコンポーネントからコンポーネントを何層も呼ぶ場合で、
以下のような実装になりえます。

```javascript
const Container1 = ({a}) => <Container2 b={a.b}/>
const Container2 = ({b}) => <Container3 b={b.c}/>
const Container3 = ({c}) => <Container4 b={c.d}/>
...
```

このようにpropsで子コンポーネントに変数を引き渡していくと

- どこでどんな値が引き渡されているのか追いにくい
- そもそも毎回propsで引き渡すのがめんどう

という課題がでてきます。
そこで、データを一元管理して、
データが必要な場合はその一元管理されたものから取得できるようにしたものがRedux（のはず）です。
なので冒頭でも述べた通り、Reduxはデータの管理に特化したものです。

# 4. Reduxの仕組み

Reduxでは以下の３つの要素を実装していきます

| 要素 | 説明 |
| --- | --- |
| action | UIからの情報をstoreに伝える役割<br> ※動作自体は定義しない<br> action creatorによって生成
| reducer | actionを元にstateを変更する役割<br> ※正確には古いstateをもとに新しいstateを生成
| store | データ（state）を一元管理する役割

シーケンスで書くとこんな感じです
※あくまで大雑把な理解のためのものです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/69c60e27-a434-c2da-f7d2-82341fe2b989.png)

storeはreducerから自動で生成するため、actionとreducerを実装していきます

# 5. Reduxを組み込む

## 5-1. actionを実装

actionはUIでのイベントをpropertyに定義したシンプルなオブジェクトです。
`type`が必須で関数を持ちません

`type`はreducerでstateを更新する際にどのような処理を行うのか判断するために使用します。

今回は、`type`に加え`incremental`という増分を表すpropertyも定義してみます。
actionは任意引数の関数の返り値として定義します。
※この関数を総称してaction creatorと呼ぶそうです

```javascript
const COUNT_UP = 'count up'

// action creator
function countUp(incremental) {
    return {
        type: COUNT_UP,
        incremental
    }
}
```

> `incremental = 1`のとき`{incremental}`と記載すると`{incremental: 1}`と同等ということを今回知りました

actionはUIで起こったイベントに対して１対１で定義していきます

## 5-2. Reducerを実装

reducerは「古いstate」と「action」を受け取り、「新しいstate」を生成する役割を持ちます。
ドキュメントで繰り返し書かれていたので念押しですが、
「stateの更新ではなく、古いstateから新しいstateを生成する」必要があります。

stateの更新時にはObject.assignという関数を使用して、stateをコピーしてから操作します

```javascript:使い方サンプル
// 空のプロパティにstateの内容をコピーする
Objct.assign({}, state)
// 空のプロパティにstateの内容をコピーした後にcountプロパティの値を1に更新する
Objct.assign({}, state, {count: 1})
```

reducerの実装はstateとactionを引数に、stateを返す関数を定義します。

```javascript
const initialState = {
    count: 0
}

// reducer
function counterApp(state = initialState, action) {
    switch (action.type) {
        case COUNT_UP:
            return Object.assign({}, state, {
                count: state.count + action.incremental
            })
        default:
            return state
    }
}
```

ポイント

- 先ほどactionで説明した通り、actionには`type`プロパティが存在するため、`type`によって処理を分岐させます。
- 定義していない`type`の場合には引数で受け取った`state`をそのまま返します
- `counterApp(state = initialState)`という書き方をしておくことで`state`が未定義の場合に`initialState`で置き換えてくれます

今回は１つだけreducerを作成しましたが、複数のreducerを定義して親のreducerの子として使用することも可能です。


## 5-3. storeを生成

reducerを実装できたところでstoreを生成します。
先ほど実装したreducerを引数に`Redux.createStore`を実行するだけです！

```javascript
const store = Redux.createStore(counterApp)
```

１つ注意点をあげるとするとstoreはデータの一元管理を目的としているため、１つのアプリに対して１つのみ生成することが基本です。

storeの関数は以下の３つを把握しておけば基礎は大丈夫かと思います

| 関数 | サンプル | 説明 |
| --- | --- | --- |
| getState  | getState() | stateの取得
| dispatch  | dispatch(actionCreator()) | actionの送信（stateの更新）
| subscribe | subscribe(() => console.log('dispatched')) | listenerの登録

## 5-4. Reactアプリに組み込む！

Reduxの準備がこれで整ったので、Reactのみで実装したアプリに組み込んでいきます

まず、`CounterApp`のstateについてstoreから取得するようにします。
また、dispatchで更新されたstateを反映するためにlistenerを登録しておきます。

```javascript
class CounterApp extends React.Component {
    constructor(props) {
        super(props)
        this.state = store.getState()
        store.subscribe(() => this.setState(store.getState()))
        this.countUp = this.countUp.bind(this)
    }
...
}
```

次にもともと定義してあった`countUp`関数については`store.dispatch`で置き換えます。

```javascript
class CounterApp extends React.Component {
    constructor(props) {
        super(props)
        this.state = store.getState()
        store.subscribe(() => this.setState(store.getState()))
    }

    render() {
        return (
            <Counter handleClick={(incremental) => store.dispatch(countUp(incremental))} count={this.state.count} />
        )
    }
}
```

最後に`Counter`コンポーネントからincremental=1を渡すように修正してみます

```javascript
const Counter = ({handleClick, count}) => <button onClick={() => handleClick(1)}>{count}</button>
```

ここまででscript全体はこのようになりました！

```javascript
<script type="text/babel">

const COUNT_UP = 'count up'

// action creator
function countUp(incremental) {
    return {
        type: COUNT_UP,
        incremental
    }
}

const initialState = {
    count: 0
}

// reducer
function counterApp(state = initialState, action) {
    switch (action.type) {
        case COUNT_UP:
            return Object.assign({}, state, {
                count: state.count + action.incremental
            })
        default:
            return state
    }
}

// storeの生成
const store = Redux.createStore(counterApp)

const Counter = ({handleClick, count}) => <button onClick={() => handleClick(1)}>{count}</button>

class CounterApp extends React.Component {
    constructor(props) {
        super(props)
        this.state = store.getState()
        store.subscribe(() => this.setState(store.getState()))
    }

    render() {
        return (
            <Counter handleClick={(incremental => store.dispatch(countUp(incremental)))} count={this.state.count} />
        )
    }
}

ReactDOM.render(
    <CounterApp/>,
    document.getElementById('app')
)
</script>
```

## 5-5. react-reduxでコンテナ生成を自動化

react-reduxの`connect`を使用すると`CounterApp`の実装を省略することができます。
また、実装内容もより合理的なものになるため`connect`の利用が推奨されています。

ということで最後の最後に`connect`を使用した形に`CounterApp`を書き換えていきます。

改めて`CounterApp`を確認すると

```javascript
class CounterApp extends React.Component {
    constructor(props) {
        super(props)
        this.state = store.getState()
        store.subscribe(() => this.setState(store.getState()))
    }

    render() {
        return (
            <Counter handleClick={(incremental => store.dispatch(countUp(incremental)))} count={this.state.count} />
        )
    }
}
```

- コンポーネントは何か？
    - `Counter`
- stateをどのようにpropsに変換するか？
    - `count={this.state.count}`
- dispatchをどのように実行するか？
    - `handleClick={(incremental => store.dispatch(countUp(incremental)))}`

上記の３つが動的に変わる部分になりそうです。

そのため、`connect`を使用するにあたって、「stateをどうやってコンポーネントに伝えるか？」と「dispatchをどのように実行するか？」を関数として定義していきます。

それぞれ、`mapStateToProps`、`mapDispatchToProps`という関数で実装していきます。

```javascript
// stateをどうやってコンポーネントに伝えるか？
const mapStateToProps = state => ({
    count: state.count
})
// dispatchをどのように実行するか？
const mapDispatchToProps = dispatch => ({
    handleClick: incremental => store.dispatch(countUp(incremental))
})
```

そして、以下のように`connect`を実行すると自動で`CounterApp`が生成されます。

```javascript
const CounterApp = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Counter)
```

最後にひさびさにレンダリングの部分を変更します

```javascript
const Provider = ReactRedux.Provider;
ReactDOM.render(
    <Provider store={store}>
        <CounterApp/>
    </Provider>,
    document.getElementById('app')
)
```

ポイント

- `Providor`の子要素として`CounterApp`を記述する
- `Providor`にはstoreを引き渡す

これで完了！！！

# 6. 完成コード

ReactとReduxを使って最終的に以下のようなコードでカウンターが完成しました！！

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">

    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>

    <script src="https://unpkg.com/react@latest/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
    <!--<script src="https://unpkg.com/react@latest/umd/react.production.min.js"></script>-->
    <!--<script src="https://unpkg.com/react-dom@latest/umd/react-dom.production.min.js"></script>-->

    <script src="https://unpkg.com/react-redux@7.2.1/dist/react-redux.js"></script>

    <!--JSXのブラウザでの単独実行のために必要-->
    <script src="https://unpkg.com/babel-standalone@latest/babel.min.js"></script>

    <title>Counter</title>
</head>
<body>

<h1>Counter</h1>
<div id="app"></div>

<script type="text/babel">

const COUNT_UP = 'count up'

// action creator
function countUp(incremental) {
    return {
        type: COUNT_UP,
        incremental
    }
}

const initialState = {
    count: 0
}

// reducer
function counterApp(state = initialState, action) {
    switch (action.type) {
        case COUNT_UP:
            return Object.assign({}, state, {
                count: state.count + action.incremental
            })
        default:
            return state
    }
}

// storeの生成
const store = Redux.createStore(counterApp)

const Counter = ({handleClick, count}) => <button onClick={() => handleClick(1)}>{count}</button>

// stateをどうやってコンポーネントに伝えるか？
const mapStateToProps = state => ({
    count: state.count
})
// dispatchをどのように実行するか？
const mapDispatchToProps = dispatch => ({
    handleClick: incremental => store.dispatch(countUp(incremental))
})

const CounterApp = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(Counter)

const Provider = ReactRedux.Provider;

ReactDOM.render(
    <Provider store={store}>
        <CounterApp/>
    </Provider>,
    document.getElementById('app')
)
</script>
</body>
</html>
```

# 7. 次は

次はToDoListを作ってみたいなーと思っています。
むしろもともとはこの記事でToDoListを作る予定だったのですが、

- jsを外部ファイルに適切に分離していかないと見通しが悪そう
- 外部ファイルに分離するとJSX周りでうまく動かない

ということで断念しました。
webpackを使用してコードを自動生成すればできるような記事も見つけたのですが、
npmを使っていろいろした方がよさそうな印象を受けました。

なのでnpmを使って（ということはNodejsベースで？）ToDoListを作る記事も書けたらなと思います。