ผลต่างระหว่างรุ่นของ "01204223/react-flask"
Jittat (คุย | มีส่วนร่วม) |
Jittat (คุย | มีส่วนร่วม) |
||
| แถว 512: | แถว 512: | ||
try { | try { | ||
const response = await fetch(TODOLIST_API_URL); | const response = await fetch(TODOLIST_API_URL); | ||
| + | if (!response.ok) { | ||
| + | throw new Error('Network error'); | ||
| + | } | ||
const data = await response.json(); | const data = await response.json(); | ||
setTodoList(data); | setTodoList(data); | ||
รุ่นแก้ไขเมื่อ 08:30, 8 มกราคม 2569
- หน้านี้เป็นส่วนหนึ่งของวิชา 01204223
เนื้อหา
เริ่มต้นและการติดตั้งซอฟต์แวร์ต่าง ๆ
node.js และ npm
เนื่องจาก react เป็น JavaScript framework เราจำเป็นต้องติดตั้งซอฟต์แวร์ที่เกี่ยวข้องเสียก่อน การใช้ JavaScript บนเครื่องเราเองนั้น เราต้องมีตัว runtime ที่ใช้ทำงานกับโปรแกรมภาษา JavaScript &nbps; ในทางฝั่ง server Node.js น่าจะเป็นระบบที่มีคนใช้มากที่สุด ที่มาพร้อมกับเครื่องมือประกอบต่าง ๆ มากมาย (มากมายจริง ๆ)
ในการจะใช้เครื่องมือบน node.js ได้นั้น เราจะต้องติดตั้งโปรแกรม npm (Node Package Manager) ด้วย ดังนั้น ในขั้นแรกเราจะติดตั้งทั้งสองอย่างไปด้วยกัน
การติดตั้งบน MacOS และ Ubuntu:
การติดตั้งบน Windows
เมื่อติดตั้งแล้ว ให้ลองเรียกทั้งสองคำสั่งด้านล่างใน terminal
node -v npm -v
ถ้ามีเลขเวอร์ชันขึ้นมาก็น่าจะเรียบร้อย
การสร้างโครง React project
ในการพัฒนาด้วย React เราจะเขียนโค้ดที่ไม่ได้ถูกนำไปรันบน browser โดยตรง เพราะว่าจะต้องมีการ preprocess อะไรก่อนมากมาย ซึ่งขั้นตอนเหล่านี้ถ้าเราต้อง setup เองทั้งหมดจะยุ่งยากมาก แต่เนื่องจาก environment ในการพัฒนาด้วย JavaScript นั้นมีเครื่องมือมากมาย เราจึงสามารถพึ่งพาระบบเหล่านั้นได้เลย
เราจะสร้างโครงโปรเจ็คด้วย vite โดยสั่ง (ให้เปิด terminal มาเรียกใช้งานเลย เพราะว่าจะมีคำสั่งที่ต้องเรียกค้างไว้ระหว่างพัฒนา)
npm create vite@latest first-react-app -- --template react
จะมีคำถามให้ตอบหลายคำถาม ถ้ามีการถามว่า
Install with npm and start now?
ให้เลือก No ไว้ก่อน เพราะว่าเราจะได้กดอะไรต่าง ๆ เอง เมื่อ npm ทำงานเสร็จ จะมีคำสั่งบอกให้เราเรียกเพื่อเริ่มติดตั้งไลบรารีและเริ่มเรียก dev server ดังด้านล่าง
cd first-react-app npm install npm run dev
ให้ทำตามรายการคำสั่งดังกล่าว
ึึึคำสั่ง npm install จะติดตั้งไลบรารีที่เกี่ยวข้องลงในไดเร็กทอรี node_modules (ซึ่งจะใหญ่ขึ้นเรื่อยๆ อาจจะทำดิสก์เต็มได้) เมื่อติดตั้งเสร็จจะมีข้อความประมาณนี้
added 156 packages, and audited 157 packages in 33s
ส่วนคำสั่ง
npm run dev
จะเป็นการเรียก development web server ให้ทำงาน ให้เรียกแล้วเปิด terminal ทิ้งไว้ เมื่อเรียกเสร็จโปรแกรมจะแสดง URL ให้เราเข้าดูหน้าเว็บ (น่าจะเป็น http://localhost:5173/ ) นอกจากนี้ ถ้าเราแก้ไขอะไรในโปรเจ็ค vite จะจัดการ build โค้ดทั้งหมดให้รวมทั้ง reload หน้าเว็บของเราใน browser ให้โดยอัตโนมัติ
ถ้าไม่มีข้อผิดพลาดอะไร ใน browser จะแสดงโลโก้ Vite และ React
- อ่านเพิ่มเติมเกี่ยวกับการทำงานของ vite
ทดลอง React เบื้องต้น
ก่อนที่เราจะทดลองพัฒนาแอพลิเคชัน เราจะศึกษาพื้นฐานการทำงานของ React ผ่านทางเว็บง่าย ๆ ก่อน ในไดเร็กทอรี first-react-app ที่เราสร้าง จะมีไฟล์
index.html
ซึ่งจะเป็นหน้าเว็บหลักของเรา ถ้ากดเขาไปดูจะพบหน้าเว็บที่เกือบ ๆ จะว่าง แต่มีส่วนโหลด /src/main.jsx อยู่
<!doctype html>
<html lang="en">
<!-- ## ละไว้บางส่วน ## -->
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
ส่วนที่เราจะเขียนโปรแกรมกันจริงๆ จะอยู่ในไดเร็กทอรี src อยู่ ในนั้นจะมีไฟล์เหล่านี้
- App.jsx // โค้ดของ component App (เราจะเขียนกันที่ไฟล์นี้ก่อน) - main.jsx // สร้าง component App และนำไปใส่ใน element id root - App.css // จัดการกับ style สำหรับ component App - index.css // style ของแอพโดยรวม
main.jsx
ให้ลองกดไปดูโค้ดใน main.jsx สักเล็กน้อย (เราจะไม่ได้แก้อะไร)
// ----- ไฟล์ main.jsx ------ (เพื่อกันความสับสนบางทีจะมีแจ้งชื่อไฟล์ไว้ด้านบน)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
โค้ดดังกล่าวเป็นไฟล์ jsx ซึ่งเป็นไฟล์ที่มีการผสมกันระหว่าง JavaScript กับ template สังเกตว่าเราจะเขียน html tag ผสมกับโค้ดได้เลย ในส่วนนั้นจะมีการทำงานไม่ต่างกับ Jinja teamplate ที่เราเขียนใน Flask มาแล้ว
App.jsx
เราจะทำงานกับไฟล์นี้เป็นส่วนใหญ่ โค้ดปัจจุบันมีการทำงานหลายอย่างอยู่แล้ว แต่เราจะเริ่มทำใหม่กันตั้งแต่แรก
เราจะสร้าง component App ที่แทนแอพลิเคชันของเรา โดยในการเขียน เราจะประกาศเป็น function App ให้แก้ function ดังกล่าวในโค้ดให้เป็นดังด้านล่างก่อน
function App() {
let count = 5;
return (
<>
<button>-1</button>
{count}
<button>+1</button>
<br />
Bar: {count > 0 ? "*".repeat(count) : "(empty)"}
<br />
{count > 10 ? (
<>
high
<button>reset</button>
</>
) : (
<>low</>
)}
</>
)
}เมื่อ save แล้วจะเห็นผลลัพธ์เป็นดังนี้
ให้ลองแก้ค่าเริ่มต้นของ count เป็นค่าต่าง ๆ แล้วลองดูผลลัพธ์ เช่น เป็น 0, 10, 20 เป็นต้น ให้ลองเปลี่ยนค่าจนเห็นปุ่ม reset
ข้อสังเกตจากโค้ดด้านบน
- การใช้ <> </> (fragment) ในการประกาศส่วนที่เป็น html
- ในการเขียน tag br ถ้าเป็น html ธรรมดา เราสามารถเขียน <br> ได้เลย แต่ใน jsx เราต้องมีการเว้นช่องว่างและระบุ / เพื่อปิด tag ด้วย (ต้องเขียนเป็น <br />)
- การใช้ฟังก์ชัน
repeat - การใช้ conditional expression (เงื่อนไข ? ค่าเมื่อจริง : ค่าเมื่อเท็จ) ในการเขียน
เราสามารถใส่ JavaScript expression ได้โดยใส่ไว้ในเครื่องหมายปีกกา { } และในนั้นถ้าเราต้องการใส่ html เราต้องเปิดปิดด้วย <> </> ก่อน (สังเกตในส่วนเงื่อนไข low, high)
ถ้าสังเกต เราจะเห็นว่าใน ui ของเรามีปุ่ม -1 และ +1 อยู่ (รวมถึง reset) น่าจะเดาได้ว่าปุ่มทั้งสองจะทำอะไร แต่ตอนนี้กดไปก็ยังไม่มีอะไรเกิดขึ้น เพราะว่าเรายังไมไ่ด้เขียนโค้ดอะไรไปติดกับมัน
ทิศทางการ render และ state
ก่อนจะเขียน React ต่อไป ให้ลองคิดว่าถ้าเราต้องเขียนโปรแกรมเพื่อจัดการกับค่า count หลังการกดปุ่มทั้งสอง เราต้องทำอะไรบ้าง
เมื่อมีการกดปุ่ม เราน่าจะต้องดำเนินการดังนี้
- แก้ค่า count
- ปรับการแสดงผล count
- คำนวณสตริงแสดงดาวให้เป็นไปตาม count
- ตรวจสอบเงื่อนไขในการแสดงว่า high หรือ low พร้อมกับแสดงหรือซ่อนปุ่ม reset
ตัวอย่างโค้ดที่เราเขียนนี้ เป็นตัวอย่างของระบบ ui ที่มีความซับซ้อนไม่มากนัก แต่เราก็อาจจะพอนึกได้ว่า ถ้ามีการเปลี่ยนสถานะบางอย่าง จะส่งผลกระทบต่อหน้าจอหลายส่วน ซึ่งผลกระทบเหล่านี้ถ้าต้องจัดการหลาย ๆ สถานะพร้อมกันจะซับซ้อนมาก
แนวคิดหลักที่ทำให้ React นั้นเป็นที่นิยมคือการเปลี่ยนมุมมองการเขียนโค้ดจากการคิดว่าถ้ามีสถานะอะไรเปลี่ยน เราจะต้องปรับอะไรบ้าง และอะไรจะกระทบกันบ้างให้เป็น
- เราไม่ต้องสนใจว่าจะต้องแก้ไขอะไรบ้าง แต่ให้พิจารณาการ render หน้าจอใหม่ทั้งหมดเลย
แต่ถ้าทำตามวิธีดังกล่าวตรง ๆ หน้าเว็บเราคงจะกระพริบไปกระพริบมา ไม่ต่างจากการกด refresh ทุกครั้ง สิ่งที่ทำให้แนวคิดดังกล่าวนำมาใช้ในการพัฒนา ui ได้จริงคือการ render ลงไปที่ virtual DOM ก่อนที่จะปรับเปลี่ยนหน้าจอจริง ๆ เฉพาะในส่วนที่มีความแตกต่าง ทำให้การแก้ไขไม่ได้กระทบหน้าจอทั้งหมด
รูปด้านล่างเปรียบเทียบวิธีการจัดการกับ interaction สองแบบ
[รูป]
ด้านล่างแสดงตัวอย่างของการ update หน้าจอเป็นส่วนๆ
[รูป]
ไอเดียที่อยากทำ
เราอยากจะให้ปรับค่าตัวแปร count ถ้ามีการกดปุ่ม เราอาจจะแก้ส่วน button -1 โดยเพิ่มตัวจัดการ event onClick ลงไป ให้เป็นดังนี้
<button onClick={() => {count--;}}>-1</button>
หมายเหตุ: สังเกตการเขียน function ด้วย arrow
ให้ทดลองแก้แล้วทดลองดูผล
สังเกตว่าเมื่อกดปุ่ม เราก็ยังไม่เห็นอะไร
เป็นไปได้ว่าไม่มีการทำงาน หรือไม่มีการปรับค่า เราจะแก้โค้ดใหม่ ให้มีการ alert ค่า count มาดูด้วย ดังด้านล่าง
<button onClick={() => {count--; alert(count)}}>-1</button>
สังเกตว่าค่าตัวแปร count นั้นลดลงจริง แต่ไม่มีผลอะไรกับการแสดงบนหน้าจอเลย ก่อนจะอ่านต่อไป ให้คิดว่าเป็นเพราะอะไร
คำตอบก็คือ การที่เราแก้ค่าตัวแปรนั้น ไม่มีผลต่อการกลับไป render App ใหม่ เพราะว่า React ไม่ทราบว่ามีการเปลี่ยนสถานะแล้ว
React hook useState
ใน React เวอร์ชันใหม่ (ไม่มากนัก) มีการเพิ่ม hook ที่เป็นฟังก์ชันพิเศษที่ทำให้เราสามารถ "จิ้ม" เข้าไปในการทำงานของ React เพื่อเพิ่มการจัดการเกี่ยวกับ state และ side effect ได้
เราจะเพิ่ม state ให้กับ component โดยการเรียก react hook ที่ชื่อว่า useState โดยเราจะแก้ส่วนหัวของ function App ให้เป็นดังนี้
// ของเดิม: let count = 5; const [count, setCount] = useState(0);
บรรทัดดังกล่าวประกาศว่าใน component App จะมี state count ที่มีค่าเริ่มต้นเป็น 0 และมีฟังก์ชัน setCount ที่เราสามารถเรียกเพื่อเปลี่ยนค่าได้
สังเกตว่าทั้ง count และ setCount จะเป็น constant
จากนั้นเราจะแก้ onClick ของปุ่ม -1 ให้เป็นดังนี้
<button onClick={() => {setCount(count - 1)}}>-1</button>
กล่าวคือ ถ้ามีการกดปุ่ม -1 ให้ปรับค่า state count (ผ่านทางฟังก์ชัน setCount) ให้เป็น count -1
ให้ลองแก้, จัดเก็บ, และทดลองกดปุ่ม
งานของคุณ: ให้แก้ปุ่ม +1 และ reset ให้ทำงานตามที่เราต้องการ โดยเพิ่ม onClick และเรียก setCount ให้เหมาะสม
Todo list แบบไม่มี server
แสดงรายการเริ่มต้น (ยังไม่มี state)
ให้แก้ component App ให้เป็นดังด้านล่าง
function App() {
let todoList = [
{ id: 1,
title: 'Learn React',
done: true },
{ id: 2,
title: 'Build a React App',
done: false },
];
return (
<>
<h1>Todo List</h1>
<ul>
{todoList.map(todo => (
<li key={todo.id}>
<span className={todo.done ? "done" : ""}>{todo.title}</span>
</li>
))}
</ul>
</>
)
}สังเกต:
- ให้สังเกตการใช้ map กับ todoList เราส่ง arrow function ที่รับ todo เข้าไปใน map เพื่อเรียกใช้ฟังก์ชันดังกล่าวกับทุก ๆ todo ในรายการ
- เราจะมี prop key ที่ระบุให้กับ element li สำหรับให้ React ตรวจสอบว่ามีการเพิ่มหรือลบขอในรายการหรือไม่ (โดยใช้ key เป็นเหมือนชื่อเฉพาะที่ใช้เรียกข้อมูลนี้
นอกจากนี้ให้เพิ่มกฎ .done ลงใน App.css ดังด้านล่างด้วย เพื่อแสดงรายการที่มีขีด
.done {
text-decoration: line-through;
}
ลองเซฟและดูผล ควรจะเห็นหน้าจอเป็นดังนี้
ใช้ useState และเพิ่มปุ่ม toggle
เราจะเก็บ todoList ใน state ดังนั้นให้ปรับส่วนประกาศให้เป็นดังด้านล่างนี้
const initialTodoList = [ // เปลี่ยนชื่อ
{ id: 1,
title: 'Learn React',
done: true },
{ id: 2,
title: 'Build a React App',
done: false },
];
const [todoList, setTodoList] = useState(initialTodoList);
เราจะเพิ่มปุ่ม toggle เอาไว้เพื่อปรับสถานะการทำเสร็จ ให้แก้ในส่วน jsx template ที่แสดงรายการให้เป็นดังนี้
{todoList.map(todo => (
<li key={todo.id}>
<span className={todo.done ? "done" : ""}>{todo.title}</span>
<button>toggle</button>
</li>
))}
เราจะเขียนตัวจัดการ event onClick ของปุ่ม toggle โดยเราจะสร้างฟังก์ชัน toggleDone และเรียกใช้ ให้เพิ่ม onClick เข้าไปดังนี้
<button onClick={() => {toggleDone(todo.id)}}>toggle</button>
เราจะเขียนฟังก์ชัน toggleDone ไว้ในฟังก์ชัน App ก่อนถึงคำสั่ง return
function App() {
const initialTodoList = [
// ละไว้
];
const [todoList, setTodoList] = useState(initialTodoList);
function toggleDone(id) {
// ** เราจะเขียนตรงนี้ **
}
return (
// ละไว้
)
}ฟังก์ชันดังกล่าว ต้องรับผิดชอบในการปรับ state ของ App โดยการเรียก setTodoList สังเกตว่าในการทำงานดังกล่าว เราจะต้องสร้างรายการใหม่ ทั้งรายการ ไม่ใช่แค่ปรับค่า todo แต่ข้อมูลเดียว เพราะว่าฟังก์ชัน setTodoList ทำงานกับทั้งรายการ
ดังนั้นโครงของ toggleDone จะเป็นดังนี้
function toggleDone(id) {
let newTodoList = ...
// ...
// ...
setTodoList(newTodoList);
}งานของคุณ ให้ทดลองเขียนเองก่อน แล้วค่อยดูเฉลยที่ใช้ map ด้านล่าง
เฉลย เราสามารถใช้ map ในการไล่ปรับค่าได้ สังเกตว่าใน arrow function ที่ส่งให้ map นั้น เราจะตรวจสอบ object todo ว่า id ตรงหรือไม่ ถ้าไม่ตรงก็คืนค่าเดิม แต่ถ้าตรง เราจะคือ object ใหม่ที่คัดลอกของจาก todo เดิม ก่อนจะปรับแค่ done ให้มีค่าตรงกันข้าม
function toggleDone(id) {
let newTodoList = todoList.map(todo => {
if (todo.id === id) {
return { ...todo, done: !todo.done };
}
return todo;
});
setTodoList(newTodoList);
}ให้สังเกตการใช้ rest property ... (ตรง ...todo) ในการคัดลอกคุณสมบัติที่เหลือทั้งหมดของ todo ที่เราไม่ได้กำหนด
งานของคุณ ให้แก้ปุ่ม toggle ให้แสดงเป็นข้อความว่า Done ถ้าการกดทำให้ done และแสดงว่า Reset ถ้ากดแล้วกลับมา done เป็น false แทนที่จะแสดงว่า toggle อย่างเดียว
Todo list: add new item
ความสะดวกของการใช้ useState ก็คือเราสามารถสร้าง state เพื่อมาติดตามการเปลี่ยนแปลงค่าของ input ในหน้าจอได้โดยสะดวก
ให้เพิ่ม state newTitle ใน App
const [newTitle, setNewTitle] = useState("");
จากนั้นให้เพิ่ม input สำหรับการเพิ่ม todo ในรายการ
return (
<>
<h1>Todo List</h1>
<ul>
... ละไว้ ...
</ul>
New: <input type="text" value={newTitle} onChange={(e) => {setNewTitle(e.target.value)}} />
<button>Add</button>
</>
)สังเกตว่าเรากำหนด event onChange ให้ไปกำหนดค่าใหม่ให้กับ state newTitle สังเกตว่า function จัดการ onChange มีการอ้างถึง e ที่เป็นข้อมูลที่มากับ event และอ่านค่า value ของ element ดังกล่าว (ซึ่งก็คือ input ที่เราติดตามอยู่นี่เอง)
เราสามารถทดลองพิมพ์ค่า state ดังกล่าวออกมาได้ โดยเพิ่ม onClick ที่ button ดังนี้
<button onClick={() => {alert(newTitle)}}>Add</button>ให้ทดลองพิมพ์ข้อความและกดปุ่ม จากนั้นแก้ข้อความแล้วกดปุ่มอีกครั้ง
เราจะเขียนฟังก์ชัน addNewTodo เพื่อเพิ่ม todo ลงในรายการ
ก่อนอื่นให้ปรับให้ event onClick ที่ปุ่ม Add ไปเรียกฟังก์ชันนี้ก่อน
<button onClick={() => {addNewTodo()}}>Add</button>และเพิ่มฟังก์ชันทั้งสองลงด้านในของ function App
function newId() {
if(todoList.length == 0) {
return 1;
}
let maxId = todoList[0].id;
todoList.forEach(todo => {
if (todo.id > maxId) {
maxId = todo.id;
}
});
return maxId + 1;
}
function addNewTodo() {
let title = newTitle;
let newTodo = {
id: newId(),
title: title,
done: false,
};
setTodoList([...todoList, newTodo]);
setNewTitle("");
}
หมายเหตุ ฟังก์ชัน newId หาค่า id มากสุด แล้วคืนค่าดังกล่าว +1 เราสามารถใช้ฟังก์ชัน Math.max (ที่รับรายการมาใน argument หลายตัว) ร่วมกับ spread operator ทำให้เขียนสั้นลงได้เป็นดังนี้
function newId() {
if(todoList.length == 0) {
return 1;
}
return 1 + Math.max(...(todoList.map(todo => todo.id)));
}
Todo list: delete todo item
เราจะเพิ่มปุ่ม delete เพื่อลบของออกจากรายการ ให้เพิ่มปุ่มสำหรับทุก ๆ todo
<button onClick={() => {deleteTodo(todo.id)}}>❌</button>
งานของคุณ เขียนฟังก์ชัน deleteTodo
คำแนะนำ: ให้ลองใช้ filter ในการเลือกข้อมูลในรายการ todoList ที่ต้องการเก็บไว้ (ใช้คล้ายๆ map)
การใช้ Flask เป็น backend: load รายการ todo list
เราจะสร้าง project Flask เพื่อทำเป็น backend ให้หาไดเร็กทอรีใหม่ และสร้าง virtual environment รวมทั้งติดตั้งไลบรารีที่ต้องใช้ในการพัฒนา Flask อ่านทบทวนได้ที่เอกสารตอนที่ทำ Flask
สิ่งที่แตกต่างไปก็คือ ตอนนี้หน้าเว็บที่เราทำจะเป็น API ที่ React จะมาเรียกใช้อีกที ดังนั้นสิ่งที่ function ที่จัดการกับแต่ละ URL คืนกลับไปจะเป็นข้อมูลประเภท json
ด้านล่างเป็นโค้ด main.py ที่รับ request ที่ url /api/todos/ และจะคืนรายการเป็น json สังเกตว่าเราจะให้รายการใน todo list แตกต่างจากใน React เพราะว่าถ้าโหลดรายการได้ จะได้เห็นความแตกต่าง
from flask import Flask, request, jsonify
app = Flask(__name__)
todo_list = [
{ "id": 1,
"title": 'Learn Flask',
"done": True },
{ "id": 2,
"title": 'Build a Flask App',
"done": False },
]
@app.route('/api/todos/')
def get_todos():
return jsonify(todo_list)
หมายเหตุ: สังเกตการใช้งานฟังก์ชัน jsonify
ให้เรียกให้ web development server ทำงาน อย่าลืม export FLASK_APP=main.py แล้วเรียก
flask run --debug
จากนั้นให้ลองเข้าเว็บที่ http://localhost:5000/api/todos/ จะเห็นข้อมูลแบบ json ที่คืนกลับมา
ตอนนี้เราจะมี terminal ที่เรียก development server ทำงานอยู่สองโปรแกรม อันหนึ่งของ React (frontend) อีกอันเป็นของ Flask (backend) อย่าเพิ่งสับสน
ใช้ useEffect ในการเรียก api เพื่อโหลดค่าเริ่มต้นของ todoList
ให้แก้ส่วน import ตอนต้น App.jsx ให้ import useEffect มาด้วย
import { useState, useEffect } from 'react'
จากนั้นเพิ่มค่าคงที่แทน URL ของ api endpoint และเขียนโค้ดให้โหลดข้อมูล จากนั้นนำมากำหนดค่าให้กับ todoList
function App() {
const TODOLIST_API_URL = 'http://localhost:5000/api/todos/';
// .. ละไว้
// ประกาศหลังประกาศ setTodoList ใน useState
useEffect(() => {
fetchTodoList();
}, []);
async function fetchTodoList() {
try {
const response = await fetch(TODOLIST_API_URL);
if (!response.ok) {
throw new Error('Network error');
}
const data = await response.json();
setTodoList(data);
} catch (err) {
alert("Failed to fetch todo list from backend. Make sure the backend is running.");
}
}
// ..
}
เมื่อเรียกใช้งาน ในหน้าจอ server ของ Flask จะเห็นว่ามีการเรียกมายัง api แต่หน้าเว็บของ React จะยังไม่มีการเปลี่ยนแปลง
ถ้าไปกด Inspection และดูที่หน้า console จะพบว่าการเรียกดังกล่าวถูก block ด้วย CORS policy เพราะว่า frontend เราทำงานที่ http://localhost:5173/ ส่วน backend เราอยู่ที่ http://localhost:5000/api/todo/ ซึ่งถือว่าเป็นคนละที่กัน
ถ้าจะทำให้สามารถเรียกได้ ใน Flask เราจะต้องเพิ่มโค้ดเพื่อเปิดการอนุญาตนี้ด้วย
CORS
เราต้องติดตั้งไลบรารี Flask-CORS เสียก่อน ให้เบรคออกมาจาก flask run แล้วเรียก
pip install flask-cors
เพื่อติดตั้งไลบรารี
จากนั้นให้เพิ่มบรรทัดที่เรียก CORS เพิ่มไปในไฟล์ main.py (บรรทัดที่เพิ่มจะมี remark ด้านหลัง)
from flask import Flask, request, jsonify
from flask_cors import CORS # เพิ่ม import
app = Flask(__name__)
CORS(app) # เพิ่มการอนุญาต
# ... ละไว้
ถ้าแก้แล้ว ให้ start flask run ใหม่ แล้วลอง refresh เว็บ React front end น่าจะเห็นว่ามีรายการใหม่ เป็น Learn Flask แทนที่ Learn React
ทำความเข้าใจกับการเรียก fetch (async / await)
เราจะกลับมาดูการเรียก api จาก React ผ่านทางฟังก์ชัน fetch (Fetch API) ตามโค้ดด้านล่าง
async function fetchTodoList() {
try {
const response = await fetch(TODOLIST_API_URL);
const data = await response.json();
setTodoList(data);
} catch (err) {
alert("Failed to fetch todo list from backend. Make sure the backend is running.");
}
}


