12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Redux で反逆を起こしてマトリックスを作る

Last updated at Posted at 2016-12-08

タイトルはあれですが riotとReduxの組み合わせのアプリの素振りをまとめました。
ということでこんなの作りました。

screen.gif

題材

普通のWebアプリでもいいかなぁって思ったのですがどうせならザクザク動くものがいいなぁと思って適当にサーフィンしていたら、Matrix 風のEffectを再現したスクリプトを見つけたのでそれをRedux , riot に落とし込んでみようかなぁっと考えました。1

元のスクリプト

元になるスクリプトは[こちら]
(http://thecodeplayer.com/walkthrough/matrix-rain-animation-html5-canvas-javascript)になります

ソースを抜粋しておきます

//drawing the characters
function draw()
{
    //省略
    //looping over drops
    for(var i = 0; i < drops.length; i++)
    {
        //a random chinese character to print
        var text = chinese[Math.floor(Math.random()*chinese.length)];
        ctx.fillText(text, i*font_size, drops[i]*font_size);
		
        if(drops[i]*font_size > c.height && Math.random() > 0.975)
        drops[i] = 0;	
        //incrementing Y coordinate
        drops[i]++;
    }
}

setInterval(draw, 33);

フォントサイズ分のカラムを用意してカラム分の配列にY座標を記録していきます。

setInterval 毎に座標を一つづつ更新していき下まで達した段階でランダムに座標をゼロに戻します。

仕様

  • 初期値: 0 - 9 までの文字列
  • 30秒毎にランダムな絵文字を混入させていく
  • 外部からのフォントサイズの変更
  • 外部からの文字列の混入

せっかくStateをRedux管理にするので出来心から外部から動的に変数を変えられるようにしてみました。

実装

setInterval の部分

元のスクリプトではdraw関数をそのままsetIntervalに登録しているとStateの管理をReduxに預けることができないのでまずはupdate用のアクションクリエータを登録するようにしました。

setInterval(draw, 33) => setInterval(あくしょんくりえーた, 33)

Storeに保持するものと保持しないもの

正直どこまでStateに保存すべきか悩みましたが描画に関するものは全てState管理するようにしました。

State管理にしたもの

  • 各カラムの座標
  • 各カラムの文字
  • fontSize
  • canvas のwidth, height
  • context

Stateから外したもの

  • バックグラウンドのスタイル

基本動的な変更を付け加えたい(今回の実装では残念ながら固定値でいいようなケースでも)項目は全てStateの管理にすることにしました。

ここまでくると draw関数は入力された値を元に描画するだけの関数となりかなりシンプルになります。

const draw = ({ctx, textArray, fontSize, fontColor, height, width, drops}) => {
      ctx.fillStyle = "rgba(0, 0, 0, 0.05)"
      ctx.fillRect(0, 0, width, height)

      ctx.fillStyle = fontColor
      ctx.font = fontSize + "px arial"

      for (let i = 0; i < drops.length; i++) {
          ctx.fillText(drops[i].text, i * fontSize, drops[i].pos * fontSize)
      }
}
const store = this.opts.store

store.subscribe(() => {
    draw(store.getState())
})

Storeの変更を検知してそのままdraw関数に流すだけとなり描画部分とデータ部分を切り離すことができました。

Randomな振る舞いをどう扱うか

Randomな座標や生成する文字列をどこで計算すべきかということです。

案1. Reducersの中で処理する

Reducers内では前回のStateを参照できるので、そこからrandomな文字列や座標を計算して新しいStateを返すという処理が比較的簡単に書けそうです。
しかしドキュメントによればReducersは純関数であることが期待されます

Randomという冪等制を持たない関数をreducer内で行うのは違うと思いやめました。

案2 ActionCreator内で処理する

ActionCreatorで処理して変数を Reducersに引き渡すのが良さそうです。
 ただActionCreator内でMath.randomを各々呼ぶとテストがしにくいと思ったので少し違う方法をとるようにしました。

案3 RNGを設定できるMiddlewareを自作する

テストのことを考えると外部からRandom-Number Generator(RNG)を渡せるような構造にしておきたいところです。
と考えると実装そしてはこんな感じになります

export const randomTest = createAction(TEST, (value, rng = Math.random()) => ...process...)

各アクションにランダムの引数入れるのイケテないなぁっと思って特定のActionの場合だけRNGを引き渡すようなMiddlewareを自作しました。
こんな風に使います

export const moveDrops = () =>
    randomAction((rng, state) =>
        rng()   <- この返り値が丸ごとpayloadになる
    )(updateDropsAction)

randomActionのActionを作成して引数にRNG, とStateを渡すようにしました。

RandomActionの第一引数の処理が終わった後に次のアクションであるupdaeDropsActionを叩くきます

ソースはこんな感じ

const RANDOM = 'RANDOM'

const randomColumns = (rng = Math.random.bind(this)) => store => next => action => {
    if(action.type === RANDOM && typeof action.fn === 'function'){
        const r = action.fn(rng, Object.assign({}, store.getState()))
        action.nextAction && store.dispatch(action.nextAction(r))
    }
    return next(action)
}

export const randomAction = fn => nextAction => ({
    type: RANDOM,
    fn,
    nextAction
})

export default randomColumns

こうすることでrngを外部から指定するということをあまり意識せずにランダムな関数を使えるようになります。

ちなみにこれ以外でもcomponentで頑張るってのもアリです。

Middlewareの作り方はReduxのドキュメントを参考にしました。
すごくわかりやすいです
http://redux.js.org/docs/advanced/Middleware.html

デモ

ソース

GitHub Pages へのデプロイ

そういえばgh-pagesに自動デプロイをしたことなかったのでここを参考にした
https://gist.github.com/domenic/ec8b0fc8ab45f39403dd
http://qiita.com/dora56/items/cafae475daec802b6b8f

断念したこと

Flow

riotの型の部分でサクッと導入できなかったので断念した。もうちょっと粘りたかったがタイムオーバー。

redux-loop

非同期な部分がなかったので導入しなかった。けど少し興味のあるライブラリ。

まとめ

今のコードだとStoreをどこからでも参照できるので若干危険な匂いがするコードになっています。その辺をうまく閉じ込めつつコンポーネントやActionCreaterで触らなくてもいいような感じにしたいところです。

react-reduxなんかはそこらへん上手くやっててStateを扱う部分は割と限定的にしつつ実装ができた気がします。

  1. ックスの部分がかかってたからというのが本音

12
8
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
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?