LoginSignup
4
4

More than 5 years have passed since last update.

React連携前にReduxにもっと親しむ

Posted at

はじめに

Redux はそんなに複雑でもないので、公式のドキュメントを見ると、ざっくり理解はできる。

手を動かして動作確認をするには、環境構築不要・1HTMLファイルで動作確認できる。保存してブラウザで開けば動作をする。

四つのボタン(+、-、奇数なら+、1秒後に+)があって、押すとカウンタの値が変化する。これを少しいじって、Reduxにより親しんでみよう。

Step#01: 改良点を見つける

これ以上ないほどシンプルなので、コードは読みやすい。しかし改良点がある。つまり、ボタンをクリックしたときに行われる処理がべた書きされているのだ。該当する箇所を示そう。

      document.getElementById('increment')
        .addEventListener('click', function () {
          store.dispatch({ type: 'INCREMENT' })
        })
      document.getElementById('decrement')
        .addEventListener('click', function () {
          store.dispatch({ type: 'DECREMENT' })
        })
      document.getElementById('incrementIfOdd')
        .addEventListener('click', function () {
          if (store.getState() % 2 !== 0) {
            store.dispatch({ type: 'INCREMENT' })
          }
        })
      document.getElementById('incrementAsync')
        .addEventListener('click', function () {
          setTimeout(function () {
            store.dispatch({ type: 'INCREMENT' })
          }, 1000)
        })

Redux では、Actionを作成するのはActionCreatorの役割だ。カウンターの例ではどのようなActionが作成されるかは自明だが、普通はロジックをこねくり回してどんなアクションが生成されるかが決まる。そこでAction Creatorを使って書き直す必要がありそうだ。上記のコードは、以下のように変更した方がより React+Redux っぽいだろう。

  document.getElementById('increment')
    .addEventListener('click', function () {
      props.increment()
    })
  document.getElementById('decrement')
    .addEventListener('click', function () {
      props.decrement()
    })
  document.getElementById('incrementIfOdd')
    .addEventListener('click', function () {
      props.incrementIfOdd(store.getState())
    })
  document.getElementById('incrementAsync')
    .addEventListener('click', function () {
      props.incrementAsync()
    })

ロジックがすべて外だしされてすっきりした。

Step#02: ActionCreator の実装

ActionCreatorはActionを返り値にとる以外はただの関数であり、Reduxとは関係がない。それを踏まえて実装しよう。

  // action creators
  const incrementCounter = () => {
    // insert your logic here
    return { type: 'INCREMENT' }
  }
  const decrementCounter = () => {
    return { type: 'DECREMENT' }
  }
  const incrementCounterIfOdd = (val) => {
    if (val % 2 !== 0) {
      return { type: 'INCREMENT' }
    } else {
      return null
    }
  }
  const incrementCounterAsync = () => {
    return new Promise((res,rej)=>{
      setTimeout(()=>{
        res({ type: 'INCREMENT' })
      },1000)
    })
  }

1秒後にカウンタ値をインクリメントするActionCreatorは、ActionがresolveされるPromiseを返すように設計した。

Step#03: ActionCreator を dispatchと連携

ActionCreatorは単純にActionを作るだけで、dispatchはしてくれない。どこかしらで生成したActionをdispatch() する必要がある。dispatchが与えられた時にActionCreatorとdispatchがどう連携するかを記述するのが、react-reduxでいうところの、mapDispatchToPropsだ。今回はreactもreact-reduxも使わないので、使用感を似せるためにこんな感じに propsを定義してみた。

  // emulate mapDispatchToProps
  const props = {
    increment: ()=>{
      store.dispatch(incrementCounter())
    },
    decrement: ()=>{
      store.dispatch(decrementCounter())
    },
    incrementIfOdd: (val) => {
      const action = incrementCounterIfOdd(val)
      if (action) {
        store.dispatch(action)
      }
    },
    incrementAsync: () => {
      incrementCounterAsync()
      .then((action)=>store.dispatch(action))
    }
  }

Step#04: まとめると

こんな感じになった。

<!DOCTYPE html>
<html>
<head>
  <title>Redux basic example</title>
  <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>

<body>
<div>
  <p>
    Clicked: <span id="value">0</span> times
    <button id="increment">+</button>
    <button id="decrement">-</button>
    <button id="incrementIfOdd">Increment if odd</button>
    <button id="incrementAsync">Increment async</button>
  </p>
</div>

<script>
  // create store
  function counter(state, action) {
    if (typeof state === 'undefined') {
      return 0
    }
    switch (action.type) {
      case 'INCREMENT':
        return state + 1
      case 'DECREMENT':
        return state - 1
      default:
        return state
    }
  }
  const store = Redux.createStore(counter)

  // subscribe to render on update
  const valueEl = document.getElementById('value')
  function render() {
    valueEl.innerHTML = store.getState().toString()
  }
  render()
  store.subscribe(render)

  // action creators
  const incrementCounter = () => {
    // insert your logic here
    return { type: 'INCREMENT' }
  }
  const decrementCounter = () => {
    return { type: 'DECREMENT' }
  }
  const incrementCounterIfOdd = (val) => {
    if (val % 2 !== 0) {
      return { type: 'INCREMENT' }
    } else {
      return null
    }
  }
  const incrementCounterAsync = () => {
    return new Promise((res,rej)=>{
      setTimeout(()=>{
        res({ type: 'INCREMENT' })
      },1000)
    })
  }

  // emulate connect(mapDispatchToProps)
  const props = {
    increment: ()=>{
      store.dispatch(incrementCounter())
    },
    decrement: ()=>{
      store.dispatch(decrementCounter())
    },
    incrementIfOdd: (val) => {
      const action = incrementCounterIfOdd(val)
      if (action) {
        store.dispatch(action)
      }
    },
    incrementAsync: () => {
      incrementCounterAsync()
      .then((action)=>store.dispatch(action))
    }
  }

  // add lister to buttons
  document.getElementById('increment')
    .addEventListener('click', function () {
      props.increment()
    })
  document.getElementById('decrement')
    .addEventListener('click', function () {
      props.decrement()
    })
  document.getElementById('incrementIfOdd')
    .addEventListener('click', function () {
      props.incrementIfOdd(store.getState())
    })
  document.getElementById('incrementAsync')
    .addEventListener('click', function () {
      props.incrementAsync()
    })
</script>
</body>
</html>

Step#05: 所感

うまくやれば、非同期処理があっても redux-saga いらなくない? mapDispatchToPropsに非同期処理をごちゃごちゃ書けばいいだけの気がする。

Step#06: もっといじりたい!

Reduxでは、たくさんのExampleがあって、それをブラウザ上で編集できる神機能がある。

上記にあるいくつかのExampleに「check out the sandbox.」という文字がある。ここをクリックすると、オンラインでのIDEが開き、その場でファイルの修正と修正後の動作確認ができる。Codesandbox.io というサービスを利用しているようだ。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4