Redux: การจัดการ State ส่วนกลางของแอป
ในบทที่แล้ว เราได้เรียนรู้การจัดการ Form ด้วย Formik และ Yup ซึ่งเป็นเครื่องมือที่ยอดเยี่ยมสำหรับการจัดการ State เฉพาะใน Form นั้นๆ แต่สำหรับแอปพลิเคชันขนาดใหญ่ที่มี Components จำนวนมากและ State ที่ซับซ้อน การจัดการ State แบบ Local ในแต่ละ Component อาจทำให้เกิดปัญหา Data inconsistency (ความไม่สอดคล้องกันของข้อมูล), ยากต่อการ Debug, และยากต่อการ Maintain ในระยะยาว ดังนั้นเราจึงต้องการเครื่องมือที่ช่วยจัดการ State ส่วนกลางของแอป ซึ่ง Redux คือคำตอบสำหรับปัญหานี้
Redux คืออะไร?
Redux เป็น Library สำหรับจัดการ State ในแอปพลิเคชัน JavaScript (รวมถึง React Native) โดยมีหลักการสำคัญคือ:
- Single Source of Truth: State ทั้งหมดของแอปจะถูกเก็บไว้ใน Store เดียว
- State is Read-Only: วิธีเดียวที่จะเปลี่ยน State ได้คือการ dispatch Action
- Changes are Made with Pure Functions: Reducer คือ Function ที่รับ State ปัจจุบันและ Action แล้วคืน State ใหม่ (Immutable)
พูดง่ายๆ คือ Redux เปรียบเสมือน "ศูนย์กลางควบคุมข้อมูล" ของแอป ที่ทุก Component สามารถเข้าถึงและแก้ไขข้อมูลได้ผ่านกระบวนการที่กำหนดไว้ ทำให้การจัดการ State เป็นระเบียบและคาดเดาได้ง่ายขึ้น
Trade-offs: การใช้ Redux เพิ่มความซับซ้อนให้กับโปรเจกต์เล็กๆ ได้ เนื่องจากต้องเขียน Boilerplate Code (โค้ดที่ต้องเขียนซ้ำๆ) จำนวนมาก แต่สำหรับโปรเจกต์ขนาดใหญ่ที่มี State ที่ซับซ้อน Redux จะช่วยให้การจัดการ State ง่ายขึ้นและลดโอกาสเกิด Bug ได้อย่างมาก
ทำไมต้องใช้ Redux ใน React Native?
ใน React Native เราสามารถใช้ Context API ในการจัดการ State ส่วนกลางได้ แต่ Redux มีข้อดีกว่าในเรื่องของ:
- Predictability: Redux มีหลักการที่ชัดเจนในการจัดการ State ทำให้เราสามารถคาดเดาได้ว่า State จะเปลี่ยนแปลงไปอย่างไร
- Debuggability: Redux DevTools ช่วยให้เราสามารถ Debug State ได้อย่างง่ายดาย โดยสามารถดู Action ที่ถูก Dispatch, State ก่อนและหลัง Action, และ Time Travel เพื่อย้อนเวลากลับไปดู State ในอดีตได้
- Maintainability: Redux ช่วยให้ Codebase เป็นระเบียบและง่ายต่อการ Maintain ในระยะยาว
การติดตั้ง Redux และ React Redux
ก่อนที่เราจะเริ่มใช้งาน Redux ได้ เราต้องติดตั้ง Library ที่เกี่ยวข้องก่อน โดยใช้ npm หรือ yarn:
npm install redux react-redux
# หรือ
yarn add redux react-redux
- redux: Library หลักของ Redux
- react-redux: Library ที่ช่วยให้เราเชื่อมต่อ React Components กับ Redux Store ได้ง่ายขึ้น
องค์ประกอบหลักของ Redux
Redux ประกอบด้วย 3 ส่วนประกอบหลัก:
1. Store
Store คือที่เก็บ State ทั้งหมดของแอปพลิเคชัน เปรียบเสมือน "คลังข้อมูล" ที่ Component สามารถเข้าถึงได้
ตัวอย่างการสร้าง Store:
import { createStore } from 'redux';
import rootReducer from './reducers'; // เดี๋ยวเราจะสร้าง Reducer กัน
const store = createStore(rootReducer);
export default store;
คำอธิบาย:
createStore(rootReducer): สร้าง Store โดยใช้rootReducerเป็นตัวกำหนดว่า State จะเปลี่ยนแปลงไปอย่างไรrootReducer: เป็น Reducer ที่รวม Reducer อื่นๆ เข้าด้วยกัน (เดี๋ยวเราจะพูดถึงในหัวข้อ Reducer)
2. Reducer
Reducer คือ Function ที่รับ State ปัจจุบันและ Action แล้วคืน State ใหม่ (Immutable) Reducer เป็น Pure Function ซึ่งหมายความว่าต้องไม่มี Side Effects (ผลข้างเคียง) และต้องคืนค่าเดิมเสมอเมื่อได้รับ Input เดิม
ตัวอย่างการสร้าง Reducer:
const initialState = {
count: 0,
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
export default counterReducer;
คำอธิบาย:
initialState: State เริ่มต้นของ Reducer(state = initialState, action) => { ... }: Reducer Function ที่รับ State ปัจจุบันและ Actionswitch (action.type) { ... }: ตรวจสอบaction.typeเพื่อกำหนดว่าจะทำอะไรกับ Statereturn { ...state, count: state.count + 1 };: คืน State ใหม่โดยใช้ Spread Operator (...) เพื่อสร้าง Copy ของ State เดิม และแก้ไขเฉพาะค่าที่ต้องการ- Immutable: การใช้ Spread Operator เป็นการสร้าง Object ใหม่ ไม่ได้แก้ไข Object เดิม ซึ่งเป็นหลักการสำคัญของ Redux
การรวม Reducer หลายตัว:
import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
import userReducer from './userReducer';
const rootReducer = combineReducers({
counter: counterReducer,
user: userReducer,
});
export default rootReducer;
combineReducers ช่วยให้เราสามารถรวม Reducer หลายตัวเข้าด้วยกันเป็น rootReducer ได้ ทำให้ Codebase เป็นระเบียบมากขึ้น
3. Action
Action คือ Object ที่อธิบายว่าเกิดอะไรขึ้นในแอปพลิเคชัน Action ต้องมี Property type เสมอ ซึ่งเป็น String ที่ระบุประเภทของ Action นั้นๆ
ตัวอย่างการสร้าง Action:
const increment = () => {
return {
type: 'INCREMENT',
};
};
const decrement = () => {
return {
type: 'DECREMENT',
};
};
export { increment, decrement };
Action Creators คือ Function ที่คืน Action Object เพื่อให้ง่ายต่อการ Dispatch Action
การเชื่อมต่อ Component กับ Redux Store
เราใช้ connect จาก react-redux เพื่อเชื่อมต่อ Component กับ Redux Store
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';
const Counter = (props) => {
return (
<div>
<p>Count: {props.count}</p>
<button onClick={props.increment}>Increment</button>
<button onClick={props.decrement}>Decrement</button>
</div>
);
};
const mapStateToProps = (state) => {
return {
count: state.counter.count, // เข้าถึง State จาก Reducer ชื่อ counter
};
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
คำอธิบาย:
connect(mapStateToProps, mapDispatchToProps)(Counter): เชื่อมต่อ ComponentCounterกับ Redux StoremapStateToProps: Function ที่รับ State จาก Store แล้ว Map State ที่ต้องการไปยัง Props ของ ComponentmapDispatchToProps: Function ที่รับdispatchFunction แล้ว Map Action Creators ไปยัง Props ของ Component- Performance:
mapStateToPropsจะถูกเรียกทุกครั้งที่ State ใน Store เปลี่ยนแปลง หาก State ที่ Component ใช้ไม่ได้เปลี่ยนแปลง Component จะไม่ Re-render ทำให้ Performance ดีขึ้น
สรุป
Redux เป็นเครื่องมือที่ทรงพลังสำหรับการจัดการ State ส่วนกลางของแอปพลิเคชันขนาดใหญ่ แม้ว่าจะมี Boilerplate Code จำนวนมาก แต่ Redux ช่วยให้การจัดการ State เป็นระเบียบและคาดเดาได้ง่ายขึ้น ทำให้ลดโอกาสเกิด Bug และง่ายต่อการ Maintain ในระยะยาว
ในบทถัดไป เราจะเรียนรู้การใช้ MapView เพื่อแสดงแผนที่ในแอป React Native