รายละเอียดสำหรับเรียนรู้ React ปี 2020-2023 โดยโค้ชพล ดูหลักสูตรได้ที่ https://www.nextflow.in.th/react-training
Redux ประกอบไปด้วย 3 ส่วนที่ต้องสร้างขึ้นมา เพื่อให้ทำงานสอดประสานกันเป็นหนึ่งเดียว เหมือนทีมฟุตบอล หรือทีมเกมส์ MOBA ต้องมีทั้งรุก รับ support มีฝ่ายใดฝ่ายหนึ่งไม่ได้
3 ฝ่ายหลักคือ
Action ส่วนใหญ่จะถูกส่งจาก Component มายัง reducer
สร้างไฟล์ src/redux/actions.js
const ActionTypes = {
SHOW_BRANCH_DATA: "SHOW_BRANCH_DATA"
}
const showBranchData = (payload) => ({
type: ActionTypes.SHOW_BRANCH_DATA,
payload: payload
})
export default {
showBranchData,
ActionTypes
}
เปรียบได้คล้ายๆ กับ Event ใน MVC แต่ Action จะวิ่งระหว่างส่วนต่างๆ ของ Redux
ไฟล์กลุ่มแรกคือ Actions จะมีส่วนประกอบใหญ่ๆ อยู่ 2 ส่วนคือ
จากไฟล์ตรงนี้ เรากำหนดประเภท Action ในแอพเราขึ้นมา 1 อัน นั่นคือตอนที่เรากดเลือกหมุดในแผนที่ จะมีการแสดงข้อมูลขึ้นมาใน Chart นั่นเอง
สร้างไฟล์ src/redux/dashboard.reducer.js
ใช่้ snippet rxreducer
ได้
แล้ว import ./actions
เข้ามา เพื่อกำหนด Action Type เป็น 1 เคสของ Reducer
import Actions from "./actions";
const initialState = {
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case Actions.ActionTypes.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 Actions from "./actions";
import BranchModel from "../models/branchModel";
const initialState = {
branches: BranchModel.branches
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case Actions.ActionTypes.SHOW_BRANCH_DATA: {
return { ...state, ...payload }
}
default:
return state
}
}
reducer ต้องการ ข้อมูลเริ่มต้น สำหรับใช้ส่งไปให้ Component ต่างๆ เสมอ เรามักเรียกส่วนนี้ว่า initialState
initialState ก็คือ object ตัวหนึ่งที่ reducer จะส่งไปให้กับ Component ต่างๆ ตอนเริ่มทำงานนั่นเอง
เราสามารถกำหนดอะไรลงไปใน initialState ก็ได้ เหมือนเป็น object ของ JavaScript ทั่วไป
สร้างไฟล์ src/redux/store.js
import { createStore } from 'redux';
import dashboardReducer from "./dashboard.reducer";
export default function configureStore() {
const store = createStore(dashboardReducer);
return store;
}
### Store
ไฟล์ส่วนที่รวมการทำงานของทุกส่วนใน Redux เข้าด้วยกัน ให้มองว่าเป็น Global Setting หรือ main board ใน computer ก็ได้
ในที่นี้สร้างเป็นไฟล์แยก เพื่อการปรับแต่งการทำงานได้ง่าย
เปิดไฟล์ src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import HeaderBar from './components/HeaderBar';
import MapBranch from './components/MapBranch';
import StatChart from './components/StatChart';
import { Layout, Menu, Row, Col } from 'antd';
const { Header, Content, Footer } = Layout;
// ...
เปิดไฟล์ src/App.js
เริ่มจาก import Provider
component จาก react-redux
และตัว store ที่เราสร้างไว้
import { Provider } from 'react-redux'
import configureStore from "./redux/store";
เรียกใช้งาน configureStore()
เพื่อสร้าง store object
const store = configureStore();
และกำหนด store
ที่ได้ให้กับ <Provider>
สังเกตว่าเราจะครอบทุกส่วนของ Application
function App() {
return (
<Provider store={store}>
<div>
<Layout className="layout">
...
</Layout>
</div>
</Provider>
);
}
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { Provider } from 'react-redux'
import configureStore from "./redux/store";
import HeaderBar from './components/HeaderBar';
import MapBranch from './components/MapBranch';
import StatChart from './components/StatChart';
import { Layout, Menu, Row, Col } from 'antd';
const { Header, Content, Footer } = Layout;
const store = configureStore();
function App() {
return (
<Provider store={store}>
<div>
<Layout className="layout">
<HeaderBar />
<Content style=>
<div
style=>
<Row gutter={16}>
<Col span={12}><MapBranch /></Col>
<Col span={12}><StatChart/></Col>
</Row>
</div>
</Content>
<Footer style=>React Redux Workshop ©2012-2019 Created by Nextflow.in.th</Footer>
</Layout>,
</div>
</Provider>
);
}
export default App;
React Component ทั่วไป จะไม่มีการเชื่อมกับระบบ Redux ตั้งแต่แรก แต่เราสามารถจับมันมาตั้งค่าให้ใช้งานกับ Redux ได้
เราเรียก Component พวกนี้ว่า Redux Container หรือ Component Container ครับ
เริ่มจาก src/components/MapBranch.js
เรา import module ชื่อ connect
เข้ามาก่อน
// snippet 'redux'
import { connect } from "react-redux";
จากนั้นเราจะย้าย export default
จากที่ใช้กับ Class ของเรา ไปใช้กับส่วนอื่น
export default class MapBranch extends Component {
// เป็น
class MapBranch extends Component {
ซึ่งเราจะมาเขียนในส่วนด้านล่างแทน
เป็น
// snippet 'reduxmap'
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = {
}
export default connect(mapStateToProps, mapDispatchToProps)(MapBranch)
ในที่นี้ Reducer ของเรามี initialState มีที่ข้อมูลของตำแหน่งสาขาอยู่
ซึ่งถ้าต้องการเอามาใช้ใน Container เราก็สามารถเขียนค่าใน mapStateToProps
ได้แบบด้านล่าง
state ที่เห็นตรงนี้คือ state ที่ reducer ส่งออกมาให้ Component ต่างๆ นั่นเอง
const mapStateToProps = (state) => ({
branches: state.branches
})
หรือแบบนี้ก็ได้
const mapStateToProps = (state) => {
return {
branches: state.branches
}
}
mapStateToProps
เป็นส่วนที่เราเขียนดึงค่าจาก state ที่ได้ ให้กับ props
ดังนั้นในที่นี้เราจึงสามารถดึงค่า this.props.branches
มาใช้ใน component ได้
handleApiLoaded(map, maps) {
let bounds = new maps.LatLngBounds();
let branches = this.props.branches;
branches.forEach(branch => {
//..
});
//..
}
import React, { Component } from 'react'
import GoogleMapReact from 'google-map-react';
import { connect } from "react-redux";
class MapBranch extends Component {
static defaultProps = {
// Kerry Siam Seaport Location
center: {
lat: 13.7200452,
lng: 100.5135078
},
zoom: 15
};
handleApiLoaded(map, maps) {
let bounds = new maps.LatLngBounds();
let branches = this.props.branches;
branches.forEach(branch => {
new maps.Marker({
position: branch.position,
map,
title: branch.name
});
bounds.extend(branch.position);
// Alternative
// bounds.extend(new maps.LatLng(branch.lat, branch.lng);
});
map.fitBounds(bounds);
}
render() {
return (
<div style=>
<GoogleMapReact
bootstrapURLKeys=
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({ map, maps }) => this.handleApiLoaded(map, maps)}
>
</GoogleMapReact>
</div>
)
}
}
const mapStateToProps = (state) => ({
branches: state.branches
})
const mapDispatchToProps = {
}
export default connect(mapStateToProps, mapDispatchToProps)(MapBranch)