オブジェクトリテラル内でのスプレッド演算子
こんにちは。
スプレッド演算子(spread operator)がオブジェクトリテラル内で使えませんでした。babel-preset-es2015
入れとけばなんとかなるだろうと思っていたのですが、babel-preset-stage-2
が必要みたいです。ECMAScriptの仕様策定方法を知らないのでstage-2
の意味はわからないですが、なんかよさそうなものがあったので読もうと思います。
ざっくりとした設定方法をメモしておきます。
インストール
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-2 webpack
webpack.config.js
はこんな感じになりました。
module.exports = { entry: './index.js', output: { path: __dirname, filename: 'bundle.js', }, module: { loaders: [ { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel', query: { presets: [ 'es2015', 'stage-2', ], }, }, ], }, }
以下動くかどうか確認のためindex.js
とindex.html
を用意。
index.js
let obj1 = { 'before': '...oh' }; alert(JSON.stringify(obj1,null,2)); let obj2 = { ...obj1 , 'after': 'oh!' }; alert(JSON.stringify(obj2,null,2)); // ひどいサンプル
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Spread</title> </head> <body> <div id="root"></div> <script type="text/javascript" src="./bundle.js"></script> </body> </html>
webpack
でビルド実行
./node_modules/.bin/webpack --colors --progress
index.html
をブラウザで開くとアラートが2回表示されます。オブジェクトリテラルが再代入なしで更新されているのが、なんとなくわかるかと思います。もっとプロパティが多いと、魅力が伝わる気がします。
感想
ちゃんとした例を書かないとダメですね。あとECMAScriptに関しても知っておきたいです。
Ctags と vim と Git
概要
vimでタグジャンプを楽にする方法です。以下適当な翻訳です。
Ctags はコードのインデックスを作成し, 関数, 変数, クラス, その他の識別子への Vim でのジャンプを容易にします. Gitのフックはリポジトリ単位です(Git hooks). あるリポジトリに対して, 指定されたフックをインストールするスクリプトを使用するのが推奨されていますが, 手作業で面倒です. テンプレを作成して楽しましょう.
テンプレートのフック作成
git全体で使用するテンプレートディレクトリを設定, 作成します.
git config --global init.templatedir '~/.git_template' mkdir -p ~/.git_template/hooks
ctagsファイルを作成します.
vim .git_template/hooks/ctags
#!/bin/sh set -e PATH="/usr/local/bin:$PATH" dir="`git rev-parse --git-dir`" trap 'rm -f "$dir/$$.tags"' EXIT git ls-files | \ ctags --tag-relative -L - -f"$dir/$$.tags" --languages=-javascript,sql mv "$dir/$$.tags" "$dir/tags"
上の内容は,
set -e
: スクリプト内の各コマンドの実行でエラーが出たら処理を中止するPATH..
: 環境変数に/usr/local/bin
を追加dir...
: gitリポジトリのパスをdir変数に格納trap..
: EXITシグナルを検知したら, 古いタグ.git/tags
を削除する?git...
: git管理下のファイルに対しctagsを生成, jsとsqlは除くmv ...
: 生成されたタグをtagsディレクトリに移動
という感じです.
各フックファイルを作成します。pre-commit
は以下です。
#!/bin/sh .git/hooks/ctags >/dev/null 2>&1 &
post-checkout
, post-merge
も内容は一緒です.
最後に rebase
, commit --amend
の対応として post-rewrite
を作成します.
#!/bin/sh case "$1" in rebase) exec .git/hooks/post-merge ;; esac
これでいけるはずです。
結局5つのファイルを作成しました。
/Users/ushumpei/.git_template |--hooks | |--ctags | |--post-checkout | |--post-commit | |--post-merge | |--post-rewrite
感想
Ctagsについて昔書いた文章が見つかったので載せてみました。
trap
とgit rev-parse
がわかっていない...
Promiseのthenが素敵
javascriptではes2015からPromise
が使えるようになります。Promise
で非同期処理を包むことで、非同期処理が終わったタイミングで次の処理を開始できて、コールバック地獄をなくす、等と言われていたりします。
詳しい使い方は検索するとたくさん出てくるので割愛させていただいて、ちょっと気になったことを書いていきます。
以下、サーバへ非同期で通信し、json
データをもらって、表示するコードです。主な動きとしては;
- 2000ms待つ。
- サーバへ非同期なGETリクエストを送信。
- レスポンスのJSON文字列をオブジェクトに変換。
- オブジェクトの情報を加工して画面に描画。
です。
app/index.js
const wait = ms => { return new Promise((resolve, reject) => { console.log(`wait ${ms}ms...`); setInterval(_ => { resolve('OK!'); }, ms) }); } const fetchData = url => { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = () => { if (xhr.readyState === 4 && xhr.status === 200) { resolve(xhr.response); return; } reject(new Error(xhr.statusText)); }; xhr.onerror = () => { reject(new Error(xhr.statusText)); }; xhr.send(null); }); }; const renderHoges = data => { const root = document.getElementById('root'); root.innerHTML = ''; data.forEach(item => { let node = document.createElement('div'); node.innerText = `${item.id}: ${item.hoge}`; root.appendChild(node); }); return data.length; }; wait(2000) .then(_ => fetchData('/api/hoges')) .then(JSON.parse) .then(renderHoges) .then(length => console.log(`${length} hoges rendered!`)) .catch(error => console.error(error));
app/static/index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Promise</title> </head> <body> <div id="root">loading...</div> <script type="text/javascript" src="bundle.js"></script> </body> </html>
サーバ側はここから見れます。
嬉しいなと思ったのは、then
を使うことで、もともとPromise
を考慮していないJSON.parse
やrenderHoges
などの同期処理をチェインできることです。
つまり明確に非同期な処理だけPromise
で書けばよく、チェインさせたいがために同期処理のPromise
版を書く必要がないということです。
感想
何当たり前のこと言ってんの?と思われるかもしれないですね。
困ったのは、Promise
から値を取り出す方法はあるのだろうか?、ということです。それを必要としている時点で処理の設計を間違えているのだろうか?とりあえず、外側で宣言した変数に再代入させる方法は上手くいきませんでした。redux
でサーバとやりとりする際にaction creator
の関数の中にPromise
を使った非同期通信を書いた時に、レスポンスの中身が取り出せない。。。
haskellのApplicative laws - composition
こんにちは。
すごいHaskellたのしく学ぼう!の11章に出てくるアプリカティブ則の一つ、「composition則」について何を言っているかを考えてみました。
composition則
Applicative型クラスを継承するデータ型は、関数を実装すること以外にも、実装した関数が満たさなければいけない規則が存在します。これを確認するのは設計者に委ねられています。Applicative型クラスでは4つの法則が存在します。ここではその4つのうちの一つ「composition則」(正式名称不明)について考えてみたいと思います。ちなみに4つの法則はBasic libraries
のリファレンスControl.Applicativeのページに載っています。
「composition則」は次で定義されています;
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
なぜこの法則を満たさなければいけないのでしょうか。ここでは関数合成に対して、「アプリカティブファンクターに包まれた合成演算子(.)
を使って関数合成を行うことと、<*>
を使って関数合成を行うことは同じか?」ということが成り立って欲しいようです。単なる想像ですが、既存に存在している合成演算子(.)
と新しい合成演算子<*>
の間にある種の整合性を保っておきたいように見えます。composition則はそのことを確かめましょう、といっています。
composition則の定義を見ると、左辺がごちゃごちゃしています。適応の順を整理していきましょう。
pure (.) <*> u <*> v <*> w -- = ( pure (.) ) <*> u <*> v <*> w -- = ( ( pure (.) ) <*> u ) <*> v <*> w -- = ( ( ( pure (.) ) <*> u ) <*> v ) <*> w :
実際に適用して行ってみましょう。
-- u, v は以下; -- u :: Num a => Maybe (a -> a) -- v :: Num a => Maybe (a -> a) ghci>:t pure (.) pure (.) :: Applicative f => f ((b -> c) -> (a -> b) -> a -> c) -- まず合成演算子(.)がpureによってアプリカティブファンクターに包まれます。 ghci>:t pure (.) <*> u pure (.) <*> u :: Num c => Maybe ((a -> c) -> a -> c) -- 次に<*>で、アプリカティブファンクターに包まれた合成演算子の、 -- 第一引数の関数としてuを渡します。 ghci>:t pure (.) <*> u <*> v pure (.) <*> u <*> v :: Num c => Maybe (c -> c) -- 再び<*>で、アプリカティブファンクターに包まれた合成演算子の、 -- 第二引数の関数としてvを渡します。 ghci>:t pure (.) <*> u <*> v <*> Just 1 pure (.) <*> u <*> v <*> Just 1 :: Num b => Maybe b -- 最後に完成したアプリカティブファンクターに包まれた関数に対し、 -- <*>でアプリカティブ値を渡します。
左辺はこれで終わりです。右辺は<*>
で関数適用をチェインしているだけなので割愛です。
なぜ必要なのか?
これに関してははっきりとしたことはわかりませんでした。composition則が何を保証したいかは、結合演算子がどんな関数にも使えることに関係してくるのではないかと思うのですが自信はないです。調べたいと思います。
Vimで折畳
こんにちは。
Vimについて書きます。
概要
Vimでは文章を折りたたむことができるようです(Vi には折畳は無い)。htmlを編集している時など、ファイルが長くてちょっと読みづらいといった時にとても便利です。
Vimユーザマニュアルはとても素晴らしいので、ここにあれこれ解説を書く意味もないかと思います。なのでここでは、コマンドとその動きとかを軽く書こうと思います。Vimを起動した状態で:help fold
や:help usr_28.txt
を実行すると関連するドキュメントを読むことが出来ます。またwebの翻訳も利用可能です。
コマンド
折りたたみ関連のコマンドの多くはz
で始まります。基本的なものは以下です。
コマンド | 内容 |
---|---|
zf |
折り畳む (Fold) |
zo |
折り畳みを開く (Open) |
zc |
折り畳みを閉じる (Close) |
za |
折り畳みの開く閉じるを切り替える (Alter?) |
zd |
折り畳みを削除する(Delete) |
zf
はオペレータなので、)
や2j
などのモーションか、it
やip
などのテキストオブジェクトを後ろにくっつけて使用します。
他にもファイル全体の操作もあります。
コマンド | 内容 |
---|---|
zr |
すべての折り畳みを開く(減らす) (Reduce) |
zm |
すべての折り畳みを閉じる(増やす) (More) |
zr
,zm
はすべての折り畳みに対する操作ですが、あくまで現在表示されているものが対象になります。つまり入れ子になっている折り畳みを開いたり閉じたりは出来ません。この時に使用するのがzR
,zM
です。これらは再帰的に折り畳みに作用します。同様に、o
,c
,a
,d
も大文字にすることで再帰的に操作を行います。例えばzR
は再帰的にすべての折り畳みを開くので、結果すべての折り畳みを開きます。zM
と交互に使うのが楽かもしれないです。
あと、折り畳み間を移動するのに便利なコマンドです。
コマンド | 内容 |
---|---|
zj |
画面下方向の次の折り畳みへ移動。 |
zk |
画面上方向の次の折り畳みへ移動。 |
[z |
現在開いている折り畳みの先頭へ移動、すでに先頭の場合はその外側の先頭へ移動、すでに一番外側の折り畳みなら何も起きない。 |
]z |
現在開いている折り畳みの末尾へ移動、動作は[z と同様。 |
疑問
折りたたまれている情報はどこにある?
マニュアルによると、ファイルを破棄すると折り畳みの情報は失われてしまうそうです。折り畳みの情報を保存するコマンドはmkview
、呼び出すコマンドはloadview
です。
保存されたファイルはset viewdir
コマンドで表示されるディレクトリ内で確認できます。ファイルを開いてみるとローカル変数の定義の後に以下のような記述が見つかりました。数字を変えたら折り畳み位置が変わりましたのでこれが保存された設定値だと思います。
... 3,6fold 8,21fold ...
感想
保存について書きましたが、折り畳みを保存して呼び出して使うのは現実的ではないと思います。めんどくさいです。htmlやコードは構造がはっきりしているため、自動的に折り畳みを定義できるはずで、実際そういう設定がVimにはあるようです。
set foldmethod
で現在の設定が確認できます。現在はmanual
になっているので、これは手動で折り畳みを定義するデフォルトの設定なようです。
この他指定可能な値は以下になるようです。
値 | 内容 |
---|---|
manual | 折り畳みは手動で設定する。 |
indent | 等しいインデントの行で折り畳みを作る。 |
expr | オプションfoldexpr で深さを設定する。 |
marker | マーカーで折り畳みを指定する。 |
syntax | 構文強調表示のキーワードを使って指定する。 |
diff | 変更されていないテキストを折り畳む。 |
とりあえずset foldmethod=indent
をvimrc
に設定して様子を見ます。ただしこれを設定すると開くファイルがことごとくデフォルトで折り畳まれてしまっています。これは結構面倒臭いのでset foldlevel=100
とか書いておくと初期表示時に折り畳まれなくなります(なんか乱暴ですね...)。
以上です!
javascriptでpartition 関数を実装
こんにちは
javascriptでhaskellでいうところのpartition関数を実装してみたのでメモします。
partition関数
全然一般的ではないので説明です。僕がpartition関数って言っているのは、「配列を、その要素に対して真偽を判定する関数により、真なものと偽なものの2つのグループに分ける関数」です。
たとえば以下のように動くものです。
[0,1,2,3,4,5,6,7,8,9].partition(num => num > 4) // => [[5,6,7,8,9],[0,1,2,3,4]] [1,"2",3].partition(item => typeof item === 'string') // => [["2"],[1,3]]
いかにもありそうな関数ですが、探した限りではありませんでした。(altjsでは実装されている場合があるようです)
やっちゃだめみたいですがArrayのprototypeを汚染して関数を定義します。Arrayの他のコールバックを引数に取る関数を参考にしました。
Array.prototype.partition = function(callback, thisArg) { if (this === null) { throw new TypeError('Array.prototype.partition called on null or undefined'); } if (typeof callback !== 'function') { throw new TypeError('callback must be a function'); } var T; if (thisArg) { T = thisArg; } var list = Object(this); var length = list.length >>> 0; var value; var accepted = new Array(); var rejected = new Array(); for(var i = 0; i < length; i++) { value = list[i]; if (callback.call(T, value, i, list)) { accepted.push(value); } else { rejected.push(value); } } return [accepted, rejected]; }; [0,1,2,3,4,5,6,7,8,9].partition(num => num > 4) // => [[5,6,7,8,9],[0,1,2,3,4]] [1,"2",3].partition(item => typeof item === 'string') // => [["2"],[1,3]]
第二引数のthisArg
はArray.prototype.map
の仕様にあったため追記しました。使い道があまりわかりませんが例えば繰り返しの中で副作用を起こさせることができます(ちょっと意味のない例。。。)。
追記(2017/11/29): よく考えるとthisArg
は関数呼ぶ時に呼び元のthis
を渡したりする時に使うためでした。
[1,2,3,4,5].partition(function(num) { this.log(num) ; return num > 2; }, { log(num) { console.log(`${num}を振り分けました`) } });
最後にreduceを使って、配列とコールバックを引数に取る関数として書いてみます。
const partition = (list, callback) => { if (list === null) throw new TypeError('partition called on null or undefined'); if (typeof callback !== 'function') throw new TypeError('callback must be a function'); return list.reduce((state, item) => { if(callback(item)) { state[0].push(item); } else { state[1].push(item); } return state; }, [[],[]]); }; partition([0,1,2,3,4,5,6,7,8,9], num => num > 4) // => [[5,6,7,8,9],[0,1,2,3,4]] partition([1,"2",3], item => typeof item === 'string') // => [["2"],[1,3]]
感想
テストとか書いてしっかり確かめたいですね。小さいものは書いていて楽しいです。
browser-syncでブラウザ自動更新
Node.jsでコードを書いていると、ターミナルとブラウザの行き来が頻発して面倒です。自分のノートPCはモニタが小さいので、ウィンドウを切り替えていると埋もれてしまい、それを探すために集中力が切れてしまったりします。
browser-syncを使ってコードを書き換えるとブラウザが自動更新されるようにしてみました。expressと連携することもできるので、そのこともメモしておきます。
単純な使い方
npm install --save-dev browser-sync
監視対象のファイルを指定して起動します。
./node_modules/.bin/browser-sync start --server --files index.html
コマンドを実行するとlocalhost:3000
でサーバが起動します。ブラウザが立ち上がり、Connected to Browser Sync
という文字がページ右上に表示されます。この状態で、index.html
を編集するとブラウザが自動更新されます。
expressと連携
expressでサーバを立てている場合、connect-browser-sync
ミドルウェアを利用してサーバに自動更新の機能を組み込むことができます。まずはインストールします。
npm install express --save npm install browser-sync --save-dev npm install connect-browser-sync --save-dev
app.jsを作成します。
var express = require('express'); var app = express(); if ( app.get('env') === 'development' ) { var browserSync = require('browser-sync'); var connectBrowserSync = require('connect-browser-sync'); var browserSyncConfigurations = { "files": "static/*" }; app.use(connectBrowserSync(browserSync(browserSyncConfigurations))); } app.use(express.static(__dirname + '/static')); app.get('/', function (req, res) { res.sendFile(__dirname + '/static/index.html'); }); app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); app.listen(3000);
if
の部分がbrowserSyncの設定です。オプションをオブジェクトでbrowserSync
関数に渡し、connectBrowserSync
ミドルウェアをexpressに登録しています。オプションのfiles
は監視対象のファイルを設定します。ワイルドカードで指定したり、配列として複数のパスを渡すこともできます。ここではstatic
以下のファイルをすべて監視しています。ここをコンパイル後のjsの吐き出し先に設定したりすれば、webpackと協調して使うこともできますね。
ミドルウェアの登録のタイミング次第では動かなかったりして少しはまりました。静的コンテンツのディレクトリ設定をbrowserSyncの前に設定したら監視が始まりませんでした。
感想
browserSync
で検索すると大体はgulp
と一緒に使おうみたいな記事が見つかります。設定ファイルの記述方法を覚えるのが大変なので、gulp
はやらなくていいかなと思い、自動更新に絞った使い方を調べました。
上記二つのケースどちらでも、browserSyncを起動すると管理サーバも立ち上がるのですが、その使い方があまりわかっていません。また「snipet
を貼り付けてね」みたいなことをbrowserSyncから注意されるのですが、これが何に使われているかわからないです。自動更新できているから、ひとまず放置しています。
追記: ブラウザ同士のイベントを同期することもできるみたいです。同一ネットワーク内だと同期されるのかな?
圏, 関手, 自然変換
圏論の定義メモ
Def.圏(category)
圏 は次のデータからなります;
- 対象(objects)
- , , ...
- 射(arrows)
- , , ...
- ドメイン(domain), コドメイン(codomain)
- 任意の射 に対し, 対象 , が定まる. この時 と表す. (ただし 、)
- 合成(composite)
- 任意の射 、に対し、 という射が定まる.
- 恒等射(identity arrow)
- 任意の対象 に対し, という射が定まる.
これらのデータは次の法則を満たします;
- 結合律(associativity):
- 任意の射 , , で, を満たすものに対し, が成り立つ.
- 恒等律(unit):
- 任意の射 に対し,
Def.関手(functor)
圏 , に対し, 関手 とは, 圏 の対象を圏 の対象に, 圏 の射を圏 の射に写すもので, 次を満たします;
Def.自然変換(natural transformation)
圏 , , 関手 に対し, 自然変換 とは, 圏 の任意の対象 に対し, 圏 の射 を与えるもので, 次を満たします;
- 圏 の任意の射 に対し, が成り立つ.
感想
だからどうしたという感じですよね。写像だ、とか集合だ、とか限定すると間違ってしまうためぼんやりした定義になってしまいます。はてなの所為なのか可換図式書けませんでした...
PCのカメラを起動する
WebRTCで遊んでみたいので、手始めにPCの内臓カメラを起動するだけのページを作成します。
navigator
オブジェクトのgetUserMedia
メソッドを使用することでデバイスにアクセスできるみたいです。ただ、ブラウザ環境ごとにこのメソッドがあったりなかったりするため、代替となる関数を列挙してそのうちあったものを使用する、というコードを書きます。
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia || navigator.msGetUserMedia;
メソッド自体はこんな形をしています。
navigator.getUserMedia(constraints,successCallback,errorCallback)
次にこのAPIを使用してメディアに接続します。
navigator.getUserMedia({video: true, audio: true}, stream => { video.src = window.URL.createObjectURL(stream); }, error => { console.error(error); return; });
あとは適当にhtmlをかいた、ら動くと思ったのですが、エラーも吐かずになぜか動かない。
そういえばwebサーバに置かないとダメ、みたいなことを聞いた気がしたので、nodeでexpressを使ってサーバを立てました。
mkdir webrtc cd webrtc npm init -y npm install express
二つのファイルを作成します。
server.js
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.sendFile(__dirname + '/index.html'); }); app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); app.listen(3000);
index.html
<html> <head> <title>sample</title> </head> <body> <video id='video' autoplay></video> <script type="text/javascript"> let video = document.querySelector('video'); navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; navigator.getUserMedia({video: true, audio: true}, stream => { video.src = window.URL.createObjectURL(stream); }, error => { console.error(error); return; }); </script> </body> </html>
あとは起動してlocalhostの3000ポートにアクセスします。
node server.js
なんか暗いですが映りました!
感想
getUserMediaがPromise化対応したそうですが、chromeはダメみたいです。
近々redux
のコンポーネントとして、動画の録画、再生、停止、保存機能を持った部品を作成したいです。
あと通信部分で、同一LAN内の携帯とPCとかならビデオチャットできないだろうか?と考えています。
オーダー
オーダーについて。適当に済ましていたので、おぼえがきします。アルゴリズムでも数学でも見ることがよくありますが、若干うろ覚えです。
定義
が のときオーダーである。
これを変形すると、 となることから、この定義は は より発散する速度が遅いということを意味しているのがわかります。この時 は で抑えられる、と言ったりするようです。
これに対し似た記法で、というものもあります。この時、以下が成り立ちます。
詳細な定義は省略しますが極限が0になることが違います。 は より発散する速度が、 の時より、十分遅いというイメージです。この時 は に比べ無視できる、と言ったりするようです。例えば、写像 が で全微分可能とは、
です。ここでは となっていて多少変化がありますが基本的に意味は変わっていません。つまり、 と は のノルムが十分小さいところでは限りなく等しいということが言えます。
アルゴリズムなどではよく、、 などと言ったりしますね。これらは「入力データ量 に対する実行時間は 」みたいな文脈だったと思います。これはつまり、
より、
この場合はアルゴリズムの実行時間が に比例する、ということですね。 なども同様です。
感想
わかったようなわかってないような微妙な気分です。当たり前のことを言っている気持ちにもなりました。
まあ少なくとも毎回ググらなくてよくなっただけ、進歩かと思います。
javascriptで実行時間を表示する
短いメモです。
console.time
、console.timeEnd
を使うとスクリプトの実行時間を表示することができます。
var loop = n => { console.time('timer'); for(i = 1; i <= n; i++) { for(j = 1; j <= n; j++) { console.log(`${i} x ${j} = ${i*j}`); } } console.timeEnd('timer'); }
引数で与えている文字列を使って、スタートとエンドを対応させます。
ここを参考にしました[JavaScript]使い分けるだけで今すぐデバッグ効率を上げる、consoleオブジェクトの関数 - Qiita。console
オブジェクトの関数がたくさん書かれています。
ReduxとExpressかElectronか
Reduxのチュートリアルを見終わったので、実際に手を動かしてみようと思い、リポジトリを作成してみました。中身はExample: Todo List | Reduxです。
express
とelectron
で動作確認ができます。
感想
写経しているとredux
に関して徐々にわかってきました。redux
は画面状態をStore
内のstate
オブジェクトとして持っていて、操作によって発行されるaction
オブジェクトとstate
をセットで、Reducer
に渡すことで、新たなstate
を取得します。
今回の登場人物は、
- Component(presentation, container)
- Action Creator
- Reducer & Store
処理の流れもこの順番です。
画面はコンポーネント(Component
)のツリー構造で表現されます。コンポーネントにはPresentation Component
とContainer Component
の二種類があり、Presentation Component
は構造を、Container Component
は状態を変化させる方法を定義します。単独で部品としても使えますが、Container Component
で定義した状態を変化させる関数を、react-redux
ライブラリに含まれるconnect
関数を使って、Presentation Component
に結びつけて使用したりもできます。また、このconnect
によってStore
とComponent
の結びつけも行なっているようです。(ちょっと調べないと、です)
また、コンポーネントのツリーの頂点にProvider
というreact-redux
ライブラリに含まれるコンポーネントを追加しているので、ここが状態をツリーの枝に伝える仕組みを提供しているのかもしれないです。
特定の動作に対して発行するAction
をAction Creator
で定義しておきます。これは最低限type
というプロパティを持ったオブジェクトを生成すれば問題ないようです。
Reducer
がStore
を定めているのだろうという認識で、この二つは同じ行に書いています。(実装的な視点、ですかね)Reducer
は古いstate
と、発行されたaction
を引数に新しいstate
を返します。redux
ライブラリのcreateStore
関数によってReducer
からStore
が生成されます。
いろいろ書きましたが、それぞれの部品はわかるものの、連結部分がわかりにくいという感想です。
知らなくてはいけないのはreact-redux
のProvider
、connect
によっていかにコンポーネントとStore
を結びつけているか、という部分ですかね。
Reduxの勉強3
引き続き、Reduxのレッスン動画を視聴した時のメモです。
- 21: ToDoアプリのリファクタリング。見た目と挙動を分けます。Main container componentからPresentational componentを分離しましょうというはなし。
- 22: container componentは振る舞いを定義するクラスとして作成した。
conponentDidMount
、conponentWillUnmont
メソッドを定義し、presentational componentは状態に対する見た目だけを定義する。のかな? - 23: Storeと結びついているコンポーネントは
forceUpdate
する?render
を全体のコンポーネントから分割したコンポーネントに行わせるように修正した。(しかしAddToDoコンポーネントにはrenderがないようだが)厳密にPresentationとContainerを分割する必要はないが、したほうがいいと言ったことを言っている気がする。。。
(だんだんきつくなってきました。。。)
- 24: StoreにアクセスするコンポーネントがContainerコンポーネントみたいだ。子のContainer ComponentにStoreを渡すために一旦storeをトップレベルの引数として渡す処理を書いた。
- 25:
Provider
を使うことでPresentation ComponentからStoreを取り除くことができるみたいだ。ContextによってContainer ComponentにStoreを渡すことができるようになった。Contextを使うにはchildContextTypesを指定しなければいけない。グローバルにアクセスできる感じか。ただしContextはあまり安定していないらしく、がっつり使っているのは良くないとのことです。 - 26:
Provider
はライブラリとして取得できるよ、という説明と記述方法でした。 - 27:
ReactRedux
ライブラリのconnect
メソッドを使うことでContainer Componentを作成することができる。最終的にどうなるか知れればいいかなという気分になってきました。 - 28, 29:
connect
メソッドを使って色々整理していきます。 - 30: ActionCreaterについて。
だいぶ聞き取れず、試すこともできず、停滞しています。 後日初めから、アプリを作成しつつ追っかけていこうと思います。。。
webpack、babel、React
Reactの環境構築です。できるだけ覚えることを減らしたいので、webpack
、babel
、react
を軸に必要最低限なものをインストールします。Node.js
の環境が整っている前提です。(npm
コマンドが使えればOKかと思います)
それぞれに関するメモです。
webpack
: jsxとかes6記法で書いたスクリプトとかをクライアント側に送るためにまとめるビルドツールという認識です。モジュール間の依存性を解決しつつビルドしてくれるそうです。チュートリアルやったらわかった気になれました。loader
というモジュールを追加することで、jsファイルだけでなくcssなどのまとめ方も指定できます。babel
: es6記法を一般的なブラウザで読めるようにコンパイルするもの。webpackでes6記法を含んだファイルをビルドする際に、エンジンとして使いました。インストールのみで特に何もしていないです。react
: jsのテンプレートエンジンです。jsxとして書くのが楽なので書くと、そのままではブラウザで動きません。なのでwebpackで普通のjsファイルにビルドさせます。
設定
パッケージファイルを作成し、必要なパッケージをインストールします。
npm init -y npm install webpack babel-loader babel-core babel-preset-es2015 babel-preset-react react react-dom --save-dev
次はwebpack
の設定です、設定ファイルを記述しない場合webpackにいろいろオプションを指定して実行することになりますので、ファイルwebpack.confing.js
を作成します。
webpack.config.js
module.exports = { entry: "./entry.js", output: { path: __dirname, filename: "bundle.js" }, module: { loaders: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, loader: "babel", query: { presets: ['es2015', 'react'] } } ] } };
このファイルにwebpackコマンド実行時の設定をオブジェクトとして記述していきます。プロパティの説明をします。
entry
がビルドしたいスクリプトになります。この例ではentry.js
と設定しています。内部でrequire
とか呼び出ししているファイルは、webpackが勝手にまとめてくれます。output
には出力ファイルのパスと名前を記述します。ディレクトリ構造を考えていないので特にパスは指定してません。module
のloaders
という配列に、どのファイルをどのようにビルドするかの設定をオブジェクトで記述します。今回は一つだけです。test
にはどのファイルを対象にするかを正規表現で記述します。今回は拡張子がjs
またはjsx
のものを対象にします。exclude
には除外するもののパターンを記述します。loader
はビルドするエンジンを指定しているのだと思います。loader
に機能を追加するのがpreset
なんだろうか。presets
はes6記法とreactに関するものを指定しました。
次に、順番が前後していますが、entry.js
とReactのコンポーネントと動作確認用のindex.html
を作りましょう。
entry.js
window.onload = () => { require('./Hello.jsx'); }
Hello.jsx
const React = require('react'); const { render } = require('react-dom'); const Hello = React.createClass({ render: function() { return ( <div className="hello"> Hello, world! </div> ); } }); render( <Hello />, document.getElementById('root') );
index.html
<html> <head> <meta charset="utf-8"> </head> <body> <script type="text/javascript" src="bundle.js" charset="utf-8"></script> <div id="root"></div> </body> </html>
あとはコマンドを叩くだけです。
./node_modules/.bin/webpack
実行完了したらindex.html
をブラウザで確認してみてください。Reactっぽさはないですが、レンダリングされているのが確認できると思います。
必須ではないですがコマンドの実行はpackage.json
のscripts
に記述しちゃうと楽です。
package.json
{ "name": "webpack-tutorial", "version": "1.0.0", "description": "", "main": "bundle.js", "dependencies": { "webpack": "^1.13.1", "css-loader": "^0.23.1", "style-loader": "^0.13.1", "babel-core": "^6.10.4", "babel-loader": "^6.2.4", "babel-preset-es2015": "^6.9.0", "babel-preset-react": "^6.11.1", "react": "^15.2.0", "react-dom": "^15.2.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "webpack": "webpack --progress --colors" }, "author": "", "license": "ISC" }
以下で起動できるようになります。
npm run webpack
以上です。コードはgithubに上げました。リポジトリ名がチュートリアルとなっていますが全然チュートリアルではないですが。。。
感想
ビルドツールのノリが少しわかりました。ビルドツールが自分にとって身近ではないのは、頻繁にビルドしないからに尽きるんでしょうね。
ともあれようやくReactが動いたので、Reduxのチュートリアルを試すことができます。
Reduxの勉強2
動画が30分で終わるかと思ったら30回あることに気がついて力尽きてしまったので、今日はその残りを見ていきます。
Webpackを使えるようになろうとか言っていましたが、いまの腰を据えて試せていないので停滞しています。残りの動画を放っておくとちょっと分かった気になっているので見なくなる可能性があります、こちらを優先させていただきます。
- 11: todos Reducerをテストファーストで書いた。actionにidがふってあるが、どこまでふったか、stateかどこかに数字を控えなければいけないのだろうか?(後の動画でグローバル変数として保持しているのがわかる)
- 12: todos Reducerに「ADD_TODO」だけではなく「TOGGLE_TODO」メソッド(case?すくなくともメソッドではない)を追加。副作用なくすためにしっかりテスト書かないとダメ。ちょっと性善説で悩む(でもみんな責任感あるし大丈夫かな)。
- 13: Reducerの分割。stateのリストを受け取るReducerの内部を、単一のstateを受け取るReducerに切り出す。どんどんReducer同士を結合させちゃえてきな英語を喋っていた気がする、この辺りは副作用のない関数であることが不安を取り去ってくれてますね。想定していないtypeのActionに関してはそのままstate返しましょう、という念押し。
- 14: Reducerの合成だが、ちょっと難しい。todosとは別のvisibilityFilter Reducerを追加し、それらを包含したtodoApp Reducerを作成。todoApp内部で、各Reducerへの振り分けを行なっているみたい。なんかStoreとReducerの関係性が曖昧になってきた。。。ActionをStoreに
dispatch
した際に、入れ子になったどのstateに対するActionか指定していないが、これは対象としていない方にundefinedが渡るからシカトしてOKということか?なんか違和感。 - 15: Reducerの合成は
combineReducers
を使うと楽と言っている。またes6の記法で定義した変数a, b, c
をobj = {a,b,c}
と書けばオブジェクトのkey valueが勝手に入るらしい。「object literal shorthand anotation」だそうです。 - 16:
combineReducers
の実装を書いて説明してくれる。reduce
で少し詰まった。多分、foldl
とかと同じでいいんですよね。配列の各要素を使って一つのものをどんどん作っていくイメージをしました。 - 17: Reactを使ってToDoの「ADD_TODO」機能のユーザインタフェースを作った。また、入力から出力までのReduxの(?)ライフサイクルを説明してくれているので、よくわからなくなったらこれを見るといいかも。
- 18: 「TOGGLE_TODO」機能をUIから使えるようにした。全体的に言えるのはどの関数も、基本的には全てのstateを総なめして一致したら処理を実行する感じみたいだ。
- 19: 「visibilityFilter」のUIを作成。画面側の機能とReducerは対応していないという当たり前のことに気がつく。filterをかける処理などは通常の関数で実装しているが、もし自分が作った時にその辺間違えそう。画面表示とstateの違いをちゃんと整理しておかないと危険ですね。
- 20: ToDoアプリのリファクタリング。Reactコンポーネントの分離について。分離の基準がよく理解できない。イベントハンドラのコールバック関数はどちらが持つべきか等。分離の途中で次回。
感想
本当は今日中に30まで見てしまって、手を動かす時間を取りたかったのですが、眠気が襲ってきてしまったのでこれで終了です。
動画の中で、コンポーネント内でinputの値を取得するために、react callback ref api
を使っていた。常識っぽいので知っておきたいです。
あと、markdownのlistはoffsetが効かないため困りました。今日は動画を11から見始めたので、
11. hogehoge 12. hogehoge ...
と書いていたのですが、画面上では
1. hogehoge 2. hogehoge ...
になってしまい、ちょっと面倒さを感じながら*
をかませて対応しました。