Help us understand the problem. What is going on with this article?

React+Redux入門

※この記事を書いたのは数年前です。Qiitaでは記事をアップデートするとその日付のみが表示されていまうため、新しい記事のように見えるかもしれませんが、現代ではもっと進化していることにご注意ください。素直にReact Hooks を使いましょう。

最近のモダンなウェブフレームワークと言えば、React+Reduxですよね。でも、なんか難しそうとか、ReactってPHPみたいにViewにロジック混ざりそうとか感じて尻込みしていませんか?それはただの誤解かもしれません。React+Reduxはそんなに難易度の高いものではありません。ただ単に、新しい概念で構成されているから、カルチャーショックのようなものがある、というだけのことです。React+Reduxに入門してみましょう。

僕自身初心者なので、大いなる勘違いをしているかもしれませんので、是非識者の方は間違いなどがありましたら、突っ込みや補足などをいただけると幸いです。

さて、日本のWebエンジニアの大半が、変化に対応しきれなくなっている件について。 - 日々、とんは語る。っていうブログが話題になっていますが、実際のところはウェブフロントの世界を知らない人、特にJavaやPHPやってるような老害な人達、もしくは技術力がなくてもウェブフロントならやれると思い込んでた人達が騒いでるだけなのでは?という状況のようです。

JavaScriptにはむしろもっと抽象化がもたらされるべきという記事を書きました。合わせて読んでみてください。

ただ、Reactに限らずJS界隈は日本語の資料があまりよくないので、英語をあたるしかないケースがきわめて多いです。そういう意味では英語を読みたくない人にはJavaScriptは敷居が高いでしょう。

JavaScriptやるなら、必ず最初に公式を見て、次に公式を見て、最後にStackoverflowとQiitaを読みましょう。

ぶっちゃけ、ReactのTutorialとReduxのドキュメントをつまみ食いするだけで覚えられます。(それだけでOKな人は、以下を読む必要ないかもしれませんが)

React+Reduxのカルチャーショックポイント

  • Reactはただ与えられたプロパティを元に描画するだけのView
  • Reduxで状態管理を奪い取る事でReact部分を簡単にする
  • 状態遷移を元にプロパティを生み出して描画に反映させる

簡単に言えば、(分割統治もできる)一つの大きな状態遷移図があります。状態が遷移したらそれが勝手にViewに反映されます。状態遷移の管理と描画を切り分ける事ができるというものです。ウェブプログラミングもやっとまともになりました!

Reactとは何か

Reactとは、コンポーネントの考え方を推し進めた、新しい概念のちょっとかしこいViewライブラリです。Reactの伝道師mizchi氏のなぜ仮想DOMという概念が俺達の魂を震えさせるのか - Qiitaとか読んでみてください。

さっきも書きましたが、React+Reduxに限定すれば、Reactは与えられたプロパティを元に描画を組み立てるだけの簡単なお仕事です。ここではReactのコンポーネントについてフォーカスして説明していきます。

コンポーネント

ES2015ならコンポーネントは単に React.Component を継承したクラスに過ぎません。JSXという少し気持ちの悪いJavaScriptの拡張は、JS上のコンポーネントクラスと、描画上のコンポーネントを一致させるためのアイデアにすぎません。つまりPHPのようなViewにロジックを混ぜ込ませる為のものとは考え方そのものが違います。

コンポーネントは階層構造になっています。

  • トップコンテナ(ただのidを振られたdiv要素)
    • App コンポーネント
      • Hoge パラメータ: 1
      • Hoge パラメータ: 2
      • Hoge パラメータ: 3
import React from 'react'
import { render } from 'react-dom'
import App from './app'

render(<App />, document.getElementById('root'))

このようなコードで、rootというIDを持つdivコンテナにAppを流し込みます。これは同じディレクトリにあるapp.jsxというファイルでexportされているAppクラスです。ここで注意したいのは、たとえば<App />のように書いても特にエラーも何もでず、描画がされないだけだというトラップです。

app.jsx
import React from 'react'
import Hoge from './hoge'

export default class App extends React.Component {
  render() {
    return <div>
      <Hoge param=1 />
      <Hoge param=2 />
      <Hoge param=3 />
    </div>
  }
}

AppではReact.Componentを継承して、renderメソッドを実装します。コンポーネントは使い回すためにパラメータを渡します。Hogeというコンポーネントをparam=1,2,3をそれぞれセットして呼び出します。

hoge.jsx
import React from 'react'

export default class Hoge extends React.Component {
  render() {
    return <div>ほげ{this.props.param}</div>
  }
}

Appと同様ですが{this.props.param}という新しいものが出てきました。this.propsは、パラメータとして渡された全プロパティが入っています。Appでparam=1などが渡されている為、this.props.paramとしてアクセスができます。

さて、React+Reduxでは、ここまで覚えればもう大丈夫です。React公式を含めてReactを説明した文章ではpropsだけではなくて、stateというものも扱われるのですが、Reduxを併用する限りいったん忘れてかまいません。(別のところで別のstateが出てくるけど)。Reactは単に与えられたpropsを元にViewを作るだけのものです。簡単ですよね!!

React+Redux

ReduxはReactから状態管理を奪い取り、ある一箇所で状態遷移を関数型言語ちっくにいじろうっていうものです。

詳しくはRedux入門【ダイジェスト版】10分で理解するReduxの基礎 - QiitaRedux入門 6日目 ReduxとReactの連携(公式ドキュメント和訳) - Qiitaを読めば大体わかると思います。

それを踏まえて、React+Reduxがどういうものかまとめてみます。ReduxやFluxアーキテクチャと呼ばれるものは、データを一方通行にする事で簡易化するという考え方があります。初期状態から順に追いかけますが、その前にいったん、ReactとReduxをつなぐ方法を見ていきましょう。

ReactとReduxをつなぐ

react-reduxというモジュールを使います。

import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'

import App from './container/app'
import reducer from './reducer'

const store = createStore(reducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

初期状態

Reduxでは状態遷移をreducerというもので管理します。

reducer.jsx
const initialState = {
  fuga: 1
}

export default function reducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT':
    ...
    default:
      return state
  }
}

reducerでは、action.typeに応じたアクションを元に状態遷移を行うのですがいったん後回しにします。default: return stateがキモで、一番最初、initialStateがセットされるような呼び出し方がされます。それによって、state が initialState である { fuga: 1 } になります。

container

containerはReduxのStoreが管理する状態遷移をReactのプロパティとして流し込む役割を持ちます。正確にはもう一つdispatchという重要な役割があるのですがいったん後回しにします。

container/app.jsx
import React from 'react'
import { connect } from 'react-redux'

import App from '../component/app'

function mapStateToProps(state) {
  return state
}

export default connect(mapStateToProps)(App)

connectは、ReduxとReactのコンポーネントを繋ぎ込む為のメソッドです。mapStateToPropsは、一枚岩のでっかいstateの中から、対象のコンポーネントに合ったプロパティを生成する為のものです。

実のところ、これは説明としてはへたくそです。mapStateToPropsで何もやってないので。よくあるパターンはstate自体が複雑な時に必要なステートを取り出して、プロパティに変換するようなコードです。

初期化直後であれば先ほどのreducerでの初期化によってstateのfugaは1になっているはずです。なので、Appコンポーネントにはprops.fugaとして1が渡ります。後は、propsを使って描画をするだけです。これはさきほどReactの説明でした通りなので省きます。

ユーザーアクションを受け付ける方法

さきほどcontainerで説明を省略したdispatchの扱いをしてみましょう。

container/app.jsx
import React from 'react'
import { connect } from 'react-redux'

import App from '../component/app'
import { increment } from '../action/app'

function mapStateToProps(state) {
  return state
}

function mapDispatchToProps(dispatch) {
  return {
    handleClick: () => { dispatch(increment()) }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

mapDispatchToPropsは、dispatch関数を受け取ってプロパティに変換します。dispatch関数はstoreにアクションを流し込む為のもので、たとえば、上記の例だとhandleClickというプロパティは引数なしの関数で実行するとincrement()した戻り値をdispatchします。

increment()はActionCreatorと呼ばれるものです。

action/app.jsx
export default {
  increment: () => {
    return { type: 'INCREMENT' }
  }
}

Actionとは、どういうアクションを行うか?というtypeと、他の任意のパラメータを持ったプレーンなオブジェクトです。

最後にReactで繋ぎ込みましょう。

component/app.jsx
import React from 'react'

export default class App extends React.Component {
  render() {
    return <div>
      <span>{this.props.fuga}</span>
      <button onClick={ () => this.props.handleClick() }>増加</button>
    </div>
  }
}

おさらいをしましょう。

  1. containerでは mapDispatchToPropsで、increment (ActionCreator) を呼び出して { type: 'INCREMENT' }をdispatchする関数を生成してhandleClickという名前のプロパティとして返す
  2. componentでは <button onClick={ () => this.props.handleClick() }>増加</button>でbuttonのonClickイベントによって、プロパティのhandleClick()を呼び出す

状態遷移する

最後に、reducerでactionを元に状態遷移をします。

reducer.jsx
const initialState = {
  fuga: 1
}

export default function reducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT': {
      return { fuga: state.fuga + 1 }
    }
    default:
      return state
  }
}

ただ、これだけです。action.type: INCREMENTをうけつけると、stateのfugaを1増加するだけです。注意点は元のstateを変更してはいけないことです。変化した後の値を返すだけで、元の値に手を加えてはいけません。(個人的にはこの制約納得いかない。)

終わりに

Electron + React + Redux でポモドーロタイマーを作ってみました。

erukiti
React/TypeScript/VSCodeやメタプロが好きです。元々バックエンドエンジニア経験および、UNIX/Win32ネイティブアプリ開発経験の方がフロントエンド経験より遙かに長いので、フロントエンド初心者です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした