Redux 入門(サンプルコードを読む)
React/Redux初心者の私が、Reduxのサンプルコードを見てみる回です。 https://github.com/reduxjs/redux/tree/master/examples
対象としては「用語はわかるけどどうやって使うのかいまいちわからん」な人です。 つまり私です。
ディレクトリ構成
見てみる。
手元でビルドできない。
import React from 'react' import Footer from './Footer' import AddTodo from '../containers/AddTodo' import VisibleTodoList from '../containers/VisibleTodoList' const App = () => ( <div> <AddTodo /> <VisibleTodoList /> <Footer /> </div> ) export default App
まあ、Todo作成する部分と、表示する部分と、フッターがあるみたい。 表示する部分から見てみよう
import { connect } from 'react-redux' import { toggleTodo } from '../actions' import TodoList from '../components/TodoList' import { VisibilityFilters } from '../actions' const getVisibleTodos = (todos, filter) => { switch (filter) { case VisibilityFilters.SHOW_ALL: return todos case VisibilityFilters.SHOW_COMPLETED: return todos.filter(t => t.completed) case VisibilityFilters.SHOW_ACTIVE: return todos.filter(t => !t.completed) default: throw new Error('Unknown filter: ' + filter) } } const mapStateToProps = state => ({ todos: getVisibleTodos(state.todos, state.visibilityFilter) }) const mapDispatchToProps = dispatch => ({ toggleTodo: id => dispatch(toggleTodo(id)) }) export default connect( mapStateToProps, mapDispatchToProps )(TodoList)
おっ、Viewを持っていない? Viewの本体はこっちみたいだ。
import React from 'react' import PropTypes from 'prop-types' import Todo from './Todo' const TodoList = ({ todos, toggleTodo }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} /> )} </ul> ) TodoList.propTypes = { todos: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired }).isRequired).isRequired, toggleTodo: PropTypes.func.isRequired } export default TodoList
connectってなんだろう?
export default connect( mapStateToProps, mapDispatchToProps )(TodoList)
なんだろう。 Reduxライブラリからimportしている。
https://react-redux.js.org/api/connect
The connect() function connects a React component to a Redux store.
おー。つまりVisibleTodoList.jsがStoreなんだね。 StoreがViewに依存する状態。
じゃあこの引数で渡しているのは何?
mapStateToProps, mapDispatchToPropsってなんだろう?
React ComponentとStoreをconnectする(きっと、StoreがComponentに対してイベントを送れる状態)時に引数で渡すこのふたつ。
const mapStateToProps = state => ({ todos: getVisibleTodos(state.todos, state.visibilityFilter) }) const mapDispatchToProps = dispatch => ({ toggleTodo: id => dispatch(toggleTodo(id)) })
stateはまあわかる。
const mapStateToProps = state => ({ todos: getVisibleTodos(state.todos, state.visibilityFilter) })
const getVisibleTodos = (todos, filter) => { switch (filter) { case VisibilityFilters.SHOW_ALL: return todos case VisibilityFilters.SHOW_COMPLETED: return todos.filter(t => t.completed) case VisibilityFilters.SHOW_ACTIVE: return todos.filter(t => !t.completed) default: throw new Error('Unknown filter: ' + filter) } }
リストとTypeを渡すとフィルターしたTodoのリストを返すような関数を渡している。 それがPropsとしてViewに渡り、それをmapして描画している。 じゃあgetVisibleTodoListに引数を渡すのは誰なんだろう?
とりあえずその下、dispatchも見てみよう。
const mapDispatchToProps = dispatch => ({ toggleTodo: id => dispatch(toggleTodo(id)) })
toggleTodoというのはActionだ。
export const toggleTodo = id => ({ type: 'TOGGLE_TODO', id })
StoreにtoffleTodoというActionをディスパッチしている。で、それもViewにconnectされる。 Viewの中で
const TodoList = ({ todos, toggleTodo }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} /> )} </ul> )
というのがあるから、セルをタップするとそのTodoの状態が変わるのかな。 このActionがどうやってViewに伝わるのか。Reducerにわたるはずやけど。
と思っていたら、鍵はこれだった
import React from 'react' import { render } from 'react-dom' import { createStore } from 'redux' import { Provider } from 'react-redux' import App from './components/App' import rootReducer from './reducers' const store = createStore(rootReducer) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
いっちばん上でrootReducerからStoreを作って、下位コンポーネント全てにぶん投げている。 つまり単一のストアをアプリケーション全体で共有している状態。 そしてこのrootReducerというのは
import { combineReducers } from 'redux' import todos from './todos' import visibilityFilter from './visibilityFilter' export default combineReducers({ todos, visibilityFilter })
こいつを指している。 これはさらに
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
こいつらを表している。
rootReducerってなんだ?
React.js - rootReducerってなんですか?|teratail なるほど。。
createStoreとは?
reducerからStoreを作る。 Storeはreduser毎にstateを作成する?
Storeとは?
わからんち
Providerとは?
これってもしかして、その下位コンポーネントがStoreの役割を持てる(connectとかdispatcherを自分の関数のように扱える)ことと関係がある?ありそう。 Providerしたらその下位コンポーネントは渡したStoreの役割を持てるっぽい。
一旦まとめ
流れとしては、 1. ルートのindex.jsが呼ばれる 2. rootReducer(reducersディレクトリ 直下のcombineReducer)を使ってStoreを作成する 3. storeが下位コンポーネントにぶん投げられる?(Provider) 4. VisibleTodoList.jsでViewとStoreをconnectする(その時にストアが更新されるたび呼び出される関数とstateを書き換えるdispatchを定義する) 5. Viewでdispatchを呼び出す 6. dispatchがアクションを発火させてreducerがstoreのstateを更新する 7. storeの更新を検知してViewのストアが更新されるたび呼び出される関数が走る 8. それに応じてViewが再レンダリングされる
参考リンク