สวัสดีครับ ในบล็อกนี้เราจะมาลองเล่น Remix web framework บน codesandbox.io กัน
สำหรับเพื่อนๆ ที่ไม่เคยใช้งานเว็บ codesandbox.io มันเป็นเว็บสำหรับทดสอบเขียนโปรแกรมเว็บ เช่น React, Javascript, Vue, Svelte , Angular เป็นต้น เราสามารถทดสอบเขียนโปรแกรมในเว็บ codesandbox.io ได้เลยโดยที่ไม่ต้องติดตั้งโปรแกรมเริ่มต้นในเครื่องเราเอง หรือไม่ต้องติดตั้งเฟรมเวิร์คอะไรให้ยุ่งยาก
เราจะดูกันคร่าวๆ ว่าโครงสร้าง Project และ การใช้งานด้าน ServerSide ของ Remix นั้นทำงานยังไงและเราจะต้องใช้งานมันแบบไหน ก่อนอื่นเรามาทำความรู้จักกับ Remix กันก่อน
Remix หรือ Remix.run เป็น web framework (เป็นคนละตัวกัน remix.ethereum.org ที่เป็น IDE นะครับ) ซึ่งสามารถทำงานได้ทั้งฝั่งเซิฟเวอร์และไคลเอนท์ ในโปรเจคเดียวกันได้ และการทำ Server Side Rendering (SSR) ได้ลักษณะเดียวกันกับ Next.js และ Blitz (อ่านจากบล็อกด้านล่างได้เลย)
ทีนี้เราจะไปสร้าง Remix บนเว็บ codesandbox.io กัน เมื่อ log in เข้าหน้าเว็บ codesandbox.io แล้วกดเลือก Create Sandbox > Remix-Starter
เมื่อสร้างโปรเจค remix-starter เสร็จเรียบร้อยแล้ว เราจะมาเริ่มดูกันที่ส่วนประกอบของตัว Editor ของ codesandbox โครงสร้างโปรเจคของ remix-starter และ dependencies ที่ติดตั้งมาในโปรเจค remix-starter นี้กันครับ
ส่วนหลักๆ ของ Editor ที่ระบุหมายเลขไว้ 1 ถึง 3 เราจะมาดูกันก่อน
หมายเลข 1 เป็นส่วนโครงสร้างโปรเจค ซึ่งจะอยู่บริเวณซ้ายมือของ Editor โปรเจคของ Remix นั้นจะไม่เหมือนกับโปรเจคที่สร้างจาก Create react app ซึ่ง ใน remix จะมีโฟลเดอร์ app เพิ่มเข้ามา ในโฟลเดอร์ app นั้นจะประกอบไปด้วย
- โฟลเดอร์ routes เป็นโฟลเดอร์เก็บไฟล์ของแต่ละหน้า หรือแต่ละพาท
- ไฟล์ entry.client.jsx เป็นไฟล์สำหรับคำสั่งแสดงผลฝั่ง client ของ remix
- ไฟล์ entry.server.jsx เป็นไฟล์สำหรับจัดการคำสั่งฝั่ง server ของ remix
- ไฟล์ root เป็นไฟล์ที่รวม Component ต่างๆ สำหรับเริ่มต้นของโปรเจค โดย Component เริ่มต้นคือ Component App เราสามารถกำหนด Layout ของแอพได้ที่นี่และกำหนด Outlet สำหรับการแสดงผลของ Children ได้ จะสามารถแสดงข้อผิดพลาดของแอพได้ผ่านฟังก์ชัน CatchBoundary และ ErrorBoundary
- โฟลเดอร์ styles เป็นโฟลเดอร์ไว้เก็บไฟล์ css หรือค่า styles ของ Component
หมายเลข 2 เป็นส่วนแสดง Dependency ที่โปรเจคได้มีการติดตั้งเอาไว้แล้ว ในโปรเจคเริ่มต้นของ remix ได้มีการติดตั้ง @remix-run/react @remix-run/server remix react react-dom react-router-dom ไว้ให้พร้อมแล้ว
เราสามารถเพิ่ม dependency ใหม่ได้โดย พิมพ์ชื่อ Dependency ที่ต้องการใส่ในช่อง Add Dependency จากนั้นก็เลือก Dependency ที่ต้องการจะติดตั้งเข้าไปใหม่
หมายเลข 3 ส่วนของการแสดงผลบนเบราเซอร์จำลอง ในบางครั้งโปรแกรมจะไม่ auto reload ให้ทันใจ เราสามารถกด refresh ได้เลย
และส่วนอื่นๆ ที่ไม่ได้กล่าวถึงได้แก่ ส่วนการเขียนโปรแกรมจะอยู่ตรงกลาง และด้านล่างส่วนแสดงผลบนเบราเซอร์จำลองจะเป็นส่วนแสดงผล console
สิ่งที่ต้องรู้เบื้องต้นในการใช้ Remix
- Remix สามารถเขียนโปรแกรมให้แอปของเราแสดงผลแบบ Server Side Rendering (SSR) ได้
- ฟังก์ชันที่ใช้ในการโหลดค่าในฝั่ง server คือ function loader
- ฟังก์ชันที่ใช้ในการรับค่าการ submit form ไปยังฝั่ง server คือ function action
- Outlet เป็น Component ที่จะแสดงผล Component ที่เป็น Children ของ Component หลัก โดยดูจากพาทในโฟลเดอร์ routes เป็นหลัก
- remix จะทำ route ให้อัตโนมัติโดยเราจะต้องสร้างไฟล์ Component ที่เป็นหน้า (page) ไว้ที่โฟลเดอร์ routes และยังสามารถสร้างโฟลเดอร์ย่อยที่ชื่อเดียวกับไฟล์ใน routes เพื่อบอกว่าจะทำการรับค่าจากพาทนั้น เช่น /movies/name
ตัวอย่างการใช้งานฟังก์ชัน Loader ใน Remix
Remix Framework นั้นสามารถทำงานแบบ Server Side Rendering (SSR) ได้ และจะมีฟังก์ชันที่ใช้งานร่วมกับ SSR นี้อยู่ 2 ฟังก์ชัน ได้แก่ loader และ action
ในตัวอย่างนี้เราจะมาดูตัวอย่างการใช้งานฟังก์ชัน loader กันก่อน
การทำงานของฟังก์ชัน loader จะทำงานก่อนที่หน้าจอ (React Component ที่อยู่ในโฟลเดอร์ routes) นั้นจะถูก render ออกมา การใช้งานคือ เรียกใช้เพื่อโหลดค่าอะไรสักอย่าง เช่น การโหลดค่าจาก API เพื่อมาแสดงบนหน้าจอ
ไปดูตัวอย่างโค้ดที่หน้า app/routes/index.jsx กันเลย
ใน Remix Framework จะใช้นามสกุลไฟล์ .jsx เพื่อบ่งบอกว่าไฟล์นั้นเป็น React Component
.
วิธีการใช้งานฟังก์ชัน loader
เราต้องเขียนฟังก์ชัน loader (ใช้ชื่อนี้เท่านั้น) ในไฟล์ jsx ก่อนเรียกใช้งานเสมอ ให้เขียนฟังก์ชัน loader อยู่นอก React Component และต้องเขียน export ฟังก์ชันด้วย (สามารถเขียนแบบ Arrow Function ได้)
สิ่งที่ return ออกจากฟังก์ชัน loader จะ return ออกเป็นค่าตัวแปรชนิดพื้นฐาน (ตัวเลข, ตัวหนังสือ/ข้อความ, เลขทศนิยม, ค่า boolean) หรือแบบ Object ก็ได้
เราจะนำค่าไปใช้ใน React Component ด้วยฟังก์ชัน useLoaderData ซึ่งเป็น hook function จะต้องเขียนใน React Component แบบที่เป็น Function Component เท่านั้น
export function loader(){
const something = true
return something
}
.
หลักการทำงานของฟังก์ชัน loader
อย่างที่บอกไว้แล้ว ฟังก์ชัน loader จะเป็นฟังก์ชันที่ถูกเรียกก่อน Component จะแสดงผล และจะเป็นการเรียกในฝั่ง Server เมื่อประมวลผลเสร็จแล้ว Compiler จะส่งค่า Html CSS Javascript ไปให้เบราเซอร์แสดงผล (ฝั่ง Client)
ฟังก์ชัน loader มีพารามิเตอร์ request ส่งเข้ามา เราสามารถใช้งานตัวแปร request นี้ในการดึงค่า url เพื่อที่จะนำมาประมวลผลได้ เช่น การดึงค่า query string ที่ชื่อว่า completed จาก url (ตัวอย่าง http://localhost:3000?complete=true) จะ เขียนโค้ดได้ดังนี้
export function loader({request}){
const reqUrl = new URL(request.url)
const completed = reqUrl.searchParams.get("completed")
return completed!=null && completed==="true"
}
.
การใช้ค่าจากฟังก์ชัน loader ใน React Component
เวลาเราจะเรียกใช้ค่าที่ประมวลผลได้จากฟังก์ชัน loader นั้น เราจะใช้ ฟังก์ชันชื่อว่า useLoaderData ซึ่งเราต้องประกาศใช้ฟังก์ชันนี้ใน React Component ที่เป็นแบบ Funtion Component เท่านั้น เพราะ ฟังก์ชันนี้เขียนเป็นแบบ hook function มีวิธีการประกาศตัวแปรที่รับค่าจากฟังก์ชัน useLoadData ดังนี้
const data = useLoaderData()
ค่าที่ได้รับมาจะอยู่ในตัวแปร data ซึ่งสามารถนำไปใช้งานต่อได้
.
ตัวอย่างโค้ดโปรแกรม การใช้งานฟังก์ชัน loader
- ตัวอย่างด้านล่าง เป็นตัวอย่างที่ใช้ฟังก์ชัน loader โหลดข้อมูล todo list จากเว็บไซต์ url https://jsonplaceholder.typicode.com/todos
- โดยจะคำนวณค่าตัวแปร filter ที่ส่งค่าผ่าน query string ชื่อ completed ใน url
- หากมี query string ส่งมาด้วยจะทำการเพิ่ม query string ใส่ท้าย url ที่จะทำการ fetch data
- จากนั้นทำการ fetch data จาก url แล้วเก็บไว้ที่ตัวแปร list
- และจะ return ค่า object ที่เก็บค่า list และ filter เพื่อนำไปใช้งานต่อ
- การเรียกใช้ค่าที่ได้รับจาก ฟังก์ชัน loader เราจะเรียกใช้ค่าที่ React Component ในตัวอย่างนี้คือ Index โดยกำหนดตัวแปร data มารับค่าจากฟังก์ชัน useLoaderData()
- ค่าในตัวแปร data จะเป็นค่า object ที่มีค่า list และ filter เก็บอยู่ เราจะนำค่านี้ไปใช้แสดงผลใน Component TodoList และ button ต่อไป
อธิบายเพิ่มเติม Component TodoList (ไฟล์อยู่ที่ app/component/TodoList.jsx) เป็น Component ที่รับค่า list มาและทำการแสดงรายการ TodoItem ของแต่ละรายการ
.
ตัวอย่างการใช้งานฟังก์ชัน Loader และ การเปลี่ยน state แบบ React ปกติ
จากตัวอย่างที่แล้วการโหลดข้อมูลโดยใช้เฉพาะฟังก์ชัน loader ให้โปรแกรมทำงานเฉพาะฝั่ง server เราจะพบว่าในการกดปุ่ม Filter(กรองข้อมูล all/Done/Todo) จะมีจังหวะที่หน่วงโปรแกรมอยู่ ในตัวอย่างนี้เราจะปรับโปรแกรมตอนที่กด Filter จากเดิมจะใช้วิธีการเปลี่ยน url เพื่อไปรันที่ server ใหม่ ให้เป็น การใช้ useState ซึ่งจะประมวลผลที่ Client แทน
- เริ่มต้นที่ ฟังก์ชัน loader จะทำการ Fetch ค่าทั้งหมดจาก url https://jsonplaceholder.typicode.com/todos แล้วส่งไปให้ Component Index ใช้งาน
- เรียกใช้งานค่าที่ได้รับจาก loader ด้วยฟังก์ชัน useLoaderData
- วิธีการแสดงผลรายการ เราจะส่งข้อมูลทั้งหมด (data.list) และข้อมูล filter (data.filter) ไปยัง Component TodoList
- ใน Component TodoList จะเช็คเงื่อนไขการแสดงค่า หากค่า filter = 1 ก็จะแสดงรายการทั้งหมด แต่ถ้า filter = 2 จะแสดงที่เสร็จแล้ว และ filter = 3 จะแสดงที่ยังไม่เสร็จ
const TodoList = ({ list, filter }) => {
if (list && list.length > 0) {
if (filter === 1) {
return list.map((item, index) => <TodoItem item={item} key={index} />);
} else {
return list
.filter((i) => i.completed === (filter === 2))
.map((item, index) => <TodoItem item={item} key={index} />);
}
} else {
return <p>- no data -</p>;
}
};
วิธีการเขียนแบบนี้ โปรแกรมจะประมวลผลการกรองข้อมูล ที่ฝั่ง Client และจะไม่มีการส่ง url ไปโหลดใหม่เหมือนกับตัวอย่างก่อนหน้า จะทำให้การใช้งานลื่นมากกว่าตัวอย่างก่อนหน้า
ดังนั้น ถึงแม้เราจะเลือกใช้ Remix Framework แต่ก็ยังสามารถใช้ความสามารถของ React ปกติ ได้อย่างครบถ้วน
หากสังเกตอีกนิด ใน Component Index จะมีการใช้งาน useMemo ไว้คำนวนค่าจำนวนของรายการ โดยกำหนด dependency เป็น data และ filter ซึ่งหมายความว่า หากค่า 2 ตัวนี้ตัวใดตัวหนึ่งมีการเปลี่ยนแปลง ค่า listLength จะถูกคำนวนใหม่ทันที
.
ตัวอย่างการใช้งานฟังก์ชัน Action
ฟังก์ชัน action เป็นฟังก์ชันที่รับค่ามากจาก Form (สามารถใช้ได้ทั้ง form ของ html ปกติ หรือ Component Form ของ Remix) ซึ่งใน Form จะรับค่า request เข้ามา
ในตัวแปร request เราสามารถนำค่าที่ผู้ใช้กรอกจาก Form มาใช้ได้ผ่านคำสั่ง
รูปแบบการประกาศใช้งานฟังก์ชัน action
export async function action({ request }) {
//...
}
การตั้งค่าให้ Form นั้นมาเรียกไปประมวลผลที่หน้าไหน เราจะกำหนดที่ action ของ Form หากไม่ได้กำหนด action ใน Form โปรแกรมก็จะไปประมวลผลที่หน้า ที่เรียกใช้ Component Form นั้น ดังนี้
const BookForm = () => {
return (
<div>
<h3>Submit Form Example</h3>
<Form method="POST">
<input type="text" name="title" placeholder="book title" required />
<br />
<textarea name="detail" placeholder="detail here" required />
<br />
<select name="type">
<option value="Book">Book</option>
<option value="Picture Book">Picture Book</option>
<option value="Fiction">Fiction</option>
<option value="Nonfiction">Nonfiction</option>
</select>
<br />
<input type="text" name="author" placeholder="author name" required />
<br />
<button type="submit">submit</button>
<button type="reset">reset</button>
</Form>
</div>
);
};
การเรียกใช้ค่าที่ส่งมาจาก Form จะใช้ชื่อเรียกตาม name ที่กำหนดในแต่ละ Input เช่น เราตั้งชื่อ Input ว่า title, detail, type, author จะสามารถเรียกค่ามาใช้งาน ได้ดังนี้
const form = await request.formData();
// การนำค่าจากฟิวด์ต่างใน Form ไปใช้งาน
const title = form.get("title")
const detail = form.get("detail")
const type = form.get("type")
const author = form.get("author")
การประมวลผลฟังก์ชัน action จะทำในฝั่ง Server ซึ่งตอนท้ายของฟังก์ชัน action เราจะต้องทำการ return ค่า ด้วยการ redirect กลับไปที่หน้าเดิม หรือ ไปยังหน้าใหม่ เพื่อให้ โปรแกรมได้แสดงผลตามข้อมูลที่เราพึ่งบันทึกลงไปได้
export async function action({ request }) {
const form = await request.formData();
//...
return redirect("/");
}
.
ตัวอย่างการใช้งาน Routes และ Outlet ใน Remix
เราสร้าง Component Movies (movies.jsx) ในโฟลเดอร์ routes และใน Movies ให้แสดงรายการชื่อหนังสือ และมี Link ไปยังหน้ารายละเอียดของแต่ละเรื่อง โดยไฟล์ Component ของแต่ละเรื่อง เราจะต้องตั้งโฟลเดอร์ชื่อ movies ใน route และในโฟลเดอร์ movies ให้มีไฟล์ $title.jsx อยู่ เพื่อใช้อ้างอิงลิ้ง /movies/$title เมื่อมีการผ่านค่าข้อความอะไรก็ตามตามหลังพาท /movies router จะชี้มายังไฟล์ /movies/$title.jsx ทันที
.
การใช้งาน Outlet ใน Remix Framwork
จะสังเกตว่าใน Component Movies จะมีการเรียกใช้ <Outlet /> อยู่ในส่วนท้าย ซึ่ง <Outlet /> นี้จะทำหน้าที่แสดงผลในส่วนของ Component ที่อยู่โฟลเดอร์ที่ชื่อเดียวกันกับ Component นั้น ดังตัวอย่าง
<Outlet /> ที่อยู่ใน Component Movies จะแสดงผลของ Component Movie จากไฟล์ movies/$title.jsx เมื่อมีการเรียกไปที่พาท /movies/$titles โปรแกรมจะแสดงผลของ Component Movie ต่อท้ายให้อัตโนมัติ
.
บทส่งท้าย
ในบทความนี้เราได้ทดลองเขียนโปรแกรม React ที่ใช้ Remix Framework บน Codesanbox.io ที่ทำให้เราสามารถเขียนโปรแกรมบนเว็บไซต์ได้ และไม่ต้องติดตั้งอะไรที่เครื่องของเราเลย สะดวกมากๆ ในตัวอย่างที่ได้ยกตัวอย่างไป เช่น การใช้งาน Route การใช้งานฝั่ง Server Render ได้แก่ฟังก์ชัน loader และ action และการแสดงผลของ Component ที่อยู่ใน Root เดียวกันด้วย Component <Outlet />
Remix Framework เป็นเฟรมเวิร์คที่น่าสนใจมาก สามารถนำใช้เป็นเฟรมเวิร์คเริ่มต้นได้ เพราะ เป็นเฟรมเวิร์คที่ช่วยลดเวลาในการเขียนโปรแกรมจัดการเรื่อง Server Site Rendering การทำ Route การเก็บค่าฝั่งเซิฟเวอร์เช่น Session Coookies เป็นต้น ในความคิดเห็นส่วนตัวแล้ว ถ้าโปรแกรมที่เราจะพัฒนานั้นเป็นโปรแกรมไม่ใหญ่มาก (ผู้ใช้งานเฉพาะในองค์กร ฟีเจอร์การทำงาน เช่น CRUD และออกรายงาน เป็นต้น) และไม่จำเป็นต้องแยกเซิฟเวอร์สำหรับทำ API เซิฟเวอร์ เราก็สามารถเขียนเชื่อมข้อมูลกับฐานข้อมูลได้ในโปรเจคเดียว การจัดการเกี่ยวกับโครงสร้างโปรแกรมไม่ยุ่งยาก สำหรับคนที่มีพื้นฐานการเขียนโปรแกรมด้วย React อยู่แล้ว จะใช้เวลาศึกษาเพิ่มอีกไม่นานก็สามารถใช้งานเฟรมเวิร์คตัวนี้ได้อย่างคล่องแน่นอน
ปล. ณ วันที่เผยแพร่บทความนี้ React v.18 ก็ออกแล้ว (React v.18 ออกวันที่ 29/3/2022) ในเวอร์ชันนี้มีการปรับการ Render ของ React ใหม่ตั้งแต่บรรทัดแรกของการ Render เลย และ React สามารถ Render ฝั่ง Server ได้ด้วย Server Component (รออัพเดทเวอร์ชันหน้ามาแน่นอน!) แถมด้วยฟังก์ชัน hook ที่เพิ่มมาใหม่ ทำให้ Framework อื่นๆ จะต้องพัฒนาเพิ่มให้เยอะกว่า React ปัจจุบัน
.