Reduxストアの概念をRubyで再実装して理解する(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社エンジニアの「?」を「!」に。原著者の許諾を得て翻訳・公開いたします。Strzibnyなお、reducerは「レデューサー」「リデューサー」の2とおりのカタカナがあるようですが、ネットで多数派の「レデューサー」にしました。Reduxストアの概念をRubyで再実装して理解する(翻訳)Reduxは最近のJavaScriptアプリのステート管理ライブラリとして人気が高まっており、実際にはその中心にシンプルなデータストアがあるだけです。Reduxのストアは他と何が違うのでしょうか?データを保存する点では他のストアと同様ですが、Reduxのデータ変更は常に「アクション」を「レデューサー」と呼ばれるものにディスパッチすることで行い、サブスクライブしているリスナーに通知します。Reduxストアの話をするときには、「レデューサー」「アクション」「リスナー」の概念を理解しておく必要があります。レデューサー(reducer)はRedux世界でよく知られた名前であり、ステートとアクションを引数として受け取り、新しいステートを返す純粋関数を指します。ある関数が「純粋」である場合、関数に副作用がないことを意味します。純粋関数は常に新しい値を返し、受け取った値(関数に渡した元のステート)を変更することは決してありません。例として使われるカウンタレデューサーを見てみることにしましょう。カウンタレデューサーは、カウントアップ(increment)やカウントダウン(decrement)に応答する方法を知っており、それ以外のデータを追加しません(Gist)。end}上のRubyコードがJavaScript版と唯一異なっている点は、関数定義でlambda(無名関数)を使っていることと、アクションの定義にRubyのハッシュ(JavaScriptのオブジェクトと対になります)を用いている点です。ステートは常に引数として渡されますが、デフォルト値(ゼロからカウントアップする)も与えています。これで新しいカウンタレデューサは、カウントアップとカウントダウンのアクションに応答し、カウンタの適切な新しい値を返すようになります。ここでもうひとつ注目したい重要な点は、カウンタレデューサーは指定のアクションへの応答方法については何の知識も持たず、元のステートを無変更のまま返していることです。ここが後で重要になってきます。新しいカウンタは次のように使います。-1レデューサーを見たことのない人には上のカウンタの例が冗長に見え、アクションがハッシュで定義されている理由が謎に思えるかもしれません。アクションはこれよりもっと複雑になることもあり、他にも情報を伝える必要が生じる可能性もあります。アクションを定義する追加情報はハッシュ内で他のキーの下に保存されます。Reduxストアのアクションは実際には1個のオブジェクトなのです(技術的に言うと上のRubyコードではハッシュになっています)。レデューサーとアクションを理解できたので、今度はリスナーを定義してみましょう。リスナーは、内部に保持しているのが単なる関数である点を除けばまさしく想像どおりです。リスナー関数は「純粋」である必要はなく、単にストア内でステートが変更されたときの挙動を定義します。以下の典型的なリスナーもlambdaで定義してみました。}ここでは文字列を標準出力に出力しているだけですが、元々のReduxのユースケースではおそらくDOMを更新することになるでしょう。基本的な知識を一通り押さえることができ、カウンタレデューサーとリスナーもできあがったので、基本的なReduxStoreをRubyベースで実装する準備が整いました(Gist)。endend新しいReduxStoreは、作成時に渡される任意のレデューサーを使って動作する、一般的なストアです。初期化プロセスの部分は、基本的にはJavaScriptのcreateStore()に相当します。createStore(reducer)createStore()は、渡されたレデューサーに応じたストアを作成して返す関数です。1つのストアで使えるレデューサーは常に1つだけなので、カウンタレデューサーは自分のステートをカウンターのストアに保存します。アクションがディスパッチされると、サブスクライブしているすべてのリスナーに通知(呼び出し)されます。以下は新しいストアの利用例です。'increment'})最初にカウンタストアを作成し、次に、ストアのcurrent_stateを変更するincrementアクションをディスパッチします。ステートが変更されたときに何らかの操作を実行するリスナーの使い方を見てみましょう。3わずか数行のコードでRedux的なストアが準備できました。Reduxストアについてまだ説明していなかったことがひとつあります。Reduxストアは本質的に「アプリの全ステートの保存に用いる」ものであり、1個のレデューサーから返される1個の値のためだけのものではありません。今度は複数の値を保存するってどういうこと?」ストアの挙動をまったく変更せずに、複数のレデューサーが渡すステートを1つのストアに同時に保存できます。そのためには、複数のレデューサーを1つのルート(app)レデューサーにまとめなければなりません。することで複数のストアを保存し、すべてのレデューサーが渡す値を表すツリー状の構造を更新します。先ほど、レデューサーは自分が扱えないアクションを受け取ったときには渡された値を無変更のまま返すと申し上げたのを覚えていますでしょうか?これがまさしく、1つのアクションをルートストアにディスパッチすることで、ツリーのアップデートすべき値だけをアップデートし、それ以外のすべての値を安全に保つ仕組みです。複数のレデューサーをまとめるクラスメソッドでReduxStoreを拡張してみましょう(Gist)。endendこれはReduxのcombineReducers()関数に対応するもので、別の関数を返します。返される関数は、実際にステートとアクションをパラメータとして受け取って新しいステートを再度返すレデューサーです。このときだけ、ツリー全体に渡って動作します(Rubyではネストしたハッシュ、JavaScriptではオブジェクトになります)。実際の動作を見るために、カウンタのレデューサーと組み合わせる別のレデューサーを定義してみましょう(Gist)。end}新しいtodos_reducerにはTODOの項目リストが保存されます。todoという追加パラメータを受け取ることで、アクションをオブジェクトの概念として利用していることにご注目ください。それでは2つのレデューサーを1つにまとめるroot_reducerを作成してみましょう。})レデューサーを再び1つにできたので、アプリのストアを作成できるようになりました。milk"]}ルートのレデューサーを作成したことで、1つのルートハッシュに2つのレデューサーのステートを両方とも保存できました。これが、複数のレデューサーを実装してアクションやリスナーとまとめることで、ツリー状の構造を持つ唯一のストアをビルドする方法です。私の記事が、Reduxストアとは一体何かについて関心のお持ちの方がReduxの背後のコンセプトを理解する助けになることを願っています。コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。enno.jpを運営。実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。Storageを使ってはいけない(翻訳)#Tags#会社紹介#自社サービス#勉強会#Rails#PHP#デザイン#実績#週刊RailsウオッチCONTACTお気軽にご相談ください。(翻訳)CONTACTTechRachoでは、パートナーシップをご検討いただける方からのご連絡をお待ちしております。お気軽にご意見・ご相談ください。