รายละเอียดสำหรับเรียนรู้ React ปี 2020-2023 โดยโค้ชพล ดูหลักสูตรได้ที่ https://www.nextflow.in.th/react-training
Redux ประกอบไปด้วย 3 ส่วนที่ต้องสร้างขึ้นมา เพื่อให้ทำงานสอดประสานกันเป็นหนึ่งเดียว เหมือนทีมฟุตบอล หรือทีมเกมส์ MOBA ต้องมีทั้งรุก รับ support มีฝ่ายใดฝ่ายหนึ่งไม่ได้
3 ฝ่ายหลักคือ
Action ส่วนใหญ่จะถูกส่งจาก Component มายัง reducer
สร้างไฟล์ src/redux/action.js
export default {
SHOW_BRANCH_DATA: "SHOW_BRANCH_DATA"
}
เปรียบได้คล้ายๆ กับ Event ใน MVC แต่ Action จะวิ่งระหว่างส่วนต่างๆ ของ Redux
ไฟล์กลุ่มแรกคือ Actions จะมีส่วนประกอบใหญ่ๆ อยู่ 2 ส่วนคือ
จากไฟล์ตรงนี้ เรากำหนดประเภท Action ในแอพเราขึ้นมา 1 อัน นั่นคือตอนที่เรากดเลือกหมุดในแผนที่ จะมีการแสดงข้อมูลขึ้นมาใน Chart นั่นเอง
สร้างไฟล์ src/redux/reducer.js
ใช่้ snippet rxreducer
ได้
แล้ว import ./action
เข้ามา เพื่อกำหนด Action Type เป็น 1 เคสของ Reducer
import Action from './action'
const initialState = {
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case Action.SHOW_BRANCH_DATA: {
return { ...state, ...payload }
}
default:
return state
}
}
เปรียบได้คล้ายๆ กับ Controller ใน MVC แต่ Reducer จะส่ง State ให้กับ Component
มองว่า State ตอนนี้ ถูกใช้แทน Model ของ MVC ก็ได้
Reducer เป็นส่วนที่จะจัดการรับ Action ที่ส่งมาจากส่วนต่างๆ ของ Redux, ทำตาม business logic และอัพเดต State กลับไปที่ Component ที่ใช้งาน
import BranchModel มากำหนดเป็นค่าเริ่มต้นของ initialState
import Action from "./action";
import BranchModel from "../models/branchModel";
const initialState = {
branches: BranchModel.branches
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case Action.SHOW_BRANCH_DATA: {
return { ...state, ...payload }
}
default:
return state
}
}
reducer ต้องการ ข้อมูลเริ่มต้น สำหรับใช้ส่งไปให้ Component ต่างๆ เสมอ เรามักเรียกส่วนนี้ว่า initialState
initialState ก็คือ object ตัวหนึ่งที่ reducer จะส่งไปให้กับ Component ต่างๆ ตอนเริ่มทำงานนั่นเอง
เราสามารถกำหนดอะไรลงไปใน initialState ก็ได้ เหมือนเป็น object ของ JavaScript ทั่วไป
เปิดไฟล์ src/index.js
// เริ่มจาก import `Provider` component จาก `react-redux`
import { Provider } from 'react-redux';
// เริ่มจาก import `createStore()` function จาก `redux`
import { createStore } from 'redux';
// import reducer จาก module ที่สร้างไว้
import reducer from './redux/reducer'
// กำหนด reducer ในการสร้าง store
const store = createStore(reducer)
ReactDOM.render(
<React.StrictMode>
{/* ครอบ store ด้วย Provider component */}
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
ไฟล์ส่วนที่รวมการทำงานของทุกส่วนใน Redux เข้าด้วยกัน ให้มองว่าเป็น Global Setting หรือ main board ใน computer ก็ได้
ในที่นี้สร้างเป็นไฟล์แยก เพื่อการปรับแต่งการทำงานได้ง่าย
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './redux/reducer'
const store = createStore(reducer)
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Function component จะสามารถเข้าถึง Redux store ได้ ผ่าน React Hook ที่ Redux ได้เตรียมไว้ให้
Component ที่มีการเข้าถึง Redux พวกนี้ เรียกในอีกชื่อว่า Redux Container หรือ Component Container ครับ
ซึ่งการที่เราจะใช้ React Hook เข้าถึง Redux Store ได้ ต้องมีการครอบ Component ทั้งหมด ด้วย store ผ่าน Provider ซะก่อน ตามขั้นตอนที่แล้ว
เริ่มจาก src/components/MapBranch.js
import { useSelector } from 'react-redux'
const branches = useSelector(state => state.branches);
console.log('braches: ' + branches);
useSelector()
const handleApiLoaded = (map, maps) => {
let bounds = new maps.LatLngBounds();
// สลับมาใช้ตัว branch ที่ได้มาจาก Redux store ผ่านการเรียกใช้ useSelector()
branches.forEach(branch => {
new maps.Marker({
position: branch.position,
map,
title: branch.name
});
bounds.extend(branch.position);
});
map.fitBounds(bounds);
}
import React from 'react'
import GoogleMapReact from 'google-map-react';
import { useSelector } from 'react-redux'
export default function MapBranch({
center = {
lat: 13.7200452,
lng: 100.5135078
},
zoom = 15
}) {
const branches = useSelector(state => state.branches);
console.log('braches: ' + branches);
const handleApiLoaded = (map, maps) => {
let bounds = new maps.LatLngBounds();
branches.forEach(branch => {
let marker = new maps.Marker({
position: branch.position,
map,
title: branch.name
});
bounds.extend(branch.position);
});
map.fitBounds(bounds);
}
return (
<div style=>
<GoogleMapReact
bootstrapURLKeys=
defaultCenter={center}
defaultZoom={zoom}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({ map, maps }) => handleApiLoaded(map, maps)}
>
</GoogleMapReact>
</div>
)
}