- React: การจัดการ State ด้วย Redux Toolkit
- 1. State ใน React คืออะไร
- 2. Redux Toolkit คืออะไร
- 3. Redux Toolkit Data Flow
- 4. สร้าง React Project ด้วย Vite
- 5. ติดตั้ง Redux Toolkit
- react-redux เชื่อม React Component เข้ากับ ReduxStore
- 6. โครงสร้าง Project
- 7. สร้าง Slice ด้วย createSlice
- 8. ทำไม Redux Toolkit แก้ State แบบนี้ได้
- 9. สร้าง Redux Store
- 10. เชื่อม Redux Store กับ React
- 11. อ่าน State ด้วย useSelector
- 12. เปลี่ยน State ด้วย useDispatch
- 13. ทำความเข้าใจ dispatch
- 14. Redux Flow แบบเต็ม
- 15. การจัดการหลาย Slice
- 16. จัดการ API ด้วย createAsyncThunk
- 17. จัดการ Async State ด้วย extraReducers
- 18. เรียก API จาก Component
- 19. RTK Query
- 20. useState หรือ Redux Toolkit?
- 21. Best Practices
- 22. สรุปคำสั่งและ API สำคัญ
- 23. Roadmap สำหรับเรียน Redux Toolkit
- สรุป
- References
#React: การจัดการ State ด้วย Redux Toolkit
การพัฒนา React Application เมื่อระบบมีขนาดใหญ่ขึ้น มักพบว่า State ไม่ได้ถูกใช้งานอยู่เพียง Component เดียว แต่ต้องแชร์ข้อมูลระหว่างหลาย Component หรือหลายหน้า เช่น ข้อมูลผู้ใช้ที่ Login, Shopping Cart, Theme, Filters หรือข้อมูลจาก API
หนึ่งในเครื่องมือยอดนิยมสำหรับจัดการ Global State คือ Redux Toolkit (RTK) ซึ่งเป็นแนวทางมาตรฐานที่ Redux แนะนำสำหรับการเขียน Redux ในปัจจุบัน ช่วยลด Boilerplate Code และทำให้การสร้าง Store, Reducer และ Action ง่ายกว่า Redux แบบเดิมอย่างมาก
บทความนี้จะทดลองสร้าง React Application ที่จัดการ State ด้วย Redux Toolkit ตั้งแต่พื้นฐานจนถึงการเรียก API แบบ Asynchronous
#1. State ใน React คืออะไร
State คือข้อมูลที่สามารถเปลี่ยนแปลงได้ระหว่างการทำงานของ Application และเมื่อ State เปลี่ยน React จะอัปเดต UI ที่เกี่ยวข้อง
ตัวอย่าง Local State:
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
useState เหมาะกับข้อมูลที่ใช้เฉพาะภายใน Component หรือ Component Tree ขนาดเล็ก
แต่เมื่อข้อมูลต้องถูกใช้งานหลายส่วน เช่น
- Authentication
- User Profile
- Shopping Cart
- Application Settings
- Shared Filters
- Workflow State
การใช้ Global State Management จะช่วยให้โครงสร้างระบบชัดเจนขึ้น
#2. Redux Toolkit คืออะไร
Redux Toolkit เป็น Official Toolset สำหรับพัฒนา Redux Application โดยมี API สำคัญ เช่น
configureStore()สำหรับสร้าง Redux StorecreateSlice()สำหรับสร้าง State, Reducer และ ActioncreateAsyncThunk()สำหรับจัดการงาน Asynchronous- RTK Query สำหรับ Data Fetching และ Caching
เว็บไซต์อย่างเป็นทางการ:
React Redux:
#3. Redux Toolkit Data Flow
แนวคิดพื้นฐานของ Redux เป็น One-way Data Flow
React Component
|
| dispatch(action)
v
Action
|
v
Reducer
|
v
Redux Store
|
| useSelector()
v
React Component
Component จะไม่แก้ Global State โดยตรง แต่ส่ง Action ผ่าน dispatch() จากนั้น
Reducer จะประมวลผลและปรับ State ใน Store
#4. สร้าง React Project ด้วย Vite
สร้าง Project:
npm create vite@latest redux-demo -- --template react
เข้าไปใน Project:
cd redux-demo
ติดตั้ง Dependency:
npm install
ทดลองรัน:
npm run dev
#5. ติดตั้ง Redux Toolkit
ติดตั้ง Package:
npm install @reduxjs/toolkit react-redux
Package มีหน้าที่ต่างกันดังนี้
Package หน้าที่
@reduxjs/toolkit สร้าง Store, Slice, Reducer, Async
Logic และ RTK Query
#react-redux เชื่อม React Component เข้ากับ Redux
Store
#6. โครงสร้าง Project
ตัวอย่างโครงสร้างแบบ Feature-based:
src/
├── app/
│ └── store.js
├── features/
│ └── counter/
│ └── counterSlice.js
├── App.jsx
└── main.jsx
เมื่อ Application ใหญ่ขึ้นสามารถเพิ่ม Feature ได้ เช่น
features/
├── auth/
├── cart/
├── products/
└── counter/
#7. สร้าง Slice ด้วย createSlice
สร้างไฟล์:
src/features/counter/counterSlice.js
เพิ่ม Code:
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
value: 0
}
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
}
}
})
export const {
increment,
decrement,
incrementByAmount
} = counterSlice.actions
export default counterSlice.reducer
createSlice() จะสร้างทั้ง Reducer และ Action Creator ให้อัตโนมัติ
#8. ทำไม Redux Toolkit แก้ State แบบนี้ได้
ใน Redux แบบดั้งเดิมไม่ควรแก้ State โดยตรง
แต่ Redux Toolkit สามารถเขียน:
state.value += 1
ได้ เพราะ Redux Toolkit ใช้ Immer ภายใน ทำให้ Code ที่ดูเหมือน Mutation ถูกแปลงเป็น Immutable Update อย่างถูกต้อง
ดังนั้นจึงไม่จำเป็นต้องเขียนแบบนี้ใน Reducer ของ createSlice():
return {
...state,
value: state.value + 1
}
#9. สร้าง Redux Store
สร้างไฟล์:
src/app/store.js
เพิ่ม:
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
State จะมีโครงสร้าง:
{
counter: {
value: 0
}
}
#10. เชื่อม Redux Store กับ React
แก้ไข src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import App from './App.jsx'
import { store } from './app/store'
createRoot(document.getElementById('root')).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
)
Provider ทำหน้าที่ส่ง Redux Store ให้ Component ภายใน Application
สามารถใช้งานได้
#11. อ่าน State ด้วย useSelector
React Redux มี Hook ชื่อ useSelector() สำหรับอ่านข้อมูลจาก Store
import { useSelector } from 'react-redux'
function Counter() {
const count = useSelector(
(state) => state.counter.value
)
return <h1>{count}</h1>
}
Flow:
Redux Store
|
v
state.counter.value
|
v
useSelector()
|
v
Component
เมื่อค่าที่ Component เลือกเปลี่ยน React Redux จะทำให้ Component ที่เกี่ยวข้อง Render ใหม่
#12. เปลี่ยน State ด้วย useDispatch
ใช้ useDispatch() สำหรับส่ง Action ไปยัง Redux Store
แก้ไข App.jsx
import { useDispatch, useSelector } from 'react-redux'
import {
increment,
decrement,
incrementByAmount
} from './features/counter/counterSlice'
function App() {
const count = useSelector(
(state) => state.counter.value
)
const dispatch = useDispatch()
return (
<div>
<h1>Redux Toolkit Counter</h1>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>
+1
</button>
<button onClick={() => dispatch(decrement())}>
-1
</button>
<button
onClick={() => dispatch(incrementByAmount(10))}
>
+10
</button>
</div>
)
}
export default App
#13. ทำความเข้าใจ dispatch
เมื่อเขียน:
dispatch(increment())
Redux Toolkit จะสร้าง Action ประมาณ:
{
type: 'counter/increment'
}
ถ้าเขียน:
dispatch(incrementByAmount(10))
Action จะมี Payload:
{
type: 'counter/incrementByAmount',
payload: 10
}
จากนั้น Reducer ที่เกี่ยวข้องจะนำ Payload ไปปรับ State
#14. Redux Flow แบบเต็ม
ตัวอย่างเมื่อผู้ใช้กดปุ่ม +10
User
|
v
Button
|
v
dispatch(incrementByAmount(10))
|
v
Action
|
| type: counter/incrementByAmount
| payload: 10
|
v
Reducer
|
v
state.value += 10
|
v
Redux Store
|
v
useSelector()
|
v
React UI
นี่คือแนวคิดหลักที่ควรเข้าใจเมื่อเริ่มใช้งาน Redux
#15. การจัดการหลาย Slice
Application จริงมักมี State หลาย Domain
ตัวอย่าง:
src/features/
├── auth/
│ └── authSlice.js
├── cart/
│ └── cartSlice.js
├── products/
│ └── productSlice.js
└── counter/
└── counterSlice.js
สามารถนำ Reducer ทั้งหมดมาใส่ใน Store:
import { configureStore } from '@reduxjs/toolkit'
import authReducer from '../features/auth/authSlice'
import cartReducer from '../features/cart/cartSlice'
import productReducer from '../features/products/productSlice'
import counterReducer from '../features/counter/counterSlice'
export const store = configureStore({
reducer: {
auth: authReducer,
cart: cartReducer,
products: productReducer,
counter: counterReducer
}
})
Global State จะมีลักษณะ:
Redux Store
|
+-- auth
| +-- user
| +-- token
|
+-- cart
| +-- items
|
+-- products
| +-- data
|
+-- counter
+-- value
#16. จัดการ API ด้วย createAsyncThunk
Redux Toolkit มี createAsyncThunk() สำหรับจัดการ Asynchronous Logic เช่น
REST API
ตัวอย่าง:
import {
createAsyncThunk,
createSlice
} from '@reduxjs/toolkit'
export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async () => {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
)
if (!response.ok) {
throw new Error('Unable to fetch users')
}
return response.json()
}
)
createAsyncThunk() จะสร้าง Action Lifecycle ให้อัตโนมัติ ได้แก่
pending
fulfilled
rejected
#17. จัดการ Async State ด้วย extraReducers
ตัวอย่าง User Slice:
const initialState = {
users: [],
loading: false,
error: null
}
const userSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true
state.error = null
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false
state.users = action.payload
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false
state.error = action.error.message
})
}
})
export default userSlice.reducer
#18. เรียก API จาก Component
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchUsers } from './features/users/userSlice'
function UserList() {
const dispatch = useDispatch()
const {
users,
loading,
error
} = useSelector((state) => state.users)
useEffect(() => {
dispatch(fetchUsers())
}, [dispatch])
if (loading) {
return <p>Loading...</p>
}
if (error) {
return <p>Error: {error}</p>
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
export default UserList
#19. RTK Query
ถ้า Application มีการเรียก API จำนวนมาก Redux Toolkit มี RTK Query ซึ่งออกแบบมาสำหรับ Server State โดยเฉพาะ
RTK Query ช่วยจัดการ:
- Data Fetching
- Caching
- Loading State
- Error State
- Cache Invalidation
- Refetching
- Mutation
ตัวอย่าง API Definition:
import {
createApi,
fetchBaseQuery
} from '@reduxjs/toolkit/query/react'
export const userApi = createApi({
reducerPath: 'userApi',
baseQuery: fetchBaseQuery({
baseUrl: 'https://jsonplaceholder.typicode.com/'
}),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users'
})
})
})
export const {
useGetUsersQuery
} = userApi
Component สามารถเรียกใช้ Hook ที่ RTK Query สร้างให้:
function UserList() {
const {
data: users,
isLoading,
error
} = useGetUsersQuery()
if (isLoading) {
return <p>Loading...</p>
}
if (error) {
return <p>Error</p>
}
return (
<ul>
{users?.map((user) => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
จึงไม่จำเป็นต้องสร้าง loading, error และ Data Fetching Logic เองในทุก
Slice
#20. useState หรือ Redux Toolkit?
ไม่ควรนำ State ทุกตัวเข้า Redux Store
สถานการณ์ เครื่องมือที่เหมาะสม
Input ภายใน Component useState
Modal เปิด/ปิดเฉพาะหน้า useState
State แชร์หลาย Component Redux Toolkit
Authentication State Redux Toolkit
Shopping Cart Redux Toolkit
Complex Client Workflow Redux Toolkit
Server Data/API Cache RTK Query
หลักคิดสำคัญคือใช้ Redux เมื่อ State มีความเป็น Shared/Global จริง ๆ ไม่ใช่เพียงเพราะต้องการรวม State ทุกอย่างไว้ที่เดียว
#21. Best Practices
#แยก State ตาม Feature
ควรใช้:
features/
├── auth/
├── cart/
└── products/
แทนการรวม Reducer ทั้งหมดไว้ในไฟล์เดียว
#เก็บ State เท่าที่จำเป็น
หลีกเลี่ยงการเก็บค่าที่สามารถคำนวณจาก State เดิมได้ หากคำนวณผ่าน Selector ได้
#ไม่ใส่ Non-serializable Data โดยไม่จำเป็น
Redux State ควรเน้นข้อมูลที่ Serialize ได้ เช่น Object, Array, String, Number และ Boolean
#ใช้ RTK Query สำหรับ Server State
หากข้อมูลหลักมาจาก REST API และต้องการ Cache/Refetch/Invalidation ควรพิจารณา
RTK Query ก่อนเขียน createAsyncThunk() จำนวนมาก
#ใช้ Redux DevTools
configureStore() รองรับ Redux DevTools ใน Development ทำให้สามารถตรวจสอบ
Action และ State Transition ได้สะดวก
#22. สรุปคำสั่งและ API สำคัญ
API หน้าที่
configureStore() สร้าง Redux Store
createSlice() สร้าง Slice, Reducer และ Action
Provider เชื่อม Store กับ React
useSelector() อ่าน State
useDispatch() Dispatch Action
createAsyncThunk() จัดการ Async Logic
createApi() สร้าง RTK Query API
#23. Roadmap สำหรับเรียน Redux Toolkit
แนะนำให้เรียนตามลำดับ:
React State
|
v
useState
|
v
Redux Concept
|
v
configureStore
|
v
Provider
|
v
createSlice
|
v
useSelector
|
v
useDispatch
|
v
createAsyncThunk
|
v
RTK Query
หากเข้าใจ Store -> Action -> Reducer -> State -> UI จะสามารถต่อยอดไปยัง
Redux Toolkit ในระบบ React ขนาดใหญ่ได้ง่ายขึ้น
#สรุป
Redux Toolkit ทำให้การใช้งาน Redux ใน React ง่ายและเป็นระบบกว่า Redux
แบบดั้งเดิม โดยหัวใจสำคัญคือ configureStore(), createSlice(),
useSelector() และ useDispatch()
สำหรับ Client State ที่ต้องแชร์ระหว่างหลายส่วนของ Application Redux Toolkit เป็นตัวเลือกที่เหมาะสม ส่วนข้อมูลจาก Server หรือ REST API ที่ต้องมี Caching, Refetching และ Cache Invalidation ควรพิจารณา RTK Query
แนวทางสำคัญคือ ไม่จำเป็นต้องใช้ Redux กับ State ทุกตัว แต่เลือกใช้ Redux Toolkit เมื่อ State มีขอบเขตการใช้งานร่วมกันหรือมีความซับซ้อนเพียงพอ
#References
- Redux Toolkit: https://redux-toolkit.js.org/
- Redux Toolkit Quick Start: https://redux-toolkit.js.org/tutorials/quick-start
- React Redux: https://react-redux.js.org/
- RTK Query: https://redux-toolkit.js.org/rtk-query/overview