Redux とは何ですか?
Redux は、アプリケーションの状態を個別に管理する JS アプリ用の柔軟な状態コンテナーです。アプリケーションの状態を 1 つのストアで管理するため、アプリ全体にわたる複雑な状態ロジックの処理が容易になります。
Redux を使用する理由
通常のフローでは、コンポーネント間で状態を渡すためにプロップドリルを行う必要があります。一部のレベルではここでの状態を必要としませんが、これは負担です。また、大規模な中型アプリの状態を引き上げるには構造的な変更が必要なため、スケーラブルなソリューションではありません。だからこそ、状態を管理するために redux が必要なのです。ここでのすべての状態はストアに保持され、必要なコンポーネントはそのストアにサブスクライブするだけで済みます。 Redux は、一方向のデータ フローを強制することで、予測可能な状態管理、デバッグの容易化、スケーラビリティの向上を保証します。
アクション: 何が起こったかを説明するオブジェクト。通常、これにはタイプとオプションのペイロードが含まれます。 (コマンド)
Dispatch: 状態を更新するためにストアにアクションを送信するために使用される関数。 (イベント発生中)
Reducer: 現在の状態とアクションを受け取り、新しい状態を返す純粋な関数。 (アクションのディスパッチ時にトリガーされる関数)
インストール: npm i @reduxjs/toolkit reverse-redux
スライスの作成:
スライスは、単一の機能に対する Redux Reducer ロジックとアクションのコレクションです。 prepare コールバックを使用すると、アクション ペイロードがリデューサーに到達する前にカスタマイズできます。
import { createSlice, nanoid } from "@reduxjs/toolkit"; const postSlice = createSlice({ name: "posts", initialState: [], reducers: { addPost: { reducer: (state, action) => { state.push(action.payload); }, prepare: (title, content) => ({ payload: { id: nanoid(), title, content }, }), }, deletePost: (state, action) => { return state.filter((post) => post.id != action.payload); }, }, }); export const { addPost, deletePost } = postSlice.actions; export default postSlice.reducer;
ストアの作成:
import { configureStore } from "@reduxjs/toolkit"; import postReducer from "../features/posts/postSlice"; export const store = configureStore({ reducer: { posts: postReducer }, });
プロバイダーでラップ:
import { Provider } from "react-redux"; import { store } from "./app/store.jsx"; createRoot(document.getElementById("root")).render( <StrictMode> <Provider store={store}> <App /> </Provider> </StrictMode> );
コンポーネントで使用:
const PostList = ({ onEdit }) => { const posts = useSelector((state) => state.posts); const dispatch = useDispatch(); return ( <div className="w-full grid grid-cols-1 gap-6 mt-12"> {posts.map((post) => ( <div key={post.id}></div> ))} </div> ); };
Redux ブラウザ拡張機能: Redux DevTools
const store = configureStore({ reducer: rootReducer, devTools: process.env.NODE_ENV !== 'production', });
Redux では、非同期操作 (API 呼び出しなど) はミドルウェアを使用して処理されます。これは、Redux がデフォルトで同期状態更新のみをサポートしているためです。非同期操作を処理するための最も一般的なミドルウェアは、Redux Thunk、createAsyncThunk を備えた Redux Toolkit (RTK)、および Redux Saga です。
実装:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // Fetch all posts export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); return response.json(); }); // Initial State const initialState = { posts: [], post: null, loading: false, error: null, }; // Slice const postsSlice = createSlice({ name: 'posts', initialState, reducers: {}, extraReducers: (builder) => { builder // Fetch all posts .addCase(fetchPosts.pending, (state) => { state.loading = true; }) .addCase(fetchPosts.fulfilled, (state, action) => { state.loading = false; state.posts = action.payload; }) .addCase(fetchPosts.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }) }, }); export default postsSlice.reducer;
使用例:
import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { fetchPosts, createPost, updatePost, deletePost } from './postsSlice'; const Posts = () => { const dispatch = useDispatch(); const { posts, loading, error } = useSelector((state) =>state.posts); useEffect(() => { dispatch(fetchPosts()); }, [dispatch]); const handleCreate = () => { dispatch(createPost({ title: 'New Post', body: 'This is a new post' })); }; if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return ( <div> <h1>Posts</h1> <button onClick={handleCreate}>Create Post</button> </div> ); }; export default Posts;
ミドルウェア
Redux のミドルウェアはディスパッチされたアクションをインターセプトし、ロギング、クラッシュ レポート、または非同期ロジックの処理を可能にします。ミドルウェアを使用すると、ディスパッチプロセスをカスタマイズできます。
const blogPostMiddleware = (storeAPI) => (next) => (action) => { if (action.type === 'posts/publishPost') { const contentLength = action.payload.content.length; if (contentLength < 50) { console.warn('Post content is too short. Must be at least 50 characters.'); return; } console.log('Publishing post:', action.payload.title); } return next(action); }; const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(blogPostMiddleware), });
セレクター
セレクターは、状態の特定の部分にアクセスするのに役立ちます。
export const selectCount = (state) => state.counter.value;
エラー処理
適切な状態管理によりエラーを効果的に処理します。
import { createSlice, nanoid } from "@reduxjs/toolkit"; const postSlice = createSlice({ name: "posts", initialState: [], reducers: { addPost: { reducer: (state, action) => { state.push(action.payload); }, prepare: (title, content) => ({ payload: { id: nanoid(), title, content }, }), }, deletePost: (state, action) => { return state.filter((post) => post.id != action.payload); }, }, }); export const { addPost, deletePost } = postSlice.actions; export default postSlice.reducer;
RTK クエリは、データのフェッチ、キャッシュ、同期を簡素化します。 RTK クエリはリクエストを自動的にキャッシュし、不必要な再フェッチを回避してパフォーマンスを向上させます。
RTK クエリのセットアップ
import { configureStore } from "@reduxjs/toolkit"; import postReducer from "../features/posts/postSlice"; export const store = configureStore({ reducer: { posts: postReducer }, });
コンポーネントでの使用法
import { Provider } from "react-redux"; import { store } from "./app/store.jsx"; createRoot(document.getElementById("root")).render( <StrictMode> <Provider store={store}> <App /> </Provider> </StrictMode> );
Immer を使用すると、内部で更新を不変に保ちながら、状態を直接「変更」するロジックを作成できます。
const PostList = ({ onEdit }) => { const posts = useSelector((state) => state.posts); const dispatch = useDispatch(); return ( <div className="w-full grid grid-cols-1 gap-6 mt-12"> {posts.map((post) => ( <div key={post.id}></div> ))} </div> ); };
Mutate: データを直接変更します。たとえば、オブジェクトまたは配列を変更します。
不変: データを直接変更する代わりに、元のデータはそのままにして、変更を適用した新しいコピーを作成します。
Immer の仕組み
Immer は、データを変更している (つまり、データを直接変更している) ように見えるコードを作成するのに役立ちますが、内部では自動的に変更を不変に保ちます。これは、JavaScript で不変のデータ構造を扱うときによくあるバグを回避するのに役立ちます。
例: Immer なし (突然変異):
const store = configureStore({ reducer: rootReducer, devTools: process.env.NODE_ENV !== 'production', });
Immer あり (不変性):
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // Fetch all posts export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => { const response = await fetch('https://jsonplaceholder.typicode.com/posts'); return response.json(); }); // Initial State const initialState = { posts: [], post: null, loading: false, error: null, }; // Slice const postsSlice = createSlice({ name: 'posts', initialState, reducers: {}, extraReducers: (builder) => { builder // Fetch all posts .addCase(fetchPosts.pending, (state) => { state.loading = true; }) .addCase(fetchPosts.fulfilled, (state, action) => { state.loading = false; state.posts = action.payload; }) .addCase(fetchPosts.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }) }, }); export default postsSlice.reducer;
これにより、手動で状態を複製して更新する必要がなくなるため、Redux (またはその他の状態管理) の操作が容易になります。 Immer はそれを自動的に実行します。
ページが更新されても Redux 状態を維持するには、Redux Persist を統合します。これにより、Redux の状態がローカル ストレージまたはセッション ストレージに保存され、アプリが更新されたときに再ロードされます。
インストール:
npm install redux-persist
実装:
import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { fetchPosts, createPost, updatePost, deletePost } from './postsSlice'; const Posts = () => { const dispatch = useDispatch(); const { posts, loading, error } = useSelector((state) =>state.posts); useEffect(() => { dispatch(fetchPosts()); }, [dispatch]); const handleCreate = () => { dispatch(createPost({ title: 'New Post', body: 'This is a new post' })); }; if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return ( <div> <h1>Posts</h1> <button onClick={handleCreate}>Create Post</button> </div> ); }; export default Posts;
パーシットゲートでラップ:
const blogPostMiddleware = (storeAPI) => (next) => (action) => { if (action.type === 'posts/publishPost') { const contentLength = action.payload.content.length; if (contentLength < 50) { console.warn('Post content is too short. Must be at least 50 characters.'); return; } console.log('Publishing post:', action.payload.title); } return next(action); }; const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(blogPostMiddleware), });
localStorage の代わりに sessionStorage を使用します:
ストレージをセッションベースに変更します (ブラウザを閉じるとクリアされます):
initialState: { items: [], status: 'idle', error: null, }, .addCase(fetchData.rejected, (state, action) => { state.status = 'failed'; state.error = action.error.message; });
選択的永続性:
状態の特定のスライスのみを永続化します:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; const api = createApi({ reducerPath: 'api', baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com' }), endpoints: (builder) => ({ getPosts: builder.query({ query: () => '/posts', }), getPostById: builder.query({ query: (id) => `/posts/${id}`, }), createPost: builder.mutation({ query: (newPost) => ({ url: '/posts', method: 'POST', body: newPost, }), }), updatePost: builder.mutation({ query: ({ id, ...updatedPost }) => ({ url: `/posts/${id}`, method: 'PUT', body: updatedPost, }), }), deletePost: builder.mutation({ query: (id) => ({ url: `/posts/${id}`, method: 'DELETE', }), }), }), }); export const { useGetPostsQuery, useGetPostByIdQuery, useCreatePostMutation, useUpdatePostMutation, useDeletePostMutation, } = api; export default api;
CRUD 機能を備えた React、redux、ant デザインを使用してシンプルなブログ プロジェクトを作成しました。ぜひチェックしてみてください。
プロジェクト リンク - Redux ブログ アプリ
? Redux Toolkit をマスターして React アプリをレベルアップしましょう!
以上がReact 開発者向けの包括的な Redux ツールキットのメモの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。