Step by Step で学ぶ
react + redux
Motivation
- redux 難しい...
- connect 難しい...
↓
徐々にredux を導入するサンプルがあれば、
分かりやすいのでは?
題材
メモを追加するだけアプリ
目次
1. redux 無し
2. redux 有り / react-redux無し
3. redux 有り/ react-redux有り
redux 無し
親のstateを使い回すパターン
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = { todos: [] };
this.addTodo = this.addTodo.bind(this);
}
addTodo(word) {
let id = 0;
this.setState({ todos: [...this.state.todos, { id, word }] });
++id;
}
render() {
return (
<div className="App">
<header className="App-header">
<Todo todos={this.state.todos} addTodo={this.addTodo} />
</header>
</div>
);
}
}
state を生成して、state.todo を <Todo> 渡す
Todo.js
const Todo = ({ todos, addTodo }) => {
let input;
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) return;
addTodo(input.value);
input.value = "";
}}
>
<input ref={node => (input = node)} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo, index) => (
<p key={index}>{todo.word}</p>
))}
</div>
);
};
state.todo を 描画
redux 有り /
react-redux無し
前回の問題
- state をバケツリレー
- (きっと今後) state 大量発生
今回の改善
- state を外に出して一元管理
App.js
const store = createStore(todoApp)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {todos: []};
store.subscribe(() => {
this.setState({todos: store.getState().todos})
});
}
render() {
return (
<div className="App">
<header className="App-header">
<Todo store={store} todos={this.state.todos} />
</header>
</div>
);
}
}
redux の createStore で store を作成
Todo.js
const Todo = ({ store, todos }) => {
let input;
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) return;
store.dispatch(addTodo(input.value))
input.value = "";
}}
>
<input ref={node => (input = node)} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo, index) => (
<p key={index}>{todo.text}</p>
))}
</div>
);
};
store.dispatch で addTodo を実行
Actions.js
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
action を作る関数
Reducer.js
import { combineReducers } from 'redux'
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
}
]
default:
return state
}
}
export default combineReducers({
todos,
})
store.dispatch で action を受ける
action.type に応じて store を更新する
redux 有り /
react-redux 有り
前回の問題
- store をバケツリレー
今回の改善
- 下のコンポーネントから直接に store を参照したい
- 下のコンポーネントから直接に action を参照したい
App.js
const store = createStore(todoApp)
class App extends React.Component {
render() {
return (
<div className="App">
<Provider store={store}>
<header className="App-header">
<Todo />
</header>
</Provider>
</div>
);
}
}
react-redux の Provider で
store の変更を下のコンポーネントに伝える
Todo.js
const Todo = props => {
const { todos, addTodo } = props;
let input;
return (
<div>
// 略
{todos.map((todo, index) => (
<p key={index}>{todo.text}</p>
))}
</div>
);
};
export default connect(
state => ({
todos: state.todos
}),
dispatch => ({
addTodo: text => dispatch(addTodo(text))
})
)(Todo);
react-redux の connent で
store に直接アクセスする
Reducer.js
Action.js
前回と同じ!!
今回のまとめ
- redux は 一番の親のstate を外に切り出したもの(=store)
- react-redux は store をreact で「楽に」使えるようにするもの
おまけ (hooks)
前回の問題
- connect 書くのダルい
今回の改善
- connect 書かなくていい
const Todo = () => {
const dispatch = useDispatch()
const todos = useSelector(state => state.todos)
const handleAddTodo = (word) => dispatch(addTodo(word))
let input;
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
handleAddTodo(input.value);
input.value = "";
}}
>
<input ref={node => (input = node)} />
<button type="submit">Add Todo</button>
</form>
{todos.map((todo, index) => (
<p key={index}>{todo.text}</p>
))}
</div>
);
};