WorkShop WebApplication ด้วย React : 51
ใช้งาน Form
การใชง้ าน Form ใน React กจ็ ะคล้ายๆ กบั HTML ธรรมดาๆ ครับ เรามาลองสรา้ ง Form กันดีกว่าครบั เหมือน
เดิมครับให้แก้ไขไฟล์ App.js เปน็ โคด้ ด้านลา่ งน้ีครับ
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
<h1>App Page</h1>
<form>
<label>
Name:
<input type="text"
name="name"
placeholder="enter Name"
defaultValue="Peter"
onChange={this.handleChange}
/>
</label>
</form>
</div>
)
}
handleChange = e => {
console.log(e.target.value)
}
}
export default App;
จากโคด้ จะเหน็ ว่าเปน็ การสร้งฟอรม์ ปกตเิ หมอื น HTML ท่วั ๆ ไปครับ โดยใน input สามารถก�ำหนด placeholder
ได้ ก�ำหนดคา่ defaultValue ได้ และหากเราต้องการแสดงค่าทีก่ รอกเราจะจัดการที่ event onChange โดยในโค้ดผมส่งไป
ให้ function handelChange ซึ่ง function นีผ้ มสั่งให้แสดงค่าใน textbox ออกมาโดยใชค้ ำ� สัง่ e.target.value
เมอ่ื ดูที่ browser กจ็ ะได้ดังรปู ด้านลา่ งครับ
การท�ำงานกบั Form ดว้ ยโคด้ ข้างบนนน้ั จะมขี ้อเสียคอื เวลาเราต้องการค่าของ input หรือ checkbox หรือ ฯลฯ
มันจะทำ� ได้ยากครับ และถา้ หากเราต้องการเปลี่ยนข้อความใน Textbox นี้กท็ �ำไมไ่ ดค้ รับ
ดงั นน้ั มนั จะมวี ิธีการใช้งาน Form อกี วิธที ีเ่ ขาเรยี กวา่ “controlled components” เรยี กแบบเข้าใจงา่ ยๆ คือ form
51
WorkShop WebApplication ด้วย React : 52
แบบควบคมุ ได้ คำ� ถามคอื “ทำ� ไมต้องไปควบคุม Form”
ค�ำตอบคือ พอทำ� งานจริงๆ การที่เราจะสง่ คา่ ของฟอรม์ ไปบันทกึ ขอ้ มลู เราจะส่งไปกบั State ครับ หรอื ไม่เขากจ็ ะใช้
module ตวั หน่งึ ท่ชี อื่ ว่า redux-form ในการเกบ็ คา่ ของ Form เขา้ State แบบอตั โนมัตเิ พ่อื เอาไปใช้งาน
เรามาดูวธิ ีใช้งานดูครับ เหมอื นเดิมครับแกไ้ ขไฟล์ App.js
import React, { Component } from 'react';
class App extends Component {
state = { name: '' }
render() {
return (
<div>
<h1>App Page</h1>
<form>
<label>
Name:
<input type="text"
name="name"
placeholder="enter Name"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
</form>
<button onClick={this.handleClick}>Change Text</button>
</div>
)
}
handleClick = e => {
this.setState({ name: "I am React" })
}
handleChange = e => {
this.setState({ name: e.target.value })
}
}
export default App;
จากโคด้ จะเห็นว่ามีการใชง้ าน state ซ่งึ state นีเ้ ราเอาไปใชก้ �ำหนดคา่ ใน textbox และเหมือนเดมิ ใน TextBox
เราต้องก�ำหนด event onChange ดว้ ยนะครบั โดยเรียก function handleChange
และทสี่ ำ� คัญ ปุ่ม ท่เี พ่ิมเข้ามาผมทำ� เพ่อื แสดงใหเ้ หน็ วา่ ในกรณีท่เี ราต้องการเปลย่ี นแปลงค่าของ TextBox เรากส็ ่งั
setState เพื่อใหค้ ่าใน TextBox มันเปลยี่ น (เพราะ TextBox ตวั นี้ผูก value ไวก้ บั state.name) ถ้าเป็นการใช้งาน Form
ก่อนหนา้ นี้เราจะสั่งเปล่ียนค่าใน TextBox ไม่ไดน้ ะครับ
52
5 WorkShop WebApplication ด้วย React : 53
#WorkShop
เวบ็ PhotoAlbum
กอ่ นเริม่ สร้างโปรเจคให้สรา้ ง Folder สำ� หรับเกบ็ โปรเจครวมของเราทงั้ หมดก่อน โดยหนงั สอื เล่มนจ้ี ะสรา้ ง
Folder ชื่อ ReactToPro ผเู้ รยี นจะสร้างชือ่ อื่นกไ็ ดน้ ะครบั ตำ� รวจไม่จบั โดยสรา้ งไว้ที่ Drive ไหนของเคร่ืองก็ได้ ซ่งึ
Folder นจ้ี ะใช้เก็บ Folder โปรเจคต่างๆ ของเราทงั้ หมด...ถา้ พร้อมแล้ว...ลุยโลด !!
ภาพรวมระบบ
WorkShop น้ีจะเปน็ การดงึ ขอ้ มลู จาก API ภายนอก เพือ่ ให้ผเู้ รยี นไดเ้ รียนรู้การ fetch data การใช้ Router การใช้
Redux (อนั นโ้ี คตรส�ำคัญ) รวมถงึ การน�ำ CSS จากภายนอกเข้ามาใชง้ าน โดยในระบบจะแสดง
1. รายชือ่ ผใู้ ช้
2. รายชอ่ื Album (แสดงตามรหัสผ้ใู ช้)
3. ภาพใน Album นน้ั ๆ
WorkShop งา่ ยๆ แตโ่ คตรจะสนุก และไดป้ พู ้ืนฐานหลายๆ อยา่ ง รับรองไดว้ า่ เร่ืองท่คี ณุ คิดวา่ มนั ยาก และ งง...
งวย มันจะงา่ ยข้ึนทันที
เร่มิ สร้างโปรเจค
1. เปิด Terminal หรือ Command Prompt (ส�ำหรับ windows) พิมพ์ cd reacttopro แลว้ กด Enter
2. สรา้ งโปรเจคโดยพมิ พ์ create-react-app photoalbum แล้วกด Enter
53
WorkShop WebApplication ด้วย React : 54
3. รอจน react สรา้ งโปรเจคเสรจ็ เหมอื ภาพด้านลา่ ง
4. พิมพ์ cd photoalbum แล้วกด Enter (เพ่ือเข้าไปยงั folder photoalbum)
5. ทดลองรันโปรเจคโดยพิมพ์ npm start แล้วกด Enter
6. เมอื่ ระบบพรอ้ มใช้งานหน้าตา่ ง terminal จะแสดงขอ้ ความดังภาพด้านล่าง เลข IP อาจไมใ่ ช้ 192.168.1.3
เหมอื นในรปู แต่โดยทัว่ ไปจะเข้าใช้งานผา่ นทาง localhost นะครบั
7. เปิด Chrome Browser แล้วพมิ พ์ url ไปที่ http://localhost:3000 เพื่อดูผลงาน
54
WorkShop WebApplication ด้วย React : 55
จดั ระเบยี บไฟล์ใหม่
ท่ีตอ้ งจดั ระเบยี บไฟล์ใหม่เพื่อให้เห็นการทำ� งานของ react แบบพื้นฐานครับจะได้ไม่ งง
1. เปิด folder โปรเจค photoalbum ดว้ ยโปรแกรม Visual Studio Code จากนน้ั กล็ บไฟลใ์ นโฟล์เดอร์ src
ออก ดงั นี้ App.css, App.test.js, logo.csv เมอื่ ลบแล้วก็จะเหลือแคไ่ ฟลเ์ หมอื นในรปู ดา้ นลา่ ง
2. แกไ้ ขไฟล์ App.js โดยลบโค้ดท่ีเราจะไมใ่ ช้งานออกไปให้เหลือแค่โค้ดในรูป (เพิ่มคำ� วา่ Hello เข้าไปดว้ ยนะ)
3. สร้าง folder ใหมภ่ ายใต้ src ตงั้ ชอ่ื ว่า pages และ components โดย 2 folder นผี้ มเลียนแบบ Next.js
ที่เขาแบ่ง folder ท�ำงานเป็น 2 folder หลกั ๆ 2 ตวั โดย folder pages จะเก็บหน้าเพจที่แสดงผล เช่น เพจรายชื่อผใู้ ช,้ เพจ
รายชื่อ Album เปน็ ตน้ ส่วน folder components จะใช้เก็บ components ตา่ งๆ เพื่อประกอบกบั เพจ
4. ยา้ ยไฟล์ src/App.js ไปไว้ท่ี src/pages/App.js เสรจ็ แล้วจะได้ folder ดังรปู (แตย่ งั ไม่เสร็จนะคบั )
55
WorkShop WebApplication ด้วย React : 56
5. แกไ้ ขไฟล์ src/index.js โดย
บรรทัด import App from ‘./App’ ให้แกเ้ ปน็ import App from ‘./pages/App’
6. ลองกลบั ไปดทู ่ี browser กจ็ ะเจอข้อความ Hello ข้ึนมาแทน (เท่ห์ สดุ ๆ)
สดุ ยอด !! เขียนโปรแกรมได้แลว้ ปดิ เคร่อื งแลว้ ไปฉลองกนั ....เฮย้ !! ไม่ใช่ละ
น่แี ค่เรม่ิ ต้นครบั ความสนุกยงั รออยู่ในหนา้ ตอ่ ไป...
56
WorkShop WebApplication ดว้ ย React : 57
ติดตง้ั และใช้งาน React Router
การเปลย่ี นหนา้ เพจจากเพจหนึง่ ไปอกี เพจหนงึ่ ถ้าใน HTML กจ็ ะใชแ้ ท็ก a ใชม่ ้ยั ครบั เชน่ <a href=”album”>Al-
bum</a> แต่คณุ สมบัตขิ องแท็ก a จะทำ� ให้หนา้ เว็บเกิดการ refresh
ส�ำหรับ React นั้นการเปล่ียนหนา้ เพจจะตอ้ งมีการใช้งานสิ่งทเ่ี รียกวา่ Router ซึง่ Router กค็ ือ function ทใ่ี ช้
เปลย่ี นหนา้ เพจแบบไมใ่ หเ้ กดิ การ refresh (และมีคณุ สมบตั ิอ่นื ๆ เกย่ี การการใชง้ านลิงคต์ ่างๆ ครบั )
Router นี้เราสามารถเขยี นเองได้แต่ก็ยุง่ ยากพอสมควร จึงมคี นท�ำเป็น component ให้เราได้ใช้งานฟรีๆ ส�ำหรบั
React โดยเฉพาะ ซงึ่ น่ันก็คือ component ทีช่ อื่ ว่า react-router
ปัจจุบนั react-router นน้ั พัฒนาถึงเวอร์ชนั 4 (เวอร์ชนั 5 ยังเป็น Alpha) แตผ่ มจะไม่สอนใช้ v.4 หรอกนะครับ ผม
จะสอนใช้ v.3 “อ้าว!! ทำ� ไมใชข้ องเกา่ หละ” ปัญหามนั มีอย่วู ่า ใน v.4 นน้ั มีปัญหาเรอ่ื ง Waterfall ซง่ึ ส่งผลต่อประสิทธิภาพ
ซึง่ ไมร่ ู้ว่าทางผู้พัฒนาแกไ้ ขหรอื ยงั ผมในฐานนะทใ่ี ช้ React มาก่อนจงึ แนะนำ� ใหใ้ ช้ v.3 อยา่ ลมื นะครบั ว่าตัว router น้ันเป็น
เพยี ง function ทีเ่ ขียนข้นึ มาจดั การการเปลย่ี นหนา้ เพจ “ดงั นน้ั เลือกในสิ่งทีม่ นั ง่ายและมีประสิทธภิ าพ ดีกว่าเลือกของใหม่
แตย่ ังไมม่ ่ันใจ” ศึกษาตวั react-router เพิ่มเตมิ ไดท้ ่ี https://github.com/ReactTraining/react-router
ติดตงั้ react-router
1. ยกเลิกการรันโปรเจคโดยกดปมุ่ control + c
2. ติดตั้ง module react-router โดยพิมพ์ npm i react-router@3 -S แลว้ กด Enter (อยา่ ลืมใส่ @3 เพือ่ ระบุ
วา่ ใหต้ ิดตั้ง react-router version 3)
57
WorkShop WebApplication ดว้ ย React : 58
เรม่ิ ใช้งาน react-router
1. เปดิ ไฟล์ src/index.js แล้วแก้เปน็ โคด้ ด้านลา่ งน้ี
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
import { Router, browserHistory } from 'react-router' B A
import routes from './routes'
ReactDOM.render( C
<Router
history={browserHistory}
routes={routes}
/>,
document.getElementById('root')
);
registerServiceWorker();
อธบิ ายโค้ด
A. ทำ� การ import ตัว Router และ browserHistory จาก module ช่อื react-router
B. import routes จากไฟล์ routes (src/routes.js) ในไฟลน์ ี้จะเปน็ ตวั ระบุ route (หรือเสน้ ทางขอการแสดง
หนา้ เพจต่างๆ) ในการสรา้ ง route น้ันจะไม่นยิ มน�ำมาใสใ่ นไฟล์ index.js เพราะจะทำ� ใหร้ กและจัดการโคด้ ไดย้ ากจงึ จ�ำเปน็
ต้องแยกไปสรา้ งอีกไฟล์
C. ส่ังให้ ReactDom ท�ำการ render ตวั Route แทนท่กี าร render <App/> ออกมาตรงๆ โดยจะส่ง props ไป
2 ตวั คอื history โดยสง่ browserHistory จาก react-router (เพอ่ื เก็บและจำ� เส้นทางการคลิกลงิ คต์ า่ งๆ ไว้ ฯลฯ) และอกี ตวั
คือ routes จากไฟล์ routes.js ซ่งึ จะต้องสรา้ งขึ้นมาเองในขอ้ ถัดไปครับ
โดยในการทำ� งานของ react-router นนั้ เมอื่ ผใู้ ช้เปดิ เว็บมายงั หนา้ แรก มนั จะเข้ามาทำ� งานทไ่ี ฟล์
index.js (จรงิ ๆ พอ build แล้วเราจะไมไ่ ดไ้ ฟล์ index.js หรอกนะครบั เพยี งแตม่ นั จะเอาโคด้ ข้างใน index.js ไป
รวมกบั ไฟล์อืน่ ๆ) แลว้ มนั จะมาเจอ component ชอ่ื Router ซง่ึ เจ้าตวั Router กจ็ ะมรี ายการเส้นทาง (ที่ไดจ้ าก
ไฟล์ routes.js) เพอื่ โหลด component อ่ืนๆ มาแสดงอีกทีหนง่ึ (ฟังดูแล้ว งง ไมแ่ ปลกครบั ตอ้ งไดล้ องท�ำและรนั ดู
จะเริม่ เข้าใจเอง แต่ก่อนอื่นตอ้ งสร้างไฟล์ routes.js ก่อนนะครบั งน้ั เราไปสรา้ งกนั เลย!!)
2. สร้างไฟล์ routes.js ไว้ใน folder src แลว้ พมิ พโ์ คด้ ขา้ งล่างน้ี
import App from './pages/App' A
const routes = [{ B
path: '/',
component: App,
}]
export default routes C
58
WorkShop WebApplication ดว้ ย React : 59
อธิบายโคด้
A. import App จากไฟล์ src/App.js
B. ประกาศตัวแปรชือ่ routes (ตวั แปรนท้ี เ่ี ราเอาไปใชใ้ นไฟล์ index.js ไง) โดยสมาชิกตัวแรกคอื path ก�ำหนด
คา่ ให้เท่ากบั / และ component ใหแ้ สดงตวั App อธบิ ายแบบชวิ ๆ คือ ถา้ ป้อน url เป็น http://localhost:3000 หรือ
http://localhost:3000/ ตวั Router กจ็ ะไปหยิบ component App ออกมาแสดง (ซ่ึงตอนน้ีมันแสดงคำ� ว่า Hello อยู่ ไม่
เช่ือลองดทู ี่ browser สิ ซึง่ เราจะใชค้ วามสามารถนี้ในการท�ำเปน็ Layout หลกั ของเว็บ)
C. ส่งออกตัวแปร routes เพราะไมง่ น้ั ก็จะไม่มไี ฟล์ไหนสามารถเรยี กใชไ้ ด้
ความจรงิ จะต้องมีการก�ำหนด indexRoute แล childRoutes แตต่ อนนยี้ งั ไม่ไดส้ ร้างหนา้ เพจอื่นๆ กก็ �ำหนดแคน่ ไี้ ป
ก่อนครบั ส�ำหรบั ใครท่ใี ช้ react-router v.4 จะก�ำหนดคนละแบบนะครับ
สรา้ ง Layout หลกั
ปกตเิ วลาทำ� web application ทว่ั ๆ ไปก็จะมีการสรา้ งไฟล์ส�ำหรับ Layout เพื่อนำ� สว่ น Header, Footer, Side-
bar และ Content มาประกอบกับ (เป็นแนวคิดท่ีฉลาดนะคบั ) คงไม่มีใคร import Header, Footer หรือ Sidebar ไปไว้ทุกๆ
หน้าเพจหรอกนะคับ แบบนั้นเด็กๆ เขาท�ำกนั
แล้วใน React หละท�ำไง โดยใน React เราก็จะใช้ความสามารถของ props.children รว่ มกบั react-router ครับ
ถ้าพรอ้ มแล้วลุย!!
ตดิ ตงั้ Bulma CSS framework
เนือ่ งจากเราจะจัดเว็บเราใหส้ วยงาม และเรยี นรู้การใช้งาน CSS framwork จากภายนอก ในโปรเจคนเ้ี ลือกใช้
Bulma (ศึกษาเพ่ิมเตมิ ไดท้ ่ี https://bulma.io/)
1. ใน terminal กดปุ่ม Ctrl + c เพ่ือยกเลิกการรันโปรเจค
2. พมิ พ์ npm i bulma -S แลว้ กด Enter (พอตดิ ตั้งเสรจ็ ก็ พมิ พ์ npm start เพือ่ รนั โปรเจคได้เลยครบั )
59
WorkShop WebApplication ด้วย React : 60
เร่มิ สรา้ ง Layout
1. สรา้ ง folder ชือ่ Header ไวท้ ่ี src/components แล้วสรา้ งไฟล์ Header.js ไว้ข้างใน folder Header
2. เพิม่ โคด้ ขา้ งล่างเขา้ ไปที่ไฟล์ src/components/Header/Header.js
import React from 'react' A
import { Link } from 'react-router'
const Header = () => { B
return (
<nav className="navbar">
<div className="container">
<div className="navbar-brand">
PhotoAlbum (Sample)
</div>
<div className="navbar-menu">
<div className="navbar-start">
<Link to="/" className="navbar-item">Home</Link>
<Link to="/user" className="navbar-item">Photo</Link>
</div>
</div>
</div>
</nav>
)
}
export default Header
อธบิ ายโค้ด
Header.js นเ้ี ราจะใช้แสดงเมนูด้านบนเวบ็ ครบั เหมือนเว็บทวั่ ๆ ไปทีม่ ีเมนูด้านบน
A. import Link จาก react-router ตวั นจ้ี ะเปน็ ตวั สร้าง tag link คลา้ ยๆ กบั การสรา้ ง <a href=’xxx.com’>
ครับ แตส่ �ำหรับ Link ใน react-router นี้ จะทำ� ให้หน้าเวบ็ ไม่ refresh เวลาคลิกลงิ ค์ (วธิ ีใชข้ ้อ B)
B. สรา้ ง Link โดยตอ้ งสง่ props ชอ่ื to และระบเุ ส้นทางใหก้ บั มันซง่ึ จะต้องก�ำหนดเสน้ ทางให้ตรงกับเสน้ ทางใน
ไฟล์ src/routes.js ที่มนี ะครบั (มนั จะเอาไป map กนั เอง) รูปแบบการใชง้ าน Link ก็จะคลา้ ยแท็ก a ใน html ปกติครบั (ก็
เหมอื นในโคด้ นนั้ แหละครับ จบข่าว...)
ปล. สว่ นของการแสดงเมนู Home และ Photo จะแสดงเม่ือขนาดหนา้ จอ >=1024 px (กข็ นาดหนาจอ desktop
ปกตินั่นแหละครับ) ดงั นนั้ ถ้ายอ่ ขนาด browser อาจจะไมแ่ สดงเมนูนะครบั ใหเ้ ปิด browser ใหม้ ันใหญ่ๆ หน่อย หรือใคร
อยากปรับใหม้ นั แสดงได้ทุก device ก็ดรู ายละเอียดเพม่ิ เติมไดท้ ี่ https://bulma.io/ ตรงจุดน้ีผมท�ำให้ดพู อเป็นแนวทาง
3. แกไ้ ขไฟล์ src/index.css (ปรบั style เพิม่ เติมจากตัว CSS Bulma นดิ หนอ่ ยครบั ) เพม่ิ โค้ดดา้ นลา่ งลงไปครบั
.content{ มีโคด้ ต่อด้านล่าง
padding: 20px; 60
}
.navbar{
background-color: #3368DF !important;
color: #ffffff
}
.navbar-brand{ WorkShop WebApplication ดว้ ย React : 61
font-size: 20px; โคด้ ต่อจากดา้ นบน
font-weight: 600;
padding: 10px 0 0 10px
}
a.navbar-item{
color: #ffffff !important;
}
a.navbar-item:hover{
color: #3368DF !important;
}
4. แกไ้ ขไฟล์ src/pages/App.js โดยปรบั เปน็ โค้ดดา้ นล่างนี้
import React, { Component } from 'react'; A
import 'bulma/css/bulma.css'
import Header from '../components/Header/Header'
class App extends Component { B
render() { C
return (
<div className="App">
<Header />
<div className="container">
<div className="content">
{this.props.children}
</div>
</div>
</div>
);
}
}
export default App;
อธิบายโคด้
A. น�ำเขา้ bulma.css (เรยี กใช้แบบน้ีเพราะเราตดิ ตง้ั Bulma แบบ node module จะตา่ งจากหน้า index.js ที่
เรียกใช้ไฟล์ index.css นะครับ) และอีกตัวคอื น�ำเขา้ component Header ทเี่ ราพ่ึงสรา้ งเมอื่ ก้นี ีเ้ อง
B. สงั่ แสดง component Header
C. จดุ นี้สำ� คัญ เพราะค�ำสง่ั this.props.children จะเปน็ การแสดง component ที่ถกู สง่ ตอ่ มาจากตัว router
(ตอ้ งไปกำ� หนด indexRoute และ childRoutes ในไฟล์ src/routes.js ซง่ึ ผมจะพาทำ� ในข้อถดั ไปครับ)
61
WorkShop WebApplication ด้วย React : 62
5. สร้างไฟล์ Home.js และ User.js ไวท้ ่ี src/pages (ตอนนส้ี ร้างรองรับไวก้ อ่ นนะครบั เพ่อื จะใช้แสดงการเปลี่ยน
เพจ จะไดเ้ ขา้ ใจการท�ำงานของ Router) จากนน้ั พิมพ์โค้ดเขา้ ไปตามนค้ี รบั
ไฟล์ src/pages/Home.js
import React, { Component } from 'react'
class Home extends Component {
render() {
return (
<div>
Page Home
</div>
)
}
}
export default Home
ไฟล์ src/pages/User.js
import React, { Component } from 'react'
class Home extends Component {
render() {
return (
<div>
Page User for Photo
</div>
)
}
}
export default Home
5. แกไ้ ขไฟล์ src/routes.js A
import App from './pages/App'
import Home from './pages/Home' B
import User from './pages/User' C
const routes = [{
path: '/', 62
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'user', component: User }
]
}]
export default routes
WorkShop WebApplication ด้วย React : 63
อธบิ ายโคด้
A. import Home.js และ User.js เขา้ มาใช้งาน
B. indexRoute ตัวแปรน้ีเปน็ การระบวา่ จะใหแ้ สดง Component ไหนเปน็ หน้าแรก อาจจะแสดงหน้า Dash-
board หรือหน้า Home เหมอื นในโค้ (มนั อาจจะดทู ับซอ้ นกบั path: ‘/’ นะครับแต่จริงๆ มันก็ท�ำหน้าทีกันคนละอย่าง)
C. childRoutes ตัวแปรนจี้ ะเป็นแบบ Array นะครบั แล้วข้างในมี Object ทต่ี ้องระบุ path กบั component ที่
ต้องการใหแ้ สดงมอ่ื มี path เข้ามาตรงกับทีก่ �ำหนดไวใ้ น childRoutes ถ้ายอ้ นกลบั ขน้ึ ไปดขู ้อ 1 ท่มี ีการสร้าง Link นัน้ แหละ
ครบั ตรง Link นน้ั ท่ีมกี ารส่งั to เช่น <Link to=”/user” className=”navbar-item”>Photo</Link> ตรงจดุ นแ่ี หละ
ครับมันจะมาตรงกับ childRoutes ที่ระบวุ ่า pat=’user’ มันกจ็ ะไปหยิบเอา component User ขน้ึ มา render (ใส่ / หรือ
ไม่ใสก่ ็ได้นะครบั ปกติเขาจะไมใ่ สก่ ัน) แต่ถ้าเป็น <Link to=”/” className=”navbar-item”>Home</Link> ซง่ึ จะ
สงั เกตวุ า่ ใน to ระบุแค่ / ดังนั้นเมอ่ื เข้ามาหน้าแรกของเว็บมันก็จะไปตรงกบั เงือ่ นไขในการกำ� หนด indexRoute แตถ่ า้ เราไม่
กำ� หนด indexRoute มนั ก็จะไปเข้าเง่ือนไขแรกคอื path: ‘/’ ดังนั้นมันก็จะแสดงสิ่งท่ีอยูใ่ น src/pages/App.js ออกมาซงึ่ ปกติ
เขาจะไมท่ �ำกันหรอกครบั เขาจะตอ้ งมีการกำ� หนด indexRoute เพราะเขาจะใช้ path: ‘/’ เป็นการสั่งใหแ้ สดง component
ที่จะใชเ้ ปน็ Layoute ออกมา
หลังจาก save แล้วก็ดทู ี่ browser ได้เลยครับ มนั จะต้องเหมอื นของผมนะ ไมเ่ ชือ่ ลองดเู ดีย๋ วจะหาว่าโม้!!
ภาพแสดงเม่ือคลิกเมนู Home หรอื เข้ามายงั หน้าแรกเว็บ
ภาพแสดงเมอ่ื คลิกเมนู Photo
63
WorkShop WebApplication ด้วย React : 64
การใช้งาน Redux
หัวข้อนีเ้ ราจะมาเรียนรู้วิธกี ารใช้ Redux หลายคนอาจสงสัยวา่ Redux คืออะไร ใช้ทำ� ไม ไมใ่ ช้จะเป็นไรมยั้ ... ผมจะ
สรุปส้นั ๆ คือ Redux มนั มีเพ่ือเอามาใช้เก็บ State เขา้ ไปไว้ใน Store พอผมบอกว่าใชเ้ กบ็ State ไว้ใน Store ผมเชื่อว่า
หลายคนก็จะสงสยั วา่ เกบ็ State ไวท้ ำ� ไม
เอาหละครบั ...เรามาคลายข้อสงสัยกนั โดยผมจะให้ลองเห็นปัญหาของการที่เราไม่ใช้ Redux กนั นะครับ โดยที่
1. เปดิ ไฟล์ src/pages/Home.js แลว้ แกโ้ ค้ดเป็นแบบด้านล่างนี้ครบั
import React, {Component} from 'react'
class Home extends Component { A
state = { age: 0 }
render() {
return (
<div>
<h3>อายุของคุณ : {this.state.age} ปี</h3> B C
<button onClick={this.increteAge}>+คลกิ บวกอายุ</button> D
<button onClick={this.decreteAge}>-คลิกลบอาย<ุ /button>
</div>
)
}
increteAge = () => { E
this.setState((prevState) => {
return {
age: prevState.age + 1
}
})
}
decreteAge = () => { F
this.setState((prevState) => {
return {
age: prevState.age - 1
}
})
}
}
export default Home
อธิบายโค้ด
A. กำ� หนดค่าเร่มิ ต้นให้ state (ตอ้ งกำ� หนดนะครบั ใช้ state ทกุ ครั้งต้องก�ำหนดค่าเรม่ิ ต้นใหม้ ัน ถ้าพดู ให้เทห่ ๆ์
เขากจ็ ะเรยี กว่าเป็นการ Initial State)
B. แสดง state โดยแสดงสมาชิกของ state ช่ือ age โดยในท่ีนเ้ี ราจะใช้แทนอายุ
C. เรยี กใช้ function increteAge เม่อื คลิกป่มุ (function นี้เป็นการบวกเลขเพิม่ ขนึ้ ไปเรือ่ ยๆ)
D. เรียกใช้ function decreateAge เมือ่ คลกิ ป่มุ (function นีเ้ ปน็ การลบเลขลงเรอ่ื ยๆ ติดลบก็ได้นะครบั )
E & F เป็น function ในการบวก และ ลบเลข ครบั งา่ ยๆ ไม่มีอะไร (ใครลมื เร่ืองการใช้ State ดูได้ทบี่ นแรกๆ นะ
ครบั )
64
WorkShop WebApplication ด้วย React : 65
2. Save ไฟล์แลว้ กลับไปดูที่ Browser นะครับ คลิกทเี่ มนู Home
ลองคลิกปุ่ม +คลิกบวกอายุ หรอื ปมุ่ -คลิกลบอายุ ดูนะครับจะเหน็ วา่ ตวั เลขมนั กจ็ ะเปลี่ยนไปครับ...yes!! เราท�ำได้
แล้ว จากนน้ั ลองคลกิ เมนู Photo (มนั กจ็ ะแสดง page User.js) จากนั้นกค็ ลิกเมนู Home อกี ครัง้ จะสังเกตวุ ่า อายุกลายเปน็
0 เหมือนเดิม!! ซง่ึ ใจจรงิ เราอยากให้เลขอายุมนั อยเู่ หมอื นตอนทเ่ี ราคลิกปุ่ม
ทเี่ ปน็ เชน่ น้นั เพราะ state มันหายครบั เนือ่ งจากเมื่อเราคลิกเพื่อให้แสดงเพจ (component) ใหม่ ตวั react มันก็
จะเข้าส่กู ารทำ� งานของ function ชอื่ componentWillUnmount ซง่ึ function นมี้ นั จะเคลยี ร์ local state ของ com-
ponent น้นั ๆ ของเราครบั ...ดงั น้นั มนั จึง มี redux เพื่อใชเ้ ก็บ state ไวไ้ งครบั (ถา้ ไมใ่ ช้ redux สามารถใช้ context ได้ แต่ย่งุ
ยากครับ และไมแ่ นะน�ำใหใ้ ช้ มันเหมาะกับการใช้กบั โปรเจคท่ีมีไมก่ ี่หนา้ ครับ เราไปใช้ redux กนั ดีกว่า ... ใครๆ เขาก็ใชก้ นั )
ตติ ตั้ง Redux
1. ใน terminal กดปมุ่ ctrl + c เพอ่ื ยกเลกิ การรนั โปรเจค
2. พมิ พ์ npm i redux react-redux -S แล้วกด Enter (พอติดตั้งเสรจ็ ก็ พิมพ์ npm start เพอ่ื รนั โปรเจคตอ่ )
อธบิ ายนิดส์...หน่งึ
redux ก็คอื ตัวหลัก สว่ น react-redux คือ component ทจ่ี ะท�ำให้ react เขา้ ถงึ store ของ redux ได้ครบั
เวอรช์ ันที่ตดิ ตงั้ ณ. ตอนน้ีคือ redux = v. 3 ส่วน react-recux = v.5 (ท้ัง 2 ตวั จะมเี ลขต่อทา้ ยดว้ ยนะครบั แตไ่ มต่ ้องสนใจ
มนั )
65
WorkShop WebApplication ด้วย React : 66
Redux พ้ืนฐานแบบตรงไปตรงมา
1. แกไ้ ขไฟล์ src/index.js โดยเพิม่ โคด้ สว่ นท่เี ปน็ ตัวหนาสีน้ำ� เงินลงไปครับ โดยโคด้ ต่อไปน้จี ะเปน็ การแสดงให้
เห็นภาพรวมงา่ ยๆ ของการท�ำงานของ redux (ใชจ้ รงิ จะแยก reducer กบั action ออกเป็นไฟล์ตา่ งหากครับ)
....... A
import routes from './routes'
import { createStore } from 'redux'
function countAge(state = 0, action) { B
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const store = createStore(countAge) C
store.subscribe(() => {console.log('subscribe', store.getState())})
console.log('befor dispatch', store.getState())
store.dispatch({
type: 'INCREMENT' D
})
ReactDOM.render(
........
อธบิ ายโค้ด
A. import createStore จาก redux
B. สรา้ ง reducer ขึ้นมาครับ โดยท่ี reducer กค็ ือ function ที่รับ state และ action และ return state ตัว
ใหม่ออกไป เอะ๊ !! มี action เข้ามา แล้ว action คืออะไร redux ระบเุ อาไว้ว่า action จะตอ้ งเปน็ javascript object ท่ี
จะต้องมี properties ช่ือวา่ type โดยจะสง่ เป็น String หรือตัวแปรทเ่ี ก็บ sting กไ็ ด้ เพื่อเอาไปเข้าเงอื่ ไข switch ใช้ในการ
เรยี บเทยี บว่า มันเปน็ type อะไร
จากตัวอย่าง ถ้าส่ง type เปน็ ค�ำว่า ‘INCREMENT’ ก็ใหท้ ำ� เงอื่ นไขน้ี ถา้ ไมใ่ ช่ก็ทำ� เงื่อนไขอื่น งา่ ยๆ แบบนแี้ หละ
คบั
C. เป็นการสร้าง store ครบั โดย store ตวั น้ีแหละครับสำ� คญั เพราะมนั จะใช้ในการเกบ็ state ต่างๆ เพ่ือให้
แตล่ ะ component สามารถเข้าถึงได้ โดยเราจะต้องสง่ ตวั reducer เขา้ ไปใน store
D. store.dispatch ค�ำสงั่ dispatch เป็นคำ� ส่ังทเ่ี ราจะใชใ้ นการเข้าถึง store ไม่ว่าจะเปน็ การเปลย่ี นแลง state
ใน store หรอื เรยี กขอ้ มูลจาก state ใน store เราจะตอ้ งใช้คำ� ส่งั dispatch นะครับ จ�ำไว้นะครับ ตอ้ งใชค้ �ำสัง่ dispatch
โดยใน dispatch จะตอ้ งส่ง javascript object ที่มี properties ชอ่ื วา่ type โดยใน type จะเปน็ String หรอื ตวั แปรทเี่ ก็บ
string ก็ได้ ซึง่ ค�ำส่ัง dispatch ก็จะวงิ่ เขา้ ไปทำ� งานท่ี reducer น่นั ไงครับ มนั กไ็ ปเข้าเง่อื นไขในขอ้ B
ส่วนท่ไี ม่ได้พดู ถึงคือ ส่วนทเ่ี ปน็ คำ� ส่ัง console.log ตรงน้เี ม่ือลองดูท่ี browser แลว้ inspect ดู โดยดูที่แท็บ
console ก็จะเหน็ มนั แสดงคา่ เหมอื นในภาพดา้ นลา่ งนคี้ รับ
66
WorkShop WebApplication ดว้ ย React : 67
อธบิ ายโคด้ (ต่อ)
ใน console ทางขวามอื จะแสดงข้อความ befor dispatch 0 และ subscribe 1 ตามล�ำดบั ถ้ายอ้ นกลับไปดูโคด้
ดา้ นบนนะครบั จะเห็นว่าเราสง่ั ให้มนั แสดง console.log(‘subscribe’......) ก่อน console.log(‘befor dispatch’......)
แตม่ นั แสดง console.log(‘befor dispatch’,....) ก่อน กเ็ พราะวา่ ตวั store.subscribe จะท�ำงานเมื่อมีการส่งั dispatch
(ง่ายๆ คอื มันจะทำ� งานทันทเี หมืนอเปน็ เงาตามตัวเม่อื เราเรยี กใช้ dispatch) ดังนนั้ มันจึงแสดง console.log(‘befor
dispatgch’,...) ก่อน
และอกี สิ่งท่คี วรรคู้ อื ถา้ อยากรู้ว่าใน store มี state อะไร จะตอ้ งใชค้ ำ� สัง่ getState() เช่น store.getState()
แทนนะครับ ตอ่ จากน้ีเราจะเอาความรจู้ ากส่ิงนี้ไปใช้ในการจดั การกับ state ทเ่ี รากำ� ลังมีปัญหาอยู่ในไฟล์ Home.js
พร้อมยงั คบั พรอ้ มแล้วลยุ ตอ่ โลด้ !!
ใชง้ าน redux และ react-redux
react-redux เปน็ modules ทจี่ ะท�ำให้ Component สามารถเขา้ ถงึ redux ได้นะครับ มาดูวิธีใช้งานกนั ดีกวา่ ครับ
1. แก้ไขไฟล์ src/index.js เปน็ เหมือนโคด้ ด้านล่างน้คี รับ
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
import { Router, browserHistory } from 'react-router' A
import routes from './routes'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
function countAge(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const store = createStore(countAge)
store.subscribe(() => { console.log('subscribe', store.getState()) })
console.log('befor dispatch', store.getState())
store.dispatch({
type: 'INCREMENT'
})
มีโค้ดต่อดา้ นลา่ ง
67
ReactDOM.render( WorkShop WebApplication ด้วย React : 68
<Provider store={store}> โค้ดต่อจากด้านบน
<Router
history={browserHistory} B
routes={routes}
/>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
อธิบายโค้ด
A. import Provider จาก react-redux ที่ได้ตดิ ตงั้ ไปกอ่ นหน้านค้ี รบั
B. เอา Provider มาครอบ Router ไว้นะครับโดยสง่ properites ชือ่ store โดยเอา store ทีเ่ ราสร้างจากข้างบน
(const store = ....) สง่ เขา้ ไปครบั ต่อไป component ไหนจะเรียกใช้ state กจ็ ะเรียกใช้ได้แลว้ ครับ (อาจดู งง แตต่ อ้ งท�ำไป
ซักพักครบั จะเร่มิ เขา้ ใจ)
2. แก้ไขไฟล์ src/index.js เป็นเหมือนโค้ดด้านล่างนีค้ รบั (เราจะไมใ่ ช้ state แลว้ นะครับ แล้วให้จ�ำไว้นะครับ
ว่า การทเี่ ราเชอ่ื มกับ redux น้ันดว้ ย connect เราจะได้ props มาใชง้ าน สงั เกตไุ ดจ้ ากโค้ดด้านล่างนะครบั เรยี กใช้
this.props กันใหพ้ รึมเลยทเี ดียว)
import React, { Component } from 'react' A
import { connect } from 'react-redux'
class Home extends Component { B
render() {
return (
<div>
<h3>อายุของคณุ : {this.props.age} ป<ี /h3>
<button onClick={this.props.increteAge}>+คลกิ บวกอาย<ุ /button>
<button onClick={this.props.decreteAge}>-คลิกลบอาย<ุ /button>
</div>
)
}
}
function mapStateToProps(state) { C
return {
age: state มโี ค้ดต่อด้านลา่ ง
}
}
68
WorkShop WebApplication ดว้ ย React : 69
โค้ดต่อจากดา้ นบน
function mapDispatchToProps(dispatch) { D
return {
increteAge: () => {
dispatch({type: 'INCREMENT'})
},
decreteAge: () => {
dispatch({type: 'INCREMENT'})
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home) E
อธบิ ายโค้ด
โคด้ ชดุ นี้จะท�ำใหเ้ ขา้ ใจการทำ� งานของ Redux และ react-redux ไดด้ ีมากๆ ครับ
A. การท่ี Component จะเขา้ ถึง store ของ redux ได้จะตอ้ งใชต้ วั connect ซึง่ นำ� เขา้ จาก react-redux
B. จะสงั เกตุว่าไมม่ ีการใช้ this.state แล้วนะครับ เราเปลยี่ นมาใช้ this.props แทน เพราะเราใช้ props เขา้ ถงึ
ส่วนตา่ งๆ ของ redux แทนครบั
E. ขา้ มมาขอ้ E ก่อนนะครบั (อยากขา้ ม 555 จะไดเ้ ขา้ ใจง่ายขึน้ ) ปกติเราจะ export default Home ใชม่ ้ัยครบั
แต่พอใช้ connect เราจะต้อง export default connect แทนเหมือนโคด้ ข้างบน โดยตวั connect จะรบั parameter 2 ตวั
(จรงิ ๆ มันมากกว่า 2 ตวั ครบั แต่ส่วนใหญ่ใช้กัน 2 ตวั ดูเพ่มิ เติมไดท้ ่ี https://github.com/reactjs/react-redux) ซ่งึ param-
eter 2 ตวั นนั้ คอื mapStateToProps, และ mapDispatchToprops โดยท้งั 2 ตัวตา่ งก็เปน็ function ครบั
C. mapStateToProps โดย function นจี้ ะรบั parameter เปน็ state ท่ไี ดม้ าจากตวั redux store (ในไฟล์
src/index.js) โดยในท่ไี ฟล์ src/index.js จะมี reducer ทช่ี อ่ื countAge นะครับถ้าจ�ำไม่ได้ย้อนกลับไปดูนะครับ แล้ว return
คา่ object ออกไป โดยในน้ีต้งั ช่อื object วา่ age
D. mapDispatchToProps โดย function นี้จะรับ parameter เปน็ dispatch แล้ว return object ซง่ึ ในโค้ด
จะ return properties ทชี่ ่อื ว่า increteAge และ decreteAge ซึ่งสรา้ งมันเป็น function ทีเ่ วลาเรยี กใชม้ นั กจ็ ะเรียกคำ� สงั่
dispatch แล้วทำ� การสง่ type ไปครบั
ท้งั mapStatetoProps และ mapDispatchToProps มันจะกลายไปเปน็ props ของ Home ดังนัน้ เวลาเรา
เรยี กใช้ เราจึงเรยี กใช้ดว้ ยคำ� ส่ัง this.props.age, this.props.increteAge และ this.props.decreteAge
โยงเสน้ การท�ำงานใหด้ ูว่ามนั สมั พันธก์ นั ยังไง
69
WorkShop WebApplication ดว้ ย React : 70
ปรบั โคด้ redux ให้ดูเป็นระบบ
จากโค้ดการใช้ redux ที่ผา่ นมาจะเห็นวา่ ตัว reducer (function countAge) มนั กองกนั อยูท่ ไ่ี ฟล์ src/index.js ซึ่ง
จริงๆ เขาจะไม่ท�ำกนั หรอกครับ เขาจะแยกออกเปน็ ไฟล์ reducer แทน ผมว่าเรามาเรม่ิ ลงมอื กันเลยดกี ว่าครบั
1. สร้างไฟล์ reducers.js ไว้ที่โฟลเดอร์ src (src/reducers.js) แลว้ ใสโ่ คด้ ด้านลา่ งน้ี
import { combineReducers } from 'redux' A
function countAge(state = 0, action) { B
switch (action.type) { C
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
const reducers = combineReducers({
counter: countAge
})
export default reducers
อธบิ ายโคด้
A. combineReducers ตัวนี้จะใช้รวม reducer หลายๆ ตัว เพราะในการท�ำงานจรงิ เราจะมี reducer หลายๆ
ตวั คดิ งา่ ยๆ คอื มีเพจกเ่ี พจ ก็จะมี reducer ตามจำ� นวนเพจ (โดยประมาณนะครบั จะได้เขา้ ใจงา่ ยๆ)
B. function countAge ทย่ี กมาจากไฟล์ src/index.js ครับ
C. สง่ั combineReducers ออกไปเป็น object ทม่ี ีคียเ์ ป็น couter ดงั นน้ั ในไฟล์ Home.js จากเดิมเคยเรียกใช้
age: state ก็ต้องเปลี่ยนใหม่เป็น age: state.counter
2. แกไ้ ขไฟล์ src/index.js แล้วแก้โค้ดเปน็ ดา้ นล่างนี้ สังเกตวุ า่ โค้ดโดนตดั ออกไปเยอะเลยหละครับ
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
import { Router, browserHistory } from 'react-router'
import routes from './routes'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducers from './reducers'
const store = createStore(reducers)
มีโคด้ ตอ่ ดา้ นลา่ ง
70
ReactDOM.render( WorkShop WebApplication ด้วย React : 71
<Provider store={store}> โคด้ ต่อจากดา้ นบน
<Router
history={browserHistory}
routes={routes}
/>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
3. แก้ไขไฟล์ src/pages/Home.js โดยแกท้ ี่ function mapStateToProps
function mapStateToProps(state) {
return {
age: state
}
}
แก้ไขเปน็
function mapStateToProps(state) {
return {
age: state.counter
}
}
เสรจ็ แล้วกด็ ูผลงานที่ Browser ได้เลยครับ โดยคลิกป่มุ +คลิกบวกอายุ ใหต้ วั เลขเยอะๆ หน่อยนะ แลว้ คลิกเมนู
Photo สลบั กบั เมนู Home ไปมา ก็จะเห็นวา่ คา่ ตวั เลขอายุ ไมเ่ ปลย่ี น แสดงว่าคณุ คือผู้ส�ำเรจ็ ราชการ Redux แลว้ หละครับ
Yes !! ท�ำไดแ้ ลว้ โว้ย
71
WorkShop WebApplication ด้วย React : 72
หน้าจอ User
หนา้ จอนี้เราจะท�ำการโหลดข้อมูล user จากเวบ็ https://jsonplaceholder.typicode.com ซ่งึ เวบ็ นเี้ ขาใหบ้ รกิ าร
Fake Online REST API for Testing and Prototyping ซึ่งแปลเป็นไทยแบบบ้านๆ กค็ ือ เวบ็ ทใี่ ห้บรกิ ารทดสอบดงึ ข้อมูล
นั่นเองครบั โดยหนา้ จอน้เี ราจะดึงเอา user ท้ังหมดมาแสดงครับ พร้อมแล้วลยุ โลด้ !!
โครงสร้างไฟล์เม่ือท�ำเสร็จจะไดแ้ บบภาพด้านล่างนน้ี ะครับ
1. แกไ้ ขไฟล์ src/pages/User.js แล้วปรับเป็นโคด้ ดา้ นลา่ งนี้
72
WorkShop WebApplication ดว้ ย React : 73
import React, { Component } from 'react'
import UserList from '../components/Users/UserList'
class Home extends Component { A
state = { data: null, isLoading: false }
componentDidMount() { B
this.setState({ isLoading: true })
fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
.then(result => this.setState({ data: result, isLoading: false }))
}
render() { C
const { data, isLoading } = this.state ED
return (
<div>
<h1>Users</h1>
{isLoading && <div>Loading...</div>}
<UserList data={data} />
</div>
)
}
}
export default Home
อธิบายโค้ด
A. กำ� หนดค่าเร่มิ ต้นให้ state โดยท่ี data เราจะใชเ้ ก็บขอ้ มลู ท่ไี ดจ้ ากการ load ข้อมลู มาครับ และ isLoading
กำ� หนดเพอ่ื ให้มันแสดง loading ระหวา่ งทมี่ ันโหลดข้อมูล
B. ในการโหลดข้อมลู (การ fetch) จะนิยมทำ� กนั ที่ event ท่ชี อ่ื ว่า componentDidMount นะครับ โดยในโค้ด
this.setState({ loading: true }) = ส่งั ให้ isLoading เป็น true ไว้กอ่ น (มนั จะแสดงขอ้ ความ loading)
fetch .... = เปน็ ค�ำสัง่ get ขอ้ มลู ถา้ ใครเคยใช้ jquery มันกจ็ ะเหมือนค�ำส่งั $.ajax({url: resturl, ..... นน่ั แหละ
ครับ แต่ใน es6 มันมตี วั fetch ให้เขา้ มาใช้งานไดง้ า่ ยขนึ้ ครบั (นอกจาก fetch แลว้ ยงั มี library ภายนอกทีช่ ่ือ axios ซง่ึ
ผมชอบมากกวา่ การใช้ fetch แต่ในบทนต้ี อ้ งสนอการใช้ fetch ใหร้ ูพ้ ืน้ ฐานกอ่ นครับ ใครสนใจก็ลอง search การใช้
axios ดนู ะครับ มันงา่ ยกวา่ fetch อกี นะ)
ในการใช้ fetch นนั้ มันจะคนื คา่ ออกมาเปน็ Promise นะครบั ดงั น้ันจึงต้องท�ำการ then กอ่ น และจะสงั เกตุ
วา่ มันมี then 2 ตัว เนอื่ งจากวา่ เม่อื fetch ขอ้ มูลแล้วมันจะไดเ้ ปน็ object มากอ่ นครบั เรายงั เอาไปใชไ้ ม่ได้ เลยตอ้ งท�ำการ
convert เป็น json ก่อน สว่ น then ตัวที่ 2 กเ็ ป็นการ set data ให้ state แลว้ เปลยี น isLoading เป็น false ครบั
C. Destructure ตวั state เกบ็ ไวใ้ นตวั แปร
D. เช็คก่อนว่า isLoading เปน็ true มัย้ ถา้ ใช่ กแ็ สดงข้อความ Loading....
E. เรียกใช้ component UserList โดยสง่ porps ชอ่ื data ซง่ึ จะโยน data ทีไ่ ด้จากการ fetch ใหไ้ ป data ตวั น้ี
จะเป็น array แล้วนะครับ array แบบมี object ขา้ งใน แต่ถา้ ตอนนด้ี ูที่หน้า browser จะ error นะครับเพราะเรายังไม่ได้
สร้าง UserList ว่าละกไ็ มส่ รา้ งกนั เล้ย!!
73
WorkShop WebApplication ด้วย React : 74
Component UserList
component นีจ้ ะใช้ในการรับ ค่ามากจาก src/pages/User.js เพื่อ loop แลว้ จะส่งต่อไปยงั อกี component หนึง่
หลายคน งง ทำ� ไมทำ� ยุ่งยากแบบนี้ ทำ� ไมไม่ท�ำใหจ้ บในหน้าเดยี ว
ผมจะบอกแบบนี้ครับ ทีมวิศวกรณ์ของ facebook บอกไวว้ า่ ให้เราแตก component ใหย้ ่อยท่สี ุดเท่าท่ีเหน็
สมควร เพ่อื ความสะดวกในการควบคุมการทำ� งาน .... ฟังแล้วก็ยัง งง ใช่มั้ยครับ โอเค ผมวา่ เรามาลงมอื ท�ำกนั ดกี วา่ จะได้
หายงองู 2 ตวั
1. สร้างไฟล์ UserList.js ไวท้ ี่ src/components/Users (สรา้ ง folder ขึ้นมาใหมน่ ะครบั ) แลว้ ใสโ่ ค้ดตามนี้ครับ
import React, { Component } from 'react'
import User from './User'
class UserList extends Component { A
render() { B
const { data } = this.props
return (
<div>
{data && data.map(e => {
return (
<User data={e} key={e.id}/>
)
})}
</div>
)
}
}
export default UserList
อธิบายโคด้
A. ทำ� การ Destructure ค่าที่สง่ มาจาก props (src/pages/User.js)
B. เช็คกอ่ นว่า data มขี อ้ มลู หรอื ไม่ ถ้ามีค่อย map (loop ขอ้ มูลออกมา) โดย สง่ ขอ้ มลู ต่อไป component User
อีกทหี นึ่ง
โดยสง่ props ช่อื data โดยเอาค่า e ซึ่งเป็นค่าทไี่ ด้จากการ map ข้อมูล อยากรวู้ ่าค่าขา้ งในมอี ะไรดไู ดท้ ลี่ งิ ค์น้ี
ครบั https://jsonplaceholder.typicode.com/users
props ทชี่ ื่อ key ตัวนีส้ ำ� คัญนะครบั เพราะเวลา map หรอื loop ข้อมูลใส่กับ component หรอื tag ต่างๆ
ทาง react บอกไวว้ า่ จะตอ้ งก�ำหนดคยี ์ด้วย (คอื เวลา react ท�ำ virsual dom มันจะตอ้ งเอาคียไ์ ปเปรียบเทียบด้วยหนะครบั )
ดงั น้นั ผมจึงก�ำหนด key = {e.id} โดย id มนั ก็จะเปน็ id ของ user ท่ีได้จากข้อมลู ครับ
ตอนน้ีมี component ใหม่เพิม่ ข้นึ มาอกี ละ เราไปสร้างกนั เถอะครับ ไมง่ น้ั ถ้าดูท่ี browser ตอนนีม้ ันกจ็ ะ er-
ror แดงเถือกเลยหละครบั
Component User
component นจ้ี ะใช้ในการรับ ค่ามากจาก src/components/UserList ข้างบนนะครบั เพอ่ื แสดงชื่อ user ที่เรา
ต้องการจรงิ ๆ ละ
1. สร้างไฟล์ User.js ไวท้ ่ี src/components/Users แล้วใส่โค้ดตามน้คี รบั
74
WorkShop WebApplication ดว้ ย React : 75
import React, { Component } from 'react'
import { Link } from 'react-router'
class User extends Component {
render() {
const { data } = this.props A
return (
<div>
<h3>
<Link to={`/album/${data.id}`}>{data.name}</Link> B
</h3>
</div>
)
}
}
export default User
อธิบายโคด้
A. Destructure ค่าทีส่ ่งมาจาก props (จาก src/components/Users/UserList.js)
B. สรา้ งลงิ ค์ (Link ยังจ�ำกนั ได้ใชม่ ยั้ ครับ) เพื่อไปยัง url: album โดยส่งค่าไปด้วยในท่ีนี้คอื id ของ user ขา้ ง
ใน Link ก็ แสดง data.name ออกมา มนั คอื ชอ่ื ของ User อยากให้ดทู ่ี url น้คี รับ https://jsonplaceholder.typicode.
com/users จะได้มองออกว่ามี data อะไรบา้ งครับ แต่ ตอนนีไ้ มต่ อ้ งคลกิ ลงิ ค์นะครบั เพราะเราะยังไมไ่ ดเ้ พิ่มใน src/
routes.js (คลกิ แลว้ มันจะ error) ดแู คก่ ารแสดงผลหน้า browser ก่อนครับจะไดเ้ หมอื นรปู ดา้ นล่างนี้
เอาเมาส์ชที้ ล่ี ิงค์ สงั เกตุดา้ นลา่ งจะมีหมายเลขต่อ
ทา้ ย url ด้วย นั้นคือผลจากท่ีเราส่ง parameters
<Link to={`/album/${data.id}`}>
75
WorkShop WebApplication ดว้ ย React : 76
ปรับหน้าจอ User ไปใช้ redux
ในการทำ� งานจริงนะครับพวก data ทไ่ี ดม้ าจาก api เขาจะนยิ มเกบ็ ไวใ้ น redux store มากกว่าครับ ประโยชน์เพ่ือ
การ Caching หรือการ Reuse ครับ แตก่ ่อนอ่นื ผมจะแสดงโค้ดในสว่ นของ mapDispatchToProps ในการ fetch ข้อมูลจาก
API วา่ ปญั หามนั คืออะไร
โค้ดต่อไปนเ้ี ปน็ โค้ดท่ผี ิดวิธีนะครับแต่ผมอยากใหท้ ำ� ตามเพื่อจะไดร้ ้ปู ญั หาว่าจะใช้ redux ยังไงกบั การ load API
จากภายนอก โดยท่ี
1. แก้ไขไฟล์ src/pages/Home.js แลว้ แกเ้ ป็นโค้ดด้านล่างนีค้ รับ
import React, { Component } from 'react'
import UserList from '../components/Users/UserList'
import {connect} from 'react-redux'
class Home extends Component { A
componentDidMount() {
this.props.loadUsers()
}
render() { B
const { users } = this.props
return (
<div>
<h1>Users</h1>
<UserList data={users} />
</div>
)
}
}
function mapStateToProps(state){ C
return {
users: state.users
}
}
function mapDispatchToProps(dispatch){ D
return{
loadUsers: ()=>{
dispatch({
type: 'LOAD_USERS'
})
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
อธิบายโค้ด
A. สว่ นน้ีเดมิ ทเี รา fetch จาก API ใชม่ ้ัยครับ ตอนนีเ้ ราเปล่ียนใหมค่ รบั เปน็ การเรียกใช้ loadUsers() ในข้อ D
B. ส่วนนกี้ เ็ ปลีย่ นจากการเอาข้อมูลจาก state เปน็ เอาขอ้ มูลจาก props แทนครับ
76
WorkShop WebApplication ดว้ ย React : 77
C. สว่ นนก้ี ็ท�ำการเรยี กใช้ state จากตวั store แสดงว่าตอ้ งมกี ารเพม่ิ reducers ชื่อ users แน่ๆ เพราะมีการเรยี ก
ใช้ state.users
D. ส่วนนี้กจ็ ะเป็นการสงั่ dispatch เพอ่ื ใหไ้ อ้ตวั reducer ทช่ี ื่อ users มันท�ำการเปรยี บเทียบตวั action.type ว่า
เป็น type ที่มขี ้อความว่าอะไร ดงั นน้ั เรากต็ ้องไปแกไ้ ขไฟล์ reducers.js กนั ครับ
2. แก้ไขไฟล์ src/reducers.js โดยแก้เปน็ โค้ดด้านลา่ งน้คี รับ
import { combineReducers } from 'redux'
function countAge(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function users(state = {}, action) {
switch (action.type) {
case 'LOAD_USERS':
const data = fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
return data
default:
return state
}
}
const reducers = combineReducers({
counter: countAge,
users
})
export default reducers
อธิบายโค้ด
ใน reducers.js เราเพ่ิม function ช่อื users เขา้ มา และใน combineReducers กเ็ พิ่ม users เข้าไปเช่นกัน ท�ำให้
component User.js เรยี กใช้ state.users แล้วใน function users ผมก็ไดย้ กตวั fetch จาก User.js มาใส่เพื่อ return ค่า
ออกไป (เพราะใน User.js เราไมไ่ ด้ fetch แล้วเกบ็ ไวใ้ น state แลว้ ไงครบั แต่เราเอามาไว้ใน reducers แทน)
แตส่ ่งิ ท่ผี มท�ำมนั “ผดิ ผดิ ผิด” (ผดิ แมง้ 3 ตวั เลย) “อ้าว...ท�ำไมหละ” กเ็ พราะวา่ ตัว redux เขากำ� หนดไว้ว่าใน re-
ducers หา้ มท�ำ side effect น่นั ก็คอื หา้ ม fetch data นัน่ เองครบั เพราะมันจะกระทบกับหลายๆ อย่าง อกี อยา่ งในโคด้ น้ี
มันก็ return คา่ ออกมาไม่มีอะไรหรอกนะครับ เพราะ fetch มันสง่ promise แม้เราจะสั่ง then แลว้ กต็ ามเราก็ไมค่ วรท�ำครบั
หนำ� ซำ้� มนั ยัง Error อีก “เฮอ้ !! หนักใจจรงิ ๆ”
“เอ....แลว้ เราจะท�ำไงด.ี ...ปิ้ง...คิดออกละ” เรากเ็ อา fetch ไปใสใ่ น dispatch ในไฟล์ User.js ส!ิ ! เออ เข้าทา่ ดีนะ
ว่าละเราก็ไปแก้ไขไฟล์ src/pages/User.js กันดกี ว่าครบั
77
WorkShop WebApplication ด้วย React : 78
3. แกไ้ ขไฟล์ src/pages/User.js โดยใน function mapDispatchToProps ให้เพิม่ payload เข้าไปแบบนคี้ รบั
function mapDispatchToProps(dispatch){
return{
loadUsers: ()=>{
dispatch({
type: 'LOAD_USERS',
payload: fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
})
}
}
}
อธบิ ายโค้ด
ในการ dispatch นอกจากเราจะสง่ type ไดแ้ ลว้ มันยังสามารถส่งขอ้ มลู อนื่ ได้อีกโดยสง่ payload เขา้ ไปครับ แล้ว
เราก็ส่ง fetch เขา้ ไปใน payload
4. แก้ไขไฟล์ src/reducers.js โดยแกท้ ี่ function users เป็นแบบนค้ี รับ
function users(state = {}, action) {
switch (action.type) {
case 'LOAD_USERS':
return action.payload
default:
return state
}
}
อธบิ ายโคด้
ฝั่ง reducers เม่อื ได้รบั dispatch มนั จะเขา้ มาทต่ี วั แปร action ใช่ม้ัยครบั ดังนนั้ ฝง่ั User.js เราส่ง payload มา ตวั
action ก็ต้องรบั คา่ เปน็ action.payload แบบน้ีครับ เหมอื นในโคด้ เรากจ็ ะ return action.payload ไปโดยที่เราคาดหวงั วา่
มันคอื ขอ้ มลู ท่ีใช้งานได้ ลอง save แลว้ ดูที่ browser ดูสิครับ “WTF อะไรอกี เนีย่ ะ แดงเถือกเลย” ใชค่ รบั มนั error ครับ
สาเหตกุ ็เพราะส่งิ ทีเ่ ราสง่ เข้าไปใน payload จากการ fetch มันคอื promise ดงั นนั้ สว่ นไหนของ component ที่มี
การเรยี กใช้ state.user นัน่ คือมันจะได้เปน็ promise ตามไปดว้ ยซึง่ กฏของ redux คือ action จะตอ้ งเปน็
object เสมอ ...แล้วเราจะทำ� ไงดีหละ “เลกิ เขยี น react เลยดีกว่าม้ยั ยากเหลอื เกนิ ” ใจเยน็ ๆ กอ่ นครบั ทางออกทผี่ มจะ
แนะน�ำคือการใช้ redux-promise-middleware (ตวั นีเ้ ปน็ middleware ไม่ง้นั ต้องเขยี นเองยุ่งยากมาก)
ตดิ ต้งั redux-promise-middleware
1. ที่ terminal กด ctrl+c เพื่อยกเลกิ การรนั
2. พมิ พ์ npm i redux-promise-middleware@4 -S แล้วกด Enter (ผมจะใช้ตัว v.4 แต่ปัจจุบันมี v.5 แล้ว
แต่พง่ึ ออกมาไมน่ าน ทางท่ดี ีใช้ตัว v.4 จะดกี ว่านะครบั )
3. ตดิ ต้งั เสร็จก็พมิ พ์ npm start เพ่อื รันโปรเจคตามเดมิ ครบั
78
WorkShop WebApplication ดว้ ย React : 79
ใชง้ าน redux-promise-middleware
หลกั การท�ำงานของ redux-promise-middle มันจะง่ายๆ ครบั คอื เม่อื มกี าร dispatch แลว้ ส่ง payload ที่เป็น
promise ไปให้ไฟล์ reducers.js ตวั redux-promise-middle มนั จะจดั การ promise ให้เราก่อน เมอ่ื ได้ขอ้ มูลแลว้ มนั
จงึ จะส่ง object ให้ action.payload ใน reducers.js เอง แม๊พขงิ ๆ (ค�ำนี้ผมวา่ เขาเลิกใช้แลว้ นะครบั 555) โดยขนั้ ตอนมี
งา่ ยๆ ตามนค้ี รับ
1. แกไ้ ขไฟล์ src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
import { Router, browserHistory } from 'react-router' A
import routes from './routes' B
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import promiseMiddleware from 'redux-promise-middleware'
import reducers from './reducers'
const store = createStore( C
reducers,
applyMiddleware(promiseMiddleware())
)
ReactDOM.render(
<Provider store={store}>
<Router
history={browserHistory}
routes={routes}
/>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
79
WorkShop WebApplication ด้วย React : 80
อธบิ ายโคด้
A. เพม่ิ ตัว applyMiddleware เขา้ มานะครับ
B. import ตัว redux-promise-middleware เข้ามาครบั ในชือ่ promiseMiddleware
C. ส่วนน้เี ปน็ การเรยี กใช้ท่ีงา่ ยโคตรๆ ครับ เน่ืองจากตวั createStore มนั จะรับ parameter อีกตวั หนงึ่ ทีเ่ ปน็
middleware ด้งั นนั้ เรากแ็ คเ่ อาตัว promiseMiddleware() มาใส่ไวใ้ น createStore เปน็ parameter ตัวท่ี 2 ต่อจาก
reducers แบบโคด้ ขา้ งบนแคน่ ี้ครับ ตัว promiseMiddleware เป็น function นะครับดงั นัน้ เวลาเรียกใชก้ ็ใส่ () ต่อท้าย
ด้วยมันจะไดท้ ำ� งานทันที
2. แกไ้ ขไฟล์ src/reducers.js
import { combineReducers } from 'redux' A
function countAge(state = 0, action) { B
C
switch (action.type) { D
case 'INCREMENT':
return state + 1 80
case 'DECREMENT':
return state - 1
default:
return state
}
}
const initialState = {isRejected: true, data: null}
function users(state = initialState, action) {
switch (action.type) {
case 'LOAD_USERS_PENDING':
return {
isRejected: false,
data: null
}
case 'LOAD_USERS_FULFILLED':
return {
isRejected: false,
data: action.payload
}
case 'LOAD_USERS_REJECTED':
return {
isRejected: true,
data: null
}
default:
return state
}
}
const reducers = combineReducers({
counter: countAge,
users
})
export default reducers
WorkShop WebApplication ด้วย React : 81
อธบิ ายโคด้
ใน reducers.js เราจะแกไ้ ขแค่ท่ี function user ครับ
A. สรา้ งตัวแปรก�ำหนดคา่ เร่ิมต้นให้ state ครับ (ก�ำหนดหรอื ไม่ก็ได้ แตค่ วรก�ำหนดครบั )
B, C, D คือ type ทีเ่ ราสง่ มาจาก User.js ยังจำ� ไดใ้ ช่ม้ัยครับ แต่...แต่...จะสงสัยว่า เราส่งมาแค่
dispatch({type: “LOAD_USERS”}) .... ครับถกู แลว้ ครับ เราสง่ มาแค่ LOAD_USERS แต่ตวั redux-prom-
ise-middleware มันจะส่งสถานะใหเ้ รา 3 สถานะแลว้ เติมคำ� ใหเ้ ราอตั โนมตั ิ โดยมันจะส่ง
_PENDING = สถานะนค้ี อื ก�ำลงั ประมวลผล เราใชส้ ถานะนใ้ี นการสรา้ งตัวแปร loading เพือ่ เอาไปใช้งานได้ครับ
_FULFILLED = สถานะนีค้ ือไดข้ อ้ มูลมาเสร็จเรยี บร้อย
_REJECTED = สถานะนคี้ ือผดิ พลาด (ความผดิ พลาดน้จี ะไม่เกยี่ วกบั ว่าเราเชือ่ มตอ่ url ไม่ได้นะครับ เช่น url ไมถ่ กู
หรือใช้เนต็ ไมไ่ ด้ อันน้ไี ม่เกีย่ วครับ มันเป็นความผิดพลาดเร่ืองขอ้ มลู ครบั )
ดังน้นั redux-promise-middleware มันเลยเอา type ทเ่ี ราสง่ มามารวมกบั type (ขอ้ ความ) ที่ middle ส่งมาให้
รวมกันกลายเป็น LOAD_USERS_PENDING, LOAD_USERS_FULFILLED และ LOAD_USERS_REJECTED (ผมชอบมาก
เลยทมี่ ันส่งสถานะมาใหแ้ บบนี้ สะดวกดคี รับ)
3. ไฟล์ src/pages/User.js จากโคด้ ลา่ สดุ ทผี่ ิด เราก็ปรับนดิ หน่อยครับ (ตามตัวหนาครบั ) ส่วนตัว mapDis-
patchToProps นี้เราไม่ตอ้ งปรบั ละใช้แบบเดมิ ไดเ้ ลย (เสรจ็ แล้วกด็ ูผลงานทห่ี น้าเว็บไดเ้ ลยนะครบั )
import React, { Component } from 'react'
import UserList from '../components/Users/UserList'
import { connect } from 'react-redux'
class Home extends Component {
componentDidMount() {
this.props.loadUsers()
}
render() {
const { users } = this.props
if (users.isRejected) {
return <div>Error....</div>
}
return (
<div>
<h1>Users</h1>
<UserList data={users.data} />
</div>
)
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
มีโคด้ ตอ่ ดา้ นลา่ ง
81
WorkShop WebApplication ด้วย React : 82
โคด้ ตอ่ จากดา้ นบน
function mapDispatchToProps(dispatch) {
return {
loadUsers: () => {
dispatch({
type: 'LOAD_USERS',
payload: fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
})
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home)
ปรับโคด้ ใหด้ ีกวา่ เดิม
ถ้าสงั เกตโุ ค้ดใน User.js จะเหน็ วา่ ในส่วน mapDispatchToProps เราฝัง url ไว้ที่ไฟล์ๆ น้นั แลว้ ถ้าเกดิ วา่
1. Component ตัวอน่ื ตอ้ งการใชข้ อ้ มูลกต็ ้องส่ัง dispatch แลว้ ส่ง url น้ีให้ action ใน reducers แลว้ ถา้ พมิ พ์
url ผิดก็ซวยไป
2. ถ้าเราต้องการเรยี กดูข้อมลู ใน store วา่ มีข้อมูล users หรอื ไมถ่ า้ มจี ะไดไ้ ม่ต้องไปโหลดจาก url มาใหม่ กจ็ ะท�ำ
ไมไ่ ด้ เนอื่ งจากหากเราต้องการเช็ค state ใน redux store จะต้องใชค้ ำ� ส่ัง getState() แตท่ �ำไมไ่ ด้หรอกครับเพราะเราไม่มี
function นี้ให้ใช้งาน (ถ้าจำ� ได้ ตอนแรกท่ีเราใช้ redux เราจะเรียกดู state ไดด้ ้วย store.getState())
3. การสร้าง mapDispatchToProps ไว้ใน component แบบนี้ ถ้าเกดิ เรามี function มากกวา่ loadUsers
หละ อาจจะมี saveUsers, deleteUsers, findUsers และอื่นๆ อีก แน่นอนวา่ โคด้ ใน component จะต้องรกมากๆ ซึ่งนัน้ มนั
คือนรกของ component ชดั ๆ ดังน้นั ทเ่ี ขาท�ำๆ กันคือเขาแยกสว่ นของ mapDispatchToProps ไปไว้ในไฟล์ action
แยก Dispatch ไปไว้ที่ Actions
1. สรา้ งไฟล์ช่ือ actions.js ไวท้ ี่ src แลว้ พมิ พ์โคด้ ด้านล่างน้คี รบั
export function loadUsers() {
return (dispatch) => {
return dispatch(fetchUsers())
}
}
function fetchUsers() {
return {
type: 'LOAD_USERS',
payload: fetch('https://jsonplaceholder.typicode.com/users')
.then(result => result.json())
}
}
อธิบายโคด้
จะเหน็ ว่าผมได้ export function loadUsers() นน้ั มกี ารส่งั return dispatch ตัว function ขอ่ื fetchUsers()
สังเกตุไว้นะครบั ว่ามนั return dispatch เปน็ function
82
WorkShop WebApplication ดว้ ย React : 83
2. แก้ไขไฟล์ src/pages/User.js แลว้ ปรบั โค้ดเปน็ ดา้ นล้างน้ีครับ
import React, { Component } from 'react' A
import UserList from '../components/Users/UserList'
import { connect } from 'react-redux' B
import { loadUsers } from '../actions'
class Home extends Component {
componentDidMount() {
this.props.dispatch(loadUsers())
}
render() {
const { users } = this.props
if (users.isRejected) {
return <div>Error....</div>
}
return (
<div>
<h1>Users</h1>
<UserList data={users.data} />
</div>
)
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
export default connect(mapStateToProps)(Home)
อธิบายโคด้
A. import ตวั function loadUsers จากไฟล์ actions.js ท่เี ราพ่ึงสร้าง
B. สงั่ dispatch ตวั loadUsers() คล้ายๆ ของเดมิ เลยใช่ม้ัยครับ ซ่งึ เราสามารถสง่ั dispatch แบบน้ีก็ไดค้ รับ ท�ำให้
เราไมจ่ ำ� เป็นต้องไปสร้าง mapDispatchToProps ครับ ในโค้ดเลยตัด function mapDispatchToProps ออกไดค้ รับ
พอ save แล้วลองดูที่ browser ครับ.......แนน่ อนครบั มัน Error เหมือนภาพด้านลา่ งครับ
83
WorkShop WebApplication ดว้ ย React : 84
สาเหตุของ Error
ถ้าดทู ี่ componentDidMount เราสัง่ dispatch แบบ this.props.dispatch(loadUsers()) ซงึ่ เจ้าตัว loadUs-
ers() น้ีมัน return function ออกมานะครบั ไมเ่ ชอื่ กลบั ไปดูท่ไี ฟล์ src/actions.js ไดค้ รับ ซ่ึง redux บอกไว่ การ dispatch
นน้ั จะตอ้ งสง่ object ให้ action เสมอ แต่นีเ้ ราดนั สง่ function ไปให้ action มันเลย error ครบั
หลายคิดอยู่ตอนนว้ี า่ “แล้ว payload ก่อนหนา้ นี้ทส่ี ง่ fetch ไปมนั เปน็ promise นิ มันไม่ใช่ object” ทำ� ไมไม่ Er-
ror แสดงว่า “ลมื แลว้ ใชม่ ย้ั ครับ” วา่ เรามี middleware ช่อื redux-promise-middle ทจี่ ดั การตวั promise อยู่มนั เลย
ไม่ Error ไงครับ
ดังนัน้ ปญั หานเ้ี ราจึงสามารถแก้ไขไดด้ ้วย middleware อกี ตวั ท่ีชื่อ redux-thunk ครับ
ตดิ ตั้ง ReduxThunk
1. ที่ terminal กด ctrl+c เพอ่ื ยกเลิกการรนั โปรเจค
2. ตดิ ตัง้ redux-thunk โดยพิมพ์ npm i redux-thunk -S แล้วกด Enter
3. พอตดิ ตัง้ เสร็จก็รันโปรเจคได้โดยพมิ พ์ npm start (แต่มนั ก็จะ error นะครบั แตร่ ันไว้กอ่ น)
ใช้งาน ReduxThunk
ตวั redux-thunk น้ใี ช้งานงา่ ยมากๆ ครบั เหมอื น redux-promise-middleware โดย
1. แกไ้ ขไฟล์ src/index.js แลว้ แกโ้ ค้ดเปน็ ด้านล่างนีค้ รบั
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import registerServiceWorker from './registerServiceWorker'
import { Router, browserHistory } from 'react-router' A
import routes from './routes'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import promiseMiddleware from 'redux-promise-middleware'
import thunk from 'redux-thunk'
import reducers from './reducers'
const store = createStore( B
reducers,
applyMiddleware(thunk, promiseMiddleware()) มีโค้ดต่อดา้ นล8า่ 4ง
)
ReactDOM.render( WorkShop WebApplication ด้วย React : 85
<Provider store={store}> โคด้ ต่อจากด้านบน
<Router
history={browserHistory}
routes={routes}
/>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
อธิบายโค้ด
1. import redux-thunk เข้ามาใชง้ าน
2. applyMiddleware นนั้ สามารถใส่ middleware ได้หลายตัวนะครบั โดยท่ี thunk จะตอ้ งอยู่ก่อน prom-
iseMiddleware นะครับ
แค่น้แี หละครับ งา่ ยๆ จากนน้ั บันทกึ แล้วก็ดผู ลที่ browser ได้เลย “ไม่ Error แล้วครบั ...แจว๋ ใชม่ ั้ยหละ”
หนา้ จอ Album
สำ� หรับหน้าจอนีจ้ ะแสดงชือ่ Album ของรปู ภาพ โดยจะแสดงตาม id ของ user สามารถดู url ของ Album ไดท้ ี่
https://jsonplaceholder.typicode.com/
85
WorkShop WebApplication ด้วย React : 86
id ของ user ทไ่ี ดเ้ ราจะได้มาจากการคลิก Link รายชื่อ user ทมี่ าจากหน้าจอ src/pages/User.js ซ่งึ ตอนนีเ้ ราได้
ทำ� ลิงคไ์ ว้แล้ว แต่ยังไมไ่ ดท้ �ำการจดั การ router และสรา้ งหนา้ จอ album ไว้รองรบั ดังน้นั เรามาเร่ิมสร้างกนั เลยครับ
1. สร้างไฟล์ Album.js ไวท้ ่ี src/pages แล้วพมิ พ์โคด้ ดา้ นลา่ งนี้
import React, { Component } from 'react'
class Album extends Component { A
render() {
return (
<div>Album {this.props.params.userID} {this.props.params.title}</div>
)
}
}
export default Album
อธิบายโค้ด
A. การรับตัวแปรมาจาก url ที่ถูกส่งมาจาก router นนั้ จะใช้ค�ำสงั่ this.props.params แล้วตามดว้ ย .ช่ือ
parameter ทสี่ ่งมา ในทนี่ ี้คือช่ือ userID และ title
2. จัดการตวั Router โดยแกไ้ ขไฟล์ src/routes.js แล้วเพิ่ม childRoutes ตวั ใหม่เขา้ ไป
import App from './pages/App' A
import Home from './pages/Home'
import User from './pages/User' B
import Album from './pages/Album'
const routes = [{
path: '/',
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'user', component: User },
{ path: 'album/:userID(/:title)', component: Album }
]
}]
export default routes
อธิบายโคด้
A. import ไฟล์ Album.js เขา้ มาครับ
B. สร้าง childRoutes ใหมโ่ ดยเรียกใช้ component Album สังเกตทุ ี่ path จะมีการใช้ /:userID(/:title) น่นั
หมายความวา่ มกี ารส่ง parameter ชอ่ื userID สว่ น title จะมหี รอื ไมม่ กี ไ็ ด้ ถ้าเป็น php กจ็ ะเปน็ http://xxx.com/
album?userID=xx&title=xxxx แบบนีค้ รับ ดงั นัน้ โค้ดในไฟล์ src/pages/Album.js เวลารับ parameter จงึ ใช้คำ� ส�ำสง่ั
this.props.params.userID และ this.props.params.title
3. แก้ไขไฟล์ /src/components/Users/User.js โดยบรรทัด <Link .... แกจ้ ากเดมิ
<Link to={`/album/${data.id`}> เป็น <Link to={`/album/${data.id}/${data.name}`}>
เพราะเรามีการสง่ parameter อกี ตวั ไปด้วย (เป็นช่อื user ครับ)
86
WorkShop WebApplication ดว้ ย React : 87
ภาพตวั อยา่ งหนา้ จอ Album ทร่ี บั id จากการคลิกลิงค์รายชือ่
โหลดขอ้ มูล Album
ตอนน้ีข้อมลู ต่างๆ ของเราล้วนท�ำผา่ น redux ดังน้ันในการโหลดข้อมูล Album เรากจ็ ะท�ำผา่ น redux ด้วยเหมือน
กันครับ โดยวธิ ีการกจ็ ะท�ำคลา้ ยๆ กบั หนา้ จอ User ครับ “มะ...เริม่ กนั ดีกวา่ ”
1. แก้ไขไฟล์ src/reducers.js โดยเพิม่ function albums และใน combineReducers กเ็ พ่ิม albums เขา้ ไป
เหมอื นโค้ดด้านลา่ งนค้ี รบั
........
function albums(state = initialState, action) {
switch (action.type) {
case 'LOAD_ALBUMS_PENDING':
return {
isRejected: false,
data: null
}
case 'LOAD_ALBUMS_FULFILLED':
return {
isRejected: false,
data: action.payload
}
case 'LOAD_ALBUMS_REJECTED':
return {
isRejected: true,
data: null
}
default:
return state
}
}
const reducers = combineReducers({
counter: countAge,
users,
albums
})
......
87
WorkShop WebApplication ด้วย React : 88
2. แกไ้ ขไฟล์ src/actions.js โดยเพิ่มโคด้ ดา้ นล่างนต้ี อ่ จากโคด้ เดิมท่ีมีอยู่
export function loadAlbums(userID) {
return (dispatch) => {
return dispatch(fetchAlbums(userID))
}
}
function fetchAlbums(userID) {
return {
type: 'LOAD_ALBUMS',
payload: fetch(`https://jsonplaceholder.typicode.com/albums?userId=${userID}`)
.then(result => result.json())
}
}
อธิบายโค้ด
สังเกตุว่า function fetchAlbums มีการรบั ตวั แรกดงั นนั้ เวลาเรยี กใชต้ ้องส่งตวั แปรเข้ามาด้วยนะครบั และให้
ระวงั เคร่ืองหมายตรง fetch มันไมใ่ ช่ single qoute นะครบั เพราะเป็นการเชือ่ ม string กบั ตวั แปรจงึ ต้องใช้เครอ่ื งหมาย ` น้ี
3. แก้ไขไฟล์ src/pages/Album.js โดยปรบั เป็นโคด้ ด้านล่างนี้
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { loadAlbums } from '../actions'
import AlbumList from '../components/Albums/AlbumList'
class Album extends Component {
componentDidMount() {
} this.props.dispatch(loadAlbums(this.props.params.userID)) A
render() {
const { albums } = this.props
if (albums.isRejected) {
return <div>Not Load Albums</div>
}
return (
<div>
<h1>Album {this.props.params.title}</h1> B
<AlbumList data={albums.data}/>
</div>
)
}
}
function mapStateToProps(state) {
return {
albums: state.albums
}
}
export default connect(mapStateToProps)(Album) 88
WorkShop WebApplication ดว้ ย React : 89
อธิบายโค้ด
การทำ� งานก็จะเหมือนกับไฟล์ src/pages/User.js นะครบั แตส่ งิ่ ท่เี พิม่ เข้ามาคอื
A. ในการ dispatch เรามีการสง่ parameter เขา้ ไปด้วยครับเป็น id ของ user ที่สง่ มาจากไฟล์ src/routes.js ที่
ช่ือ userID
B. แสดงช่ือ user ทไี่ ด้รบั parameter ท่สี ง่ มาจากไฟล์ src/routes.js ท่ชี ่ือ title
ตอนน้ีถา้ ดูท่ี browser มนั ก็จะ error นะครับ เพราะเรายังไม่ไดส้ รา้ ง component ชือ่ AlbumList
3. สร้างไฟล์ชื่อ AlbumList.js ไวท้ ี่ folder src/components/Albums (folder Albums ยังไมม่ นี ะครับให้
สรา้ งขึน้ มาใหม่ด้วย จากน้ันพิมพโ์ ค้ดด้านล้างนล้ี งไปครบั
import React, { Component } from 'react'
import Album from './Album'
class AlbumList extends Component {
render() {
const { data } = this.props
return (
<div>
{data && data.map(e => {
return (
<Album data={e} key={e.id} />
)
})}
</div>
)
}
}
export default AlbumList
อธิบายโคด้
การทำ� งานกจ็ ะเหมอื นกับไฟล์ /src/components/Users/UserList.js นะครับ คือรับ props มาจาก src/pages/
User.js ท่ชี ่ือ data แล้วมา loop ดว้ ย map ก่อนทจ่ี ะ map ก็เช็คก่อนวา่ data มขี ้อมูลหรือไมค่ รบั จากนั้นก็ส่ง data ให้
component Album (src/components/Albums/Album.js) เพ่อื แสดงผล
แตถ่ ้าดูที่ browser ตอนนีก้ จ็ ะยงั error อยู่ เพราะยังไมไ่ ด้สรา้ ง component ชอ่ื Album ครบั
89
WorkShop WebApplication ดว้ ย React : 90
3. สร้างไฟล์ Album.js ไว้ท่ี /src/components/Albums แลว้ พมิ พโ์ คด้ ด้านลา่ งน้ีครบั
import React, { Component } from 'react'
import { Link } from 'react-router'
class Album extends Component {
render() {
const { data } = this.props
return (
<div>
<h3>
<Link to={`/photo/${data.id}/${data.title}`}>{data.title}</Link>
</h3>
</div>
)
}
}
export default Album
เมือ่ ดทู ่ี browser กจ็ ะได้หนา้ จอเหมือนภาพด้านล่างน้คี รับ ลองคลกิ จาก User ดกู ็จะแสดงชอื่ Album ตาม id ของ
User ครับ (แตอ่ ย่างพ่ึงคลกิ ลงิ คใ์ น Album นะครบั เพราะเรายงั ไมไ่ ด้สรา้ ง photo มนั จะ error เฉยๆ)
90
WorkShop WebApplication ดว้ ย React : 91
หนา้ จอ Photo
สำ� หรับหนา้ จอนจี้ ะแสดงภาพตาม id ของ Album ดู url เพมิ่ เตมิ ไดท้ ่ี https://jsonplaceholder.typicode.com/
photos (url นี้จะแสดงข้อมูลของภาพออกมาทุกตวั นะครบั มนั ไมแ่ สดงตาม id ของ Album แตเ่ ราจะทำ� ใหม้ ันแสดงตาม id
ของ Album) “วา่ ละก็ลุยกันต่อดกี ว่าครบั ”
1. เร่มิ จากแก้ไขตวั routes กอ่ นครับโดยแกไ้ ขไฟล์ src/routes.js โดยเพม่ิ childRoutes เขา้ ไปอกี ครบั ซ่ึง path
มันก็จะไป match กบั url ทถ่ี กู สง่ มาจากการคลิก Link จากไฟล์ src/components/Albums/Album.js
import App from './pages/App'
import Home from './pages/Home'
import User from './pages/User'
import Album from './pages/Album'
import Photo from './pages/Photo'
const routes = [{
path: '/',
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'user', component: User },
{ path: 'album/:userID(/:title)', component: Album },
{ path: 'photo/:albumID(/:title)', component: Photo }
]
}]
export default routes
2. แกไ้ ขไฟล์ src/actions.js โดยเพมิ่ function ดา้ นล่างนี้ลงไปต่อจากของเดมิ ทม่ี อี ย่คู รับ
.........
export function loadPhotos(albumID) {
return (dispatch) => {
return dispatch(fetchPhotos(albumID))
}
}
function fetchPhotos(albumID) {
return {
type: 'LOAD_PHOTOS',
payload: fetch(`https://jsonplaceholder.typicode.com/photos?albumId=${albumID}`)
.then(result => result.json())
}
}
91
WorkShop WebApplication ด้วย React : 92
3. แกไ้ ขไฟล์ src/reducers.js โดยเพิม่ function ดา้ นลา่ งนี้ลงไป และแกไ้ ข combineReducers โดยเพ่มิ
photos เข้าไปครบั
......
function photos(state = initialState, action) {
switch (action.type) {
case 'LOAD_PHOTOS_PENDING':
return {
isRejected: false,
data: null
}
case 'LOAD_PHOTOS_FULFILLED':
return {
isRejected: false,
data: action.payload
}
case 'LOAD_PHOTOS_REJECTED':
return {
isRejected: true,
data: null
}
default:
return state
}
}
const reducers = combineReducers({
counter: countAge,
users,
albums,
photos
})
export default reducers
โอเคครบั ตอนน้กี เ็ สรจ็ งานส�ำหรบั กรรมกรแลว้ ครบั ... “ใครเขยี นโค้ดมกั โดนเรยี กว่ากรรมกร” ตอ่ ไปก็เปน็ งานของ
Designer ผทู้ รงเกรยี ติในการรงั สรรค์ component ออกมาแสดงสูส่ ายตาชาวโลก ... “ผ่าง!!” ฟงั ดยู ่งิ ใหญ่ดมี ้ยั ครบั
4. สรา้ งไฟล์ Photo.js ไวท้ ่ี src/pages แลว้ ใส่โค้ดด้านลา่ งน้ีลงไปครบั (การทำ� งานก็เดิมๆ ครบั เหมอื นโคด้ จาก
ไฟล์ src/pages/Album.js)
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { loadPhotos } from '../actions'
import PhotoList from '../components/Photos/PhotoList'
class Photo extends Component { มีโคด้ ต่อดา้ นลา่ ง
componentDidMount() { 92
this.props.dispatch(loadPhotos(this.props.params.albumID))
}
WorkShop WebApplication ดว้ ย React : 93
โคด้ ต่อจากด้านบน
render() {
const { photos } = this.props
if (photos.isRejected) {
return <div>Not Load Photos</div>
}
return (
<div>
<h1>Photos by {this.props.params.title}</h1>
<PhotoList data={photos.data} />
</div>
)
}
}
function mapStateToProps(state) {
return {
photos: state.photos
}
}
export default connect(mapStateToProps)(Photo)
ตอนนี้ถ้าดูที่ browser ก็จะ error นะครับเพราะเรายังไมไ่ ด้สรา้ งไฟล์ PhotoList ว่าละกส็ ร้างกนั เลยครบั
5. สร้างไฟล์ PhotoList.js ไว้ที่ /src/components/Photos (folder Photos ยังไม่มีตอ้ งสร้างดว้ ยนะครับ)
แล้วพมิ พ์โคด้ ดา้ นลา่ งนีค้ รบั
import React from 'react'
import Photo from './Photo'
const PhotoList = (props) => {
const { data } = props
return (
<div>
{data && data.map(e => {
return (
<Photo data={e} key={e.id} />
)
})}
</div>
)
}
export default PhotoList
อธบิ ายโคด้
ไฟลน์ ต้ี ามจรงิ โคด้ มนั จะคล้ายๆ กบั ไฟล์ src/components/Albums/AlbumList.js ใชม่ ั้ยคับ แตจ่ ากโคด้ ทำ� อีก
แบบคือไม่ได้เขียนเป็น class component แตเ่ ขียนเป็น Stateless Functional Components การทำ� แบบน้ีมันกค็ อื การ
Optimise โค้ดเราครับ เพราะเราไมม่ คี วามจ�ำเป็นต้องใช้ Component เน่อื งจากเราไมไ่ ดใ้ ช้ state และไม่ได้ connect
redux แต่ให้สังเกตุนะครับ การเรียกใช้ props ไม่ตอ้ ง this.props นะครบั ใช้ props ไดเ้ ลย
93
WorkShop WebApplication ด้วย React : 94
6. สรา้ งไฟล์ Photo.js ไว้ท่ี /src/components/Photos โดยไฟลน์ ีผ้ มกจ็ ะทำ� เปน็ แบบ Stateless Func-
tional Components เสร็จแล้วกล็ องดทู ี่ browser ไดเ้ ลยครับ เปน็ อันจบ WorkShop นค้ี รบั “สดุ ยอด!!!”
import React from 'react'
const Photo = (props) => {
const { data } = props
return (
<div>
<article className="media">
<figure className="media-left">
<p className="image is-128x128">
<img src={data.thumbnailUrl} alt={data.title}/>
</p>
</figure>
<div className="media-content">
<div className="content">
{data.title}
</div>
</div>
</article>
</div>
)
}
export default Photo
ข้อสงั เกตโุ ค้ด
1. แทก็ article, figure ไมใ่ ช่ แท็กพเิ ศษอะไรของ react หรอกนะครบั มันเปน็ html ทัว่ ๆ ไป
2. แทก็ img จะตอ้ งใส่ alt ด้วยเสมอครบั ไมง่ ัน้ react มนั จะเตือนเรา (เราจะตเี นยี นไมใ่ สก่ ็ได้ แต่กใ็ สๆ่ ไปเถอะ
ครบั มนั จะได้ไม่
ส่ิงทีอ่ ยากให้ฝกึ พฒั นาตอ่
ผมอยากให้ผู้เรียนนำ� workshop นี้ไปพัฒนาตอ่ อีกเล็กนอ้ ยเปน็ การฝึกนะครับโดย
1. สร้าง breadcrumb เพ่อื ใชค้ ลิกกลบั ไปยัง Album และ User เนอื่ งจากตอนนี้ถา้ จะกลับไปหน้าจอดังกลา่ ว
ต้องคลกิ ปุม่ back ของ browser
2. ท�ำตัว Loading ของแตล่ ะหนา้ ครบั
ทงั้ 2 ข้อนี้ลองทำ� ดูนะครับแตไ่ มต่ ้องเครียดนะครบั
“ไมม่ ใี ครทำ� ส�ำเรจ็ ในคร้ังแรก แตผ่ ทู้ ีจ่ ะส�ำเร็จคือผทู้ ีไ่ มย่ อมแพ”้
ดตู วั อยา่ งผลงานในหนา้ ถดั ไปครับ
94
WorkShop WebApplication ดว้ ย React : 95
95
6 WorkShop WebApplication ดว้ ย React : 96
#WorkShop
เว็บ PhotoAlbum v. GraphQL
ส�ำคัญมาก!!
ผู้เรยี นต้องผา่ นการท�ำโปรเจค PhotoAlbum ไมง่ น้ั คุณจะ งง ครบั ยกเว้นคณุ มพี ้ืนฐาน react ท่ดี ีอย่แู ล้ว
และแกะโคด้ คนอ่นื ไดอ้ นั นี้กต็ ามสบายเลยครับ
ภาพรวมระบบ
WorkShop นี้ จะใช้ไฟลเ์ ดิมจาก WorkShop PhotoAlbum โดยที่สามารถ copy มาไดเ้ ลยครับ แตไ่ มต่ อ้ ง copy
node_modules มานะครับเราจะมา install ทีหลัง โดยความพิเศษของ WorkShop นค้ี ือเราจะมาใช้ GraphQL ในการ
จดั การขอ้ มูลกัน และใช้ Apollo ในการเชื่อมตอ่ โดยใช้ react-apollo เปน็ ตวั กลางท่ีทำ� ให้ react เช่ือมต่อ Apollo กับ Graph-
QL ได้
และส่ิงท่เี พ่ิมเขา้ มาอกี อย่างใน WorkShop นี้คอื เราจะต้องสรา้ ง Server สำ� หรับท�ำงานกับ GraphQL ครบั “ฟังดู
แล้วยากเนาะ แตผ่ มจะพาท�ำใหเ้ ป็นเรื่องง่ายครับ”
คัดลอกโปรเจคเดมิ
1. ก่อนอื่นใหเ้ ราสรา้ ง folder ชือ่ photoalbum_graphql ไวร้ อกอ่ นครับที่ folder reacttopro
2. copy สิง่ ทอี่ ยขู่ า้ งใน folder photoalbum แต่ไมต่ ้องเอา folder node_modules มาครับ
3. เปดิ terminal แลว้ cd เขา้ ไปที่ folder photoalbum_graphql (cd reacttopro แล้วกด enter, cd pho-
toalbum_graphql แลว้ กด enter)
4. พิมพ์ npm install แลว้ กด Enter (คำ� ส่ังน้ตี วั npm จะติดตั้ง module package ต่างๆ ทเี่ ราเคยตดิ ตั้ง โดยมัน
จะไปอา่ นจากไฟล์ package.json)
5. รนั โปรเจคไดเ้ ลยนะครับ npm start แลว้ ลองทดสอบดกู ็จะเหน็ วา่ ใชง้ านได้ปกติ (ถา้ ใช้งานไมไ่ ด้ ลอง npm
install ดูใหมอ่ ีกรอบนะครบั )
96
WorkShop WebApplication ด้วย React : 97
GraphQL
GraphQL คืออะไร
อธิบายแบบงา่ ยๆ GraphQL กค็ อื REST API อกี แบบหนงึ่ ทีท่ าง facebook คดิ ข้ึนมาครบั เพ่อื ประโยชน์หลักๆ 2
ขอ้ คือ (จรงิ ๆ มันก็มอี ะไรมากกวา่ นน้ั อยคู่ รับแต่ผมเอา 2 ข้อที่พอจะมองเห็นภาพเท่าน้นั เองครบั )
1. เพ่อื ความยืดหยุ่นในการรับ-สง่ ข้อมูล
2. เพอื่ ประหยัด Bandwidth ในการรบั -ส่งข้อมูล (ข้อน้ี App มอื ถือได้ประโยชนเ์ ตม็ ๆ)
อาจจะมองไมเ่ หน็ ภาพผมจะแสดงแผนภาพการท�ำงานของ REST API แบบเดิม กบั GraphQL ใหด้ นู ะครบั
จากแผนภาพการท�ำงานของการ GET ข้อมลู ระหว่า REST API (แบบปกตทิ ี่มนษุ โลกเขาใชก้ ัน) กบั GraphQL ที่
เจ้าพอ Social สรา้ งขนึ้ มา จะเห็นวา่
1. แบบ REST API ถ้าตอ้ งการขอ้ มลู post ที่มี comment ดว้ ย (กรองตาม post id) ตอ้ งท�ำการของ api ไป 2
ครัง้ แต่แบบ GraphQL ขอแค่ 1 ครงั้
2. แบบ REST API ขอ้ มูลทไ่ี ด้กลับมาจะไดจ้ ำ� นวนฟิลดต์ ามที่ REST API สร้างไว้ (มี 100 ฟลิ ด์ มนั กค็ อื นมาให้
100 ฟลิ ด์ แตจ่ รงิ ๆ เราอาจต้องการใช้แค่ 3 ฟลิ ด์ เปลอื ง data 4G มย้ั หละแบบนี)้ แต่ GraphQL ขน้ึ กับเราเลยครับว่า
ตอ้ งการไดก้ ีฟ่ ลิ ด์ “แมพ๊ ขิงๆ เลยแบบน”้ี
น่ีแค่ 2 ข้อนะครับ (คอื ผมคดิ ออกแคน่ แี่ หละ 555 ไมอ่ ยากเอา Eng มาแปลมันเขา้ ใจยากโพด) หลายคนจะรอ้ งเป็น
เสยี งเดียวกนั ว่า “โอว้ ซาล่า มนั ยอดมาก ใช้เลยๆ” คณุ รูม้ ้ัยครบั ว่า เทคโนโลยีมันยอ่ มแลกมาดว้ ยกำ� ลัง
คอื ไอ้เจา้ GraphQL นี้ไม่ใช่ว่าอยดู่ ๆี เรากเ็ รียกใช้งานฟิลด์ต่างๆ ได้นะครบั “กรรมกร Programmer” นีแ่ หละครับ
ตอ้ งเปน็ คนเตรียม Schema ต่างๆ ไวใ้ หม้ นั เรียกได้วา่ งานหนักอย่นู ะครบั “เย็ดเปด็ เลยทีเดยี ว” Learning Curve ก็สูง
แหลง่ ข้อมลู ก็ยังนอ้ ย ท่ีมอี ยกู่ ช็ วนงง
ดงั นั้นผมบอกไม่ได้หรอกครับว่ามนั คมุ้ ม้ัยท่ีจะใชแ้ ตบ่ อกไดอ้ ยา่ งเดียววา่ อนาคต GraphQL มันดีเลยทเี ดยี ว ยง่ิ ใคร
ศึกษาเพอ่ื ไปสมคั รงานบอกได้เลยครบั ว่า “เงนิ เดือนพงุ่ กระฉดู ” ว่าละก็ไปลองใช้ GraphQL กนั เถอะ
97
WorkShop WebApplication ดว้ ย React : 98
สรา้ ง Server สำ� หรบั GraphQL ดว้ ย Apollo + Express
ในการใชง้่ าน GraphQL เราจะต้องสร้าง node server ไว้คอยให้บริการ API นะครบั หรอื แมแ้ ต้ถา้ เราท�ำโปรเจคที่
ต้องตดิ ต่อกบั ฐานข้อมลู เราก็ตอ้ งสรา้ ง node server ไวค้ อยให้บรกิ าร API โดย server นจี้ ะติดต่อกบั ฐานขอ้ มลู อีกทหี นึ่ง
โดยผมจะใช้ express framework + apollo ซง่ึ เจ้าตัว apollo นีไ่ ม่ใชย้ านอวกาศนะครบั แตเ่ ป็น libray ที่จะท�ำให้
เราสร้าง Server เพือ่ ใช้งาน GraphQL ได้นะครบั หรอื ใช้ Relay ที่ facebook คิดข้นึ มาก็ได้ครบั แตม่ ันใช้ยาก คนทั่วๆ ไปอยา่ ง
เราๆ จึงนิยมใช้ Apollo มากกวา่ ครับ
1. สร้าง folder ใหม่ช่อื photoalbum_server ไว้ท่ี folder retacttopro
2. เปดิ terminal แลว้ cd เข้าไปที่ folder photoalbum_server
3. พมิ พ์ npm init -y แล้วกด enter (ค�ำสงั่ นจี้ ะสรา้ งไฟล์ package.json ให้ พรอ้ มกรอกรายละเอียดเล็กๆ น้อยๆ
ใหอ้ ัตโนมตั คิ รบั )
4. ตดิ ตง้ั package ต่างๆ ที่เราจำ� เปน็ ตอ้ งใช้สรา้ ง server ลงไปโดยพมิ พ์
npm i nodemon graphql express cors body-parser apollo-server-express -S
nodemon คอื ตัวนี้จะท�ำหนา้ ท่ี restart server เม่อื เรามกี ารแก้ไขโค้ดใหอ้ ตั โนมตั ิ
graphql คอื ส�ำหรบั ใชส้ ร้าง schema ของ graphql
express คอื framwork ส�ำหรับ nodejs ชว่ ยใหก้ ารสร้าง node server ไดง้ า่ ยขึ้น
cors คือ middleware สำ� หรบั express เพื่อชว่ ยแก้ปญั หาเรอ่ื ง Cross Domain Origin
body-parser คอื middleware สำ� หรับ express ทำ� ให้ express สามารถอ่านค่า body จาก url ได้ (ไม่งนั้
สง่ parameter อะไรมาใน url มันจะ error นะครบั )
apollo-server-express คือ middleware ของ express ท่ชี ว่ ยให้สามารถตดิ ต่อกับ graphql ไดค้ รบั ดรู าย
ละเอยี ดเพิ่มเติมได้ที่ https://www.apollographql.com/docs/apollo-server/setup.html (จะมโี คด้ ตวั อย่างสำ� หรบั ใช้
กบั express อยคู่ รับ)
98
5. WorkShop WebApplication ด้วย React : 99
server.js เปดิ folder โปรเจค photoalbum_server ด้วยโปรแกรม Visual Studio Code แล้วสรา้ งไฟลใ์ หม่ช่ือ
6. แก้ไขไฟล์ server.js โดยเพม่ิ โคด้ ด้านล่างน้คี รบั
const express = require('express'); A
const bodyParser = require('body-parser');
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express');
const cors = require('cors')
const myGraphQLSchema = require('./schema') B
const PORT = 3009;
var app = express();
app.use(cors()) C
app.use('/api/graphql', D
bodyParser.json(),
graphqlExpress({ schema: myGraphQLSchema })
);
app.use('/graphiql',
graphiqlExpress({ endpointURL: '/api/graphql' })
);
app.listen(PORT, () => {
console.log('ready on http://localhost:' + PORT + '/graphql')
});
อธบิ ายโคด้
A. require package ต่างๆ ที่เราพึ่งติดต้ังเขา้ มาใช้งานครบั
B. ตรงนี้เป็นการเรยี กใช้ไฟล์ schema หรอื เรยี กง่ายๆ วา่ โครงสร้างของ graphql ที่เราตอ้ งการ ซ่งึ เราจะตอ้ ง
สรา้ งขนึ้ มาเองนะครบั เจา้ ตวั โครงสร้างนี้ เด๋ยี วผจะพาท�ำครบั ตอนนี้ require มนั เขา้ มาไว้กอ่ น
C. ตรงน้ีก็เป็นการเรียกใช้ middelware ตา่ งๆ ผ่าน express ทเี่ ราตอ้ งการ โดย
1. ก�ำหนด enpoint ไปแค่ url เดยี ว คือ /api/graphql (end point นเี้ วลา web อ่นื จะเขา้ ถึงหรือตัว
react จะเขา้ ถึงกจ็ ะสง่ url ประมาณวา่ http://localhost:3009/api/graphql) จากน้ันยากไดฟ้ ลิ อะไรมาทำ� งานกค็ ่อยสง่
query มาเป็น body
99
WorkShop WebApplication ดว้ ย React : 100
2. เมอื่ ส่ง url ที่มี query มากจ็ ะทำ� ให้ตวั bodyParser.json() ท�ำงานเพ่อื แปลให้ express ถอด query
พวกน้นั ได้ (ผมใช้การอธิบายแบบบา้ นๆ นะครบั ถา้ วชิ าการมันจะ งง ใครชอบวชิ าการหาดูได้จากเวบ็ ทัว่ ๆ ไปนะจ๊ะ)
3. จากนนั้ ตัว graphqlExpress กจ็ ะเปรียบเทยี บ query กบั schema ท่เี รามีว่าเข้าเงอ่ื นไขไหนครับ ตรงนี้
อาจจะยงั ไม่เข้าใจต้องไดล้ องเขียน schema กอ่ นนะครับ
D. graphiqlExpress (มตี วั i เขา้ มานะครับอา่ นว่า กราฟฟิคิวแอลเอ็กแพรส) ตวั น้จี ะทำ� ใหเ้ วลาเราเข้า url http://
localhost:3009/graphiql มนั แสดงหน้าจอการทดสอบการ query แบบ GUI ครบั โดยเราตอ้ งกำ� หนด enpointURL ใหต้ รง
กับ enpoint ของ graphl ข้างบน (C) นะครับ
7. สร้างไฟล์ schema.js แลว้ กรอกโคด้ ด้านลา่ งลงไปครับ (แบบ Basic นะครับจะไดไ้ ม่งง)
const graphql = require('graphql') A
const { C
GraphQLList, B
GraphQLObjectType, D
GraphQLSchema,
GraphQLString,
} = graphql
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
user: {
type: GraphQLString,
resolve: () => {
return "Hello GraphQL"
}
}
})
})
const schema = new GraphQLSchema({
query: QueryType,
})
module.exports = schema
อธบิ ายโค้ด
A. reuire graphql และเรียกใชง้ าน type ต่างๆ ของ graphql (type ของ graphql จะมเี ยอะกว่านีน้ ะครับใคร
สนใจก็ลองหา doc ของ graphql มาดูได้ครับ)
B. สรา้ ง schema โดยสง่ option ให้ GraphQLSchema วา่ ใช้ query และเรยี กใช้ QueryType ทเ่ี ราจะสรา้ งใน
ขอ้ C
C. สร้าง QueryType แบบ GraphQLObjectType ขา้ งในจะดู งงๆ หน่อยนะครบั โดยข้างในระบ fileds ว่ามี
user และก�ำหนดประเภท user ว่าเปน็ string (GraphQLString) จากนั้น ส่งั resolve โดย return ข้อความ Hello
GraphQL ออกไปครบั (โคตรง งง เลยใช่มัย้ ครับ แต่มันก็เปน็ โครงสรา้ งของ GraphQL ครบั ต้องท�ำบอ่ ยๆ จะเข้าใจไปเองครบั )
D. สุดท้ายก็ export schema ออกไปให้ไฟลอ์ นื่ ได้ require ได้ครับ
100