#React勉強会
Reduxを使ったTodoアプリ
https://github.com/mackay-isogawa/redux-todo.git
##Redux
肝は「action」「reducer」「store」でデータの一連の流れ
###Action
ただのオブジェクト
"type"プロパティを必ず持つ
後にこのtypeを参照してactionを呼び出す
actionに入ってくるのは、componentでキャッチしたstateとaction.type
store(reducer関数)に渡したいデータを入れる
=>reducerにてaction.methodNameでデータにアクセス可能
ex.)
let nextTodoId = 0
export const addTodo = text => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
export const setVisibilityFilter = filter => {
return {
type: 'SET_VISIBILITY_FILTER',
filter
}
}
export const toggleTodo = id => {
return {
type: 'TOGGLE_TODO',
id,
}
}
###Reducer
ただのFunction.
入力されるstateに対して新しいstateを返す
reducerに渡されるStateはStoreを作成する時に決定される
どこからStateが来るかとういうと、CreateStoreで渡すとき
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id == action.id) ? {
...todo,
completed: !todo.completed
} :
todo
)
default:
return state;
}
}
export default todos;
###Store
ReducerをReactにがっちゃんこ
import todoApp from '../reducers';
import { createStore } from 'redux';
let store = createStore(todoApp,window.STATE_FROM_SERVER);
###mapStateToProps
全てのstateを引数にコンポーネントに対して必要なstateをオブジェクトで渡す
Storeのデータはここに流れ着く
ここで、ストアのデータを加工して(もしくは無加工で)returnする
// container.js
// stateがstore
// state.name = 'mackay'
const mapStateToProps = state => {
return{
return {
name : state.name
}
}
}
const mapDipatchToProps = dispatch => {
//hogehoge
}
export default connect(
mapStateToProps,
mapDipatchToProps
)(TodoList)
// TodoList.jsx
const Todolist = (name) => {
console.log(name); // mackay
}
###mapDispatchToProps
全てのDispatchを引数に全てのコンポーネントに対してDispatchを渡す
dispatchするactionをfunctionで呼び出す
const mapStateToProps = state => {
// hogehoge
}
const mapDipatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDipatchToProps
)(TodoList)
export default VisibleTodoList;
// TodoList.jsx
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo => (
<Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
))}
</ul>
);
###Connect
"map〜"をコンポネートに実際に適用させる
###Provider
"Connect"で作成したコンポーネント をProviderでラッピングする
Providerのstore属性に渡すStoreにより、接続先Storeを管理
###Dipatch
actionを呼び出す
###combineReducer
複数にreducerを一つにまとめる
##ComponentとConatiner
containerは処理の流れ
componentはビューのみ
まずcontainerを呼び出して、componentを間接的に呼び出す。
"Root"から様々なcomponentを呼び出す例
- RootContainer
- RootComponent
- ConatinerA
- ComponentA
- ConatinerB
- ComponentB
- ConatinerC
- ComponentC
- ConatinerC
- ComponentB
- ConatinerA
- RootComponent
containerでstoreを実際に適用させる
componentで使いたいstateは、mapStateToPropsとmapDispatchToPropsでconnectする
###example
- index.js
- store.js
- component
- App.jsx
- List.jsx
- container
- List.js
- reducers
- idnex.js
- actions
- index.js
-
index.jsからstoreを呼び出し。store内でreducerをcreateStoreする。コンポーネントに渡し、子コンポーネントでstoreが使えるように
-
index.jsからccomponent/App.js呼び出し
-
component/App.jsxからcontainer/List.js呼び出し
-
container/List.jsではactions/index.jsを呼び出す。List.js内でmapStateToPropsで(store内で生成した)storeの状態を、mapDispatchToPropsでdispatchするactionを関数化。component/List.jsxにconnectする
-
coponent/List.jsxではcontainer/List.jsから渡されるstateとdipatchを受け取り、コンポーネントをレンタリング
-
表示成功(たぶん)
(src/)inedx.jsから直接containerを呼び出してもいい。
しかし、美しくない。
処理が肥大した際に、追加がめんどい。
conatinerの関心はあくまでreducerであり、いわゆるコンポーネント(view)はcomponentに任せるべきである
###Conatinerは分割すべし
一つのcontainerに複数のコンポーネントの呼び出しがあり、それぞれのコンポーネントに対してmap-Toをしている場合、ファックすることになる
コンポーネントの呼び出しに対して、新たなcontainerを作り、そこでcomponentを呼び出す
######Bad Container
badcontainer.js
import ~~
const BadContainer = () => {
<div>
<ComponentA
name= {this.props.nameA}
hangle= {this.props.handleA1}
/>
<ComponentB
name= {this.props.nameB}
hangle= {this.props.handleB1}
/>
</div>
}
const mapStateToProp = state => {
return {
stateA : state.a.name,
stateB : state.b.name
}
}
const mapDispatchToProps = dispatch => {
return {
handlerA1() {
dispatch(actions.actionA1())
},
handlerB1() {
dispatch(actions.actionA1())
},
fetch() {
dispatch(actions.fetchData())
}
}
}
)
export default connect(mapStateToProps, mapDispatchToProps)(BadContainer)
######Good Container
GooContainer.js
// GoodContainer.jsx
import ContainerA from './ContainerA'
import ContainerB from './ContainerB'
import * as actions from '../actions'
const GoodContainer = () => {
<div>
<ContainerA />
<ContainerB />
</div>
}
const mapDispatchToProps = dispatch => {
return {
fetch() {
dispatch(actions.fetchData())
}
}
}
export default connect(()=> { return {} }, mapDispatchToProps)(GoodContainer)
// ContainerA.jsx
import ComponentA from '../components/ComponentA'
import * as actions from '../actions'
const mapStateToProps = state => {
return {
name: state.a.name,
}
}
const mapDispatchToProps = dispatch => {
return {
handler1() {
dispatch(actions.actionA1())
},
handler2() {
dispatch(actions.actionA2())
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentA)
// ContainerB.jsx
import ComponentB from '../components/ComponentB'
import * as actions from '../actions'
const mapStateToProps = state => {
return {
name: state.b.name,
}
}
const mapDispatchToProps = dispatch => {
return {
handler1() {
dispatch(actions.actionB1())
},
handler2() {
dispatch(actions.actionB2())
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentB)