自己紹介
じゅんじゅんというニックネームで、関西を拠点に活動しているフロントエンドエンジニアです。
HAL大阪の2回生です👍 (2016.11.10現在)
よくstart up系イベントに行くので、大阪らへんの方は会いましょう!
開発環境
今回は、ReactがメインではなくFluxを導入したいけどどんなもんなのか頭で理解でききらん!や、チョットナニイッテルカワカラナイデスっていう方向けの記事です。
なので、開発のbabelがどうのとかwebpackがどうのとかはしんどいので、コマンド1つでReactのアプリケーションの雛形を作ってくれるFacebook公式のものを使っていきたいと思います。
使うのはcreate-react-appです。
これ自体の詳細は
Facebook公式のcreate-react-appコマンドを使ってReact.jsアプリを爆速で作成する
を参考にしていただけたらなと思います。
まずインストールから初めて行きます。
$ npm install -g create-react-app
インストールできたらコマンドでプロジェクトを作っていきます。
$ create-react-app <project名>
ここまででReactを書き始める準備はもうバッチリになっています。
ひとまず今回はCSSとかは記事の内容と関係ないのでcssファイルとlogoなどの無駄なファイルを消していきます。
最終的に
project_name/
- node_modues/
- public/
- favicon.ico
- index.html
- src/
- index.js
- .gitignore
- package.json
- README.md
と言う構成にしてください。
Flux
Fluxの説明などはいくらでもQiitaやhatenablogなどに乗ってるので今回は省きます。
今回作るTodoはfacebook/fluxのtodoを簡略化したものになります。
では、Fluxを導入するためにsrc/
を以下のように変更してください。
src/
- actions/
- TodoActions.js
- constants/
- TodoConstants.js
- components/
- TodoApp.jsx
- stores/
- TodoStore.js
- dispatcher/
- AppDispatcher.js
まずTodoApp.jsx
を書きます。
import React from "react"
export default class TodoApp extends React.Component {
render(){
return(
<h1>Hello</h1>
)
}
}
このように表示されたでしょうか。
今回はfacebookのfluxリポジトリに合わせるためにフォルダ構成をactions
,constants
,stores
,dispatcher
に分けていきたいと思います。
それぞれを簡単に説明すると
- actions
- dispatcherへ誰に送るべきかを添えて送る。
- constants
- 決め事。
- stores
- データをためておく場所、処理自体を行う。
- dispatcher
- storeへ何をどう変更するのはを伝える。
といった感じでしょうか。
ではまず、約束から決めていきましょう。
このconstantsは、それぞれ動作を表します。
今回は単純さを一番にしているので、作成と削除だけにしたいと思います。
その場合、動作としてはcreate
とdestory
になると思います。(removeやdeleteでも構わないと思いますが予約語などに気をつけてください。今回はfacebookに従います。)
この2つの動作をconstants
へ書いていきたいと思います。
import keyMirror from "keymirror"
export default keyMirror({
TODO_CREATE: null,
TODO_DESTROY: null
})
今回使っているkeyMirror
ですが、多分nullの方へkeyを転写してるだけなので自分で書いても問題ありません!
続いてdispatcher
を書いていきます。
といっても、dispatcher
自体はfluxのモジュールの中にあるのでそれを使っていきたいと思います。
import { Dispatcher } from "flux"
export default new Dispatcher()
この2行で終わりです!
続いてaction
を書いていきたいと思います。
import AppDispatcher from "../dispatcher/AppDispatcher"
import TodoConstants from "../constants/TodoConstants"
var TodoActions = {
create(text){
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_CREATE, // 誰に
text: text // 何を渡すのか
})
}
destory(id){
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_DESTROY,
id: id
})
}
}
export default TodoActions
これでAction
はdispatcherに向けてだけ投げてればいいので他のことを意識する必要がありません。
何を誰に渡すのかだけを見てdispatcherさんに渡します。
これもfluxのいいところですね。
では、stores
を書いていきましょう。
import AppDispatcher from "../dispatcherAppDispatcher"
import { EventEmitter } from "events"
import TodoConstants from "../constants/TodoConstants"
import assign from "object-assign"
var CHANGE_EVENT = "change" // chenge evnetを定数にする
var _todos = {} // 初期化
/*
* ここから処理本体を書き始める
*/
var create = (text) => {
var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
_todos[id] = {
id: id,
text: text
}
}
var destory = (id) => {
delete _todos[id]
}
var TodoStore = assign({},EventEmitter.prototype,{
getAll(){ // 今のtodo全てを返す
return _todos
}
emitChange(){ // 何かアクションがあったことを知らせる
this.emit(CHANGE_EVENT)
}
addChangeListener(callback){ // リスナーに追加
this.on(CHANGE_EVENT, callback)
}
removeChangeListener(callback){ // リスナーから削除
this.removeListener(CHANGE_EVENT, callback)
}
})
AppDispatcher.register((action)=>{
var text;
switch(action.actionType){ // actionTypeでswitchする
case TodoConstants.TODO_CREATE: // createなら
text = action.text.trim()
if(text !== ''){
create(text)
TodoStore.emitChange()
}
break;
case TodoConstants.TODO_DESTROY: // destroyなら
destroy(action.id)
TodoStore.emitChange()
break;
default:
// no
}
})
export default TodoStore
これで処理周りは全てできたので、viewを作っていきましょう。
TodoApp.jsx
の実装
import React from "react"
import TodoStore from "../stores/TodoStore"
import TodoActions from '../actions/TodoActions';
var getTodoState = () => {
return TodoStore.getAll()
}
export default class TodoApp extends React.Component {
constructor(){
super()
this.state = {
value: "",
todos: getTodoState()
}
}
componentDidMount(){
TodoStore.addChangeListener(this._onChange.bind(this))
}
componentWillUnmount(){
TodoStore.removeChangeListener(this._onChange.bind(this));
}
render(){
var todoElements = [];
var todos = this.state.todos;
for(var key in todos){
todoElements.push( // todoの分だけ配列に追加する
<li key={key} id={todos[key].id}>
<span style={{marginRight: "30px"}}>{todos[key].text}</span>
<button onClick={this._destroy.bind(this)}>×</button>
</li>
)
}
return(
<div>
<input type="text" value={this.state.value} onChange={this._Input.bind(this)}/>
<button onClick={this._submit.bind(this)}>Add.</button>
<ul>
{todoElements} {/*配列を展開する*/}
</ul>
</div>
)
}
_destroy(e){
var id = e.target.parentNode.id
TodoActions.destroy(id) // 消したい投稿のidを渡すだけ!
}
_submit(){
TodoActions.create(this.state.value) // テキストを渡すだけ!
this.setState({ // 追加したら入力欄は空にする
value: ""
})
}
_Input(e){ // 入力中(onChange)でstateを書き換える
this.setState({
value: e.target.value
})
}
_onChange(){
this.setState(getTodoState())
}
}
複数Reactならではのハマるポイントがあるので説明を付け加えます。
まず、ES2015でReactを書くとthisのオートバインドがなくなる問題。
これに関してはいくつか解決策があるのでこちらを参照ください。
次に、入力された文字が入るthis.state.value
なんですが、inputのvalueとしても使いたいです。
なので、onChange
などでstate
を書き換えまくってinputのvalueにも展開していく感じにします。
最後に、Reactには配列をループしてくれるものがありません。
なので自分でループを作り、それを展開しないといけません。
ループのやり方はこちらに詳しく書いています。
これでもうTodoができていると思います!
最終系はこんな感じ
あとがき
かなり機能を絞って作ったのでそんなに難しくはないと思うし、fluxってこんなもんか〜程度に受け取ってもらえると有り難いです!
コードはgithubに置いているのでcloneしたらすぐ動かすことができると思います!
twitter -> @konojunya