React Reduxでストアステートを極める! useSelectorフック、connect関数、MobX、Context徹底比較
React Reduxにおけるストアステートへのアクセス方法
useSelector
フックは、React Redux v6.0以降で導入された新しいフックであり、最も簡潔で現代的な方法です。このフックを使用すると、コンポーネント内で直接ストアステートにアクセスできます。
import React from 'react';
import { useSelector } from 'react-redux';
const MyComponent = () => {
const todos = useSelector(state => state.todos);
// todos変数には、ストアのtodosプロパティの値が格納されます。
return (
<div>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</div>
);
};
export default MyComponent;
connect
関数は、React Redux v6.0以前で使用されていた方法です。コンポーネントをストアに接続し、mapStateToPropsとmapDispatchToPropsという2つのプロパティを注入します。
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
todos: state.todos,
});
const mapDispatchToProps = dispatch => ({
addTodo: text => dispatch({ type: 'ADD_TODO', text }),
});
const MyComponent = ({ todos, addTodo }) => (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<input type="text" onChange={event => addTodo(event.target.value)} />
</div>
);
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
非同期処理の場合
Reduxにおける非同期処理は、通常ミドルウェアを使用して行われます。ミドルウェアは、アクションがディスパッチされた後に実行される関数を提供します。非同期処理の結果に基づいてストアステートを更新するには、ミドルウェア内でdispatch
関数を使用します。
const logger = store => next => action => {
console.log('action dispatched:', action);
const result = next(action);
console.log('new state:', store.getState());
return result;
};
const thunk = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch);
}
return next(action);
};
const store = createStore(reducer, applyMiddleware(logger, thunk));
上記の例では、logger
ミドルウェアはアクションのディスパッチと新しいステートをコンソールに記録し、thunk
ミドルウェアは非同期アクションをサポートします。
これらの方法を組み合わせることで、React Reduxアプリケーションにおけるストアステートへのアクセスと非同期処理を効果的に行うことができます。
補足
- useSelectorフックは、パフォーマンスとメモリ使用量に優れています。
- connect関数は、より詳細な制御が必要な場合に役立ちます。
- Redux DevToolsは、ストアステートとアクションをデバッグするのに役立つツールです。
React Reduxにおけるストアステートへのアクセス - サンプルコード
useSelectorフックを使用した例
この例では、useSelector
フックを使用して、ストアのtodos
プロパティにアクセスし、TODOリストを表示するコンポーネントを作成します。
import React from 'react';
import { useSelector } from 'react-redux';
const MyComponent = () => {
const todos = useSelector(state => state.todos);
return (
<div>
<h2>ToDoリスト</h2>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
};
export default MyComponent;
connect関数を使用した例
この例では、connect
関数を使用して、ストアに接続し、mapStateToPropsとmapDispatchToPropsという2つのプロパティを注入し、TODOリストを表示および新規TODOを追加するコンポーネントを作成します。
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
todos: state.todos,
});
const mapDispatchToProps = dispatch => ({
addTodo: text => dispatch({ type: 'ADD_TODO', text }),
});
const MyComponent = ({ todos, addTodo }) => {
const [newTodoText, setNewTodoText] = React.useState('');
const handleChange = event => {
setNewTodoText(event.target.value);
};
const handleSubmit = event => {
event.preventDefault();
if (newTodoText.trim()) {
addTodo(newTodoText);
setNewTodoText('');
}
};
return (
<div>
<h2>ToDoリスト</h2>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input type="text" value={newTodoText} onChange={handleChange} />
<button type="submit">追加</button>
</form>
</div>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
この例では、thunk
ミドルウェアを使用して、非同期的にAPIからTODOデータを取得し、ストアステートを更新する例を示します。
import React from 'react';
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
const FETCH_TODOS_REQUEST = 'FETCH_TODOS_REQUEST';
const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
const fetchTodosRequest = () => ({ type: FETCH_TODOS_REQUEST });
const fetchTodosSuccess = todos => ({ type: FETCH_TODOS_SUCCESS, payload: todos });
const fetchTodosFailure = error => ({ type: FETCH_TODOS_FAILURE, payload: error });
const fetchTodos = () => async dispatch => {
dispatch(fetchTodosRequest());
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
const data = await response.json();
dispatch(fetchTodosSuccess(data));
} catch (error) {
dispatch(fetchTodosFailure(error));
}
};
const reducer = (state, action) => {
switch (action.type) {
case FETCH_TODOS_REQUEST:
return {
...state,
loading: true,
error: null,
};
case FETCH_TODOS_SUCCESS:
return {
...state,
loading: false,
error: null,
todos: action.payload,
};
case FETCH_TODOS_FAILURE:
return {
...state,
loading: false,
error: action.payload,
};
default:
return state;
}
};
const MyComponent = () => {
const dispatch = useDispatch();
const todos = useSelector(state => state.todos);
const loading = useSelector(state => state.loading);
const error = useSelector(state => state.error);
React.useEffect(() => {
dispatch(fetchTodos());
}, []);
if (loading) {
return <div
import React from 'react';
import { useSelector } from 'react-redux';
const MyComponent = () => {
const todos = useSelector(state => state.todos);
// todos変数には、ストアのtodosプロパティの値が格納されます。
return (
<div>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</div>
);
};
export default MyComponent;
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
todos: state.todos,
});
const mapDispatchToProps = dispatch => ({
addTodo: text => dispatch({ type: 'ADD_TODO', text }),
});
const MyComponent = ({ todos, addTodo }) => (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<input type="text" onChange={event => addTodo(event.target.value)} />
</div>
);
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
React Contextは、Redux以外のステート管理にも使用できる、グローバルなステートを提供する仕組みです。Reduxと併用することも可能ですが、一般的にはシンプルなステート管理に適しています。
import React, { useContext } from 'react';
import { TodoContext } from './TodoContext';
const MyComponent = () => {
const { todos, dispatch } = useContext(TodoContext);
return (
<div>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
<input type="text" onChange={event => dispatch({ type: 'ADD_TODO', text: event.target.value })} />
</div>
);
};
export default MyComponent;
MobXは、オブザーバブルなステート管理ライブラリです。自動的にステートの変化を検知し、関連するコンポーネントを更新します。Reduxと同様に、単一の情報ストアを管理できますが、異なるアーキテクチャと哲学に基づいています。
import React from 'react';
import { observer } from 'mobx-react';
import { store } from './TodoStore';
const MyComponent = observer(() => {
const todos = store.todos;
return (
<div>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
<input type="text" onChange={event => store.addTodo(event.target.value)} />
</div>
);
});
export default MyComponent;
それぞれの方法の比較
方法 | 利点 | 欠点 | 適した状況 |
---|---|---|---|
useSelectorフック | 簡潔で現代的、パフォーマンスが良い | 古いバージョンのReact Reduxでは利用不可 | 最新バージョンのReact Reduxを使用している場合 |
connect関数 | 詳細な制御が可能 | useSelectorフックよりも冗長 | 複雑なステート管理が必要な場合 |
React Context | シンプルで使いやすい | グローバルステートの使用には注意が必要 | 小規模なアプリケーションやシンプルなステート管理 |
MobX | 自動オブザーバブル、コードが簡潔になる | Reduxよりも学習曲線がやや高い | 頻繁にステートが変化するアプリケーション |
どの方法を選択するかは、プロジェクトの要件と開発者の好みによって異なります。最新のReact Reduxを使用している場合は、useSelector
フックが最も簡潔で現代的な方法です。複雑なステート管理が必要な場合は、connect
関数またはMobXを検討する必要があります。React Contextは、シンプルなステート管理に適しています。
- [React Redux公式サイト](
javascript asynchronous reactjs