สอนเขียนเว็บด้วย React.js ปี 2023 (Intro to React) ตอนที่ 3

React Aug 7, 2023

สวัสดีครับ บทความนี้บทความที่ 3 และเป็นตอนสุดท้ายของซีรีย์ สอนเขียนเว็บด้วย React แล้วนะครับ

สำหรับตอนนี้ จะเป็นเรื่องของการใช้ React Router เพื่อทำเว็บให้รองรับหลายๆหน้า และก็การ Deploy เว็บไซต์ของเราด้วย Netlify นะครับ

เนื้อหาสอนเขียนเว็บ React.js มีทั้งหมด 3 ตอนนะครับ

1. React Router

ปกติตัว React เป็น Client และตัวไฟล์ ก็มีเพียงแค่ index.html เราจะทำหน้าเว็บให้มีหลายๆหน้ายังไง? คำตอบคือใช้ Client Router ซึ่งที่นิยมก็หนีไม่พ้น React Router ครับ

ตัว React Router ทำให้เราสามารถที่จะกำหนด route หน้าต่างๆ ว่าเว็บเราจะมีหน้าอะไรบ้าง (เป็นฝั่ง Client อย่างเดียวนะครับ ตัว Server ก็ยังคงมีแค่ index.html)

Home v6.14.2
เวอร์ชั่น ณ​วันที่เขียนบทความคือ React Router v6.14.2

หลักการทำงานของ ClientRouter

ปกติ เวลาเรา Users (Client) จะ request ไปที่ Server ตัว Server ก็จะส่งข้อมูลกลับใช่มั้ย

users -> request -> server
server -> response -> index.html

เมื่อฝั่ง Users ได้รับข้อมูล ใน index.html ก็จะมีไฟล์ React bundle js อยู่ เวลาที่ user กดหน้าต่างๆ มันก็จะมาเข้าเงื่อนไขใน javascript เช็คว่า router เราตั้งค่าไว้ถูกมั้ย ถ้าถูกก็ render react ตามหน้าที่เรากำหนดไว้

สำหรับตัวอย่างนี้ ผมก็จะเริ่มสร้างโปรเจ็คขึ้นมาใหม่เลย โดยใช้ Vite

npm create vite@latest hello-react-router -- --template react-ts

ต่อมาทำการติดตั้ง React Router Dom

npm install react-router-dom

ทีนี้สิ่งที่เราจะทำคือ เราต้องการให้เว็บเรามี 3 หน้า  หน้าคือ

  • / - คือหน้า Home Page
  • /products - เป็นหน้า Products
  • /about - เป็นหน้า About Page

ใน React Router  เราสามารถทำ Routes ได้หลายวิธี โดยตัวอย่าง ผมจะยกมา 2 แบบคือ

1. ใช้ Routes และ Route

เริ่มจาก ใช้ BrowserRouter โดยการเปิดไฟล์  main.tsx ให้ <App /> อยู่ภายใต้ BrowserRouter แบบนี้

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

import { BrowserRouter } from 'react-router-dom'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)

จากนั้นกำหนด Routes และ Route ที่ไฟล์ App.tsx

<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />
    <Route path="about" element={<About />} />
    <Route path="products" element={<Products />} />
    <Route path="*" element={<p>Not found</p>} />
  </Route>
</Routes>

หลักการทำงานคือ เราจะใช้ Routes เป็นเหมือน Provider ครอบ route ที่เราต้องการทั้งหมด

จากนั้น <Route> จะกำหนด

  • path - หมายถึง url path ที่ match กับ browser url
  • element - เมื่อ path match กัน มันจะทำการ render component ที่เรากำหนด

อย่างเช่น ตัวอย่าง

  • ถ้าเราเข้าเว็บ http://localhost:5173 มันจะก็จะ match path / และทำการ render Layout
  • แต่ถ้าเราเข้า http://localhost:5173/about มันก็จะ match path about ก็ render About นั่นเอง

ทีนี้มาดูที่ component Layout เราทำการสร้าง Layout.tsx ขึ้นมา ผมสร้างไว้ใน folder components

import { Link, Outlet } from 'react-router-dom'

export default function Layout() {
  return (
    <>
      <header>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/products">Products</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
          </ul>
        </nav>
      </header>

      <main>
        <Outlet />
      </main>

      <footer>React Router Example - 2023</footer>
    </>
  )
}

จะสังเกตเห็น <Outlet /> จะทำหน้าที่คล้ายๆการ render children (ไม่เหมือนกันซะทีเดียว) คือจะทำการ render nest Router ข้างใน ถ้าเรากลับไปดู routes ที่เรากำหนดไว้

<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />
    <Route path="about" element={<About />} />
    <Route path="products" element={<Products />} />
    <Route path="*" element={<p>Not found</p>} />
  </Route>
</Routes>

เมื่อ path เป็น about ตัว <Outlet> ก็จะถูกแทนที่ด้วยการ render <About /> นั่นเอง

2. Router Providers

วิธีนี้จะใช้แบบ RouterProvider โดยการเรียก createBrowserRouter() วิธีนี้ ข้อดีคือมีพวก Data API ที่มากับ React Router v6.4 (เช่นพวก data loader, fetcher, actions ต่างๆ)

วิธีการคล้ายๆ แบบแรก แต่เปลี่ยนจาก <BrowserRouter> มาเป็น <RouterProvider> แบบนี้

import { RouterProvider } from 'react-router-dom'

<RouterProvider router={router} />

โดยที่ router สร้างจาก createBrowserRouter() โดยใช้ config ก็คือ routes ที่เราต้องการ

import { createBrowserRouter } from 'react-router-dom'


const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: '/',
        index: true,
        element: <Home />,
      },
      {
        path: 'about',
        element: <About />,
      },
      {
        path: 'product',
        element: <Product />,
      },
      {
        path: '*',
        element: <p>Not Found</p>,
      },
    ],
  },
])

ในการทำ link ต่างๆ ใน React Router เราจะใช้ <Link> ดังที่ได้เห็นในโค๊ดตัวอย่าง ตรงที่เป็น Layout.tsx  เช่น  <Link to="">

import { Link } from 'react-router-dom'

function IndexPage() {
  return (
    <div>
      <h1>Hello React Router</h1>
      <Link to="/about">About</Link>
    </div>
  );
}
  • ใช้ attribute (props) to ซึ่งถ้าเป็น <a> เราจะใช้ href นั่นเอง

การทำ Dynamic Route

ใน React Router เราสามารถกำหนด pattern ให้ dynamic route ได้ เช่น เรามี product เยอะๆ สมมติ 100 ตัว การที่เราต้องมากำหนด route ให้ 100 ตัว คงลำบากน่าดู และไม่น่ามีใครเค้าทำกันใช่มั้ยครับ 🤣

<Route path="products/1" element={<Product1 />} />
<Route path="products/2" element={<Product2 />} />
<Route path="products/3" element={<Product3 />} />
...
<Route path="products/100" element={<Product100 />} />

เราสามารถกำหนด pattern ได้โดยใช้ colon (:) แบบนี้ได้ครับ

<Route path="products/:id" element={<ProductDetail />} />

แล้วเราจะ get ค่า :id ได้ยังไง? เราดึงได้จาก params.id ครับ ใช้ useParams()

สร้าง component ProductDetail ขึ้นมา

import { useParams } from 'react-router-dom'

export default function ProductDetail() {
  const { id } = useParams()

  return (
    <div>
      <h2>Product ID: {id}</h2>
    </div>
  )
}

ทีนี้เวลาเราเข้า url ที่มี id ต่างกัน เราก็จะได้ Product ID ต่างกันแล้วครับ

2. Deployment

เรื่องสุดท้าย เรื่องของการ Deployment Project ของเรา ปกติเวลาที่เรา dev เรารันคำสั่ง

npm run dev

เวลาแก้ไขไฟล์มันจะทำการ refresh การเปลี่ยนแปลงให้เอง เราอาจจะไม่รู้ว่าไฟล์ที่เราจะต้องเอาไป deploy เป็นยังไง ต้องอัพ component ลงไปที่ Hosting ที่เราจะใช้หรือไม่?

จริงๆ แล้ว การ deploy ก็เหมือนกับเรา deploy เว็บไซต์ HTML/CSS/JS ปกติเลยครับ คือมีแค่ไฟล์ html ไฟล์ css และ ไฟล์ javascript

ทีนี้ การที่เราจะ build เพื่อไป deploy เนี่ย จะใช้คำสั่งนี้

npm run build

ซึ่งคำสั่งนี้ตัวที่เราใช้ Vite มันคือการ compile typescript จากนั้น ก็ทำการ build ไฟล์ให้เรา โดยการ minify & bundle ไฟล์ต่างๆ ที่เราเขียนพวก components ต่างๆ ให้เป็นไฟล์ bundle ไฟล์เดียว และ output อยู่ใน folder dist ซึ่งถ้าเราดูใน folder จะมีไฟล์อยู่แค่ html, css และ js (ชื่อไฟล์ต่างกันตาม hash ที่ random generate)

├── assets
│   ├── index-04490b11.css
│   └── index-2bb88b1a.js
├── index.html
└── vite.svg

การ deploy ก็เพียงแค่อัพไฟล์ พวกนี้ลงบน Hosting ของเรา เราก็ได้หน้าเว็บแล้วครับ ก่อน deploy เรามาลองรัน localhost ด้วยการใช้ npx serve ดูครับ เป็นเหมือนการจำลอง server ขึ้นมา เพื่อ serve ไฟล์ใน dist ครับ

npx serve dist

จากนั้น ลองเข้าเว็บ http://localhost:3000 และลองเล่นดู จะเห็นว่าเราสามารถกด link อะไรได้ปกติ แต่ว่า ถ้าสมมติ เรา เข้า url http://localhost:3000/products/100 โดยการ refresh มันจะหาหน้าเว็บไม่เจอ

ซึ่งถูกต้องแล้วครับ ใน folder dist เราไม่มีไฟล์ products/100.html ซึ่งทั้งหมดมันปกติครับ เพราะว่า React Router มันทำงานฝั่ง client มันไม่ได้สร้างไฟล์ พวกนี้ วิธีการแก้ไข หรือปกติที่ทำกันคือ

เวลาที่เรากำหนด web server (apache, nginx) ส่วนใหญ่ เค้าจะทำการ rewrite url พวก not found ทั้งหลาย หรือทุกๆ url ให้ไปที่ index.html ครับ

ส่วนตัว serve ทำได้โดยการใช้ option -s แบบนี้

npx serve -s dist

Deploy ด้วย Netlify

Develop and deploy websites and apps in record time | Netlify
Accelerate the time to deploy your websites and apps. Bring your integrations and APIs together on one powerful serverless platform. Get started for free!

การ Deploy ด้วย Netlify ที่ง่ายที่สุด คือ จับโยน folder dist ลงไปเลย โดยเราเลือก Add a new site -> จากนั้นเลือก Deploy Manually

จะไปหน้านี้ จากนั้นเราก็ upload folder dist ของเราลงไปเลย

Netlify App
Start building the best web experiences in record time

เมื่ออัพโหลดไฟล์ของเราเสร็จ ตัว Netlify ก็จะ generate website ให้เรา เราก็จะได้เว็บของเราแล้วครับ (สามารถเปลี่ยนชื่อ subdomain ทีหลังได้ ถ้าชื่อไม่ซ้ำกับคนอื่น)

นอกจากนี้ ก็ยังมีวิธีการ deploy โดยเชื่อมกับ Git นะครับ ทุกครั้งที่เรา push code ลงไป ตัว Netlify ก็จะทำการ build และ deploy ให้เรา แต่ขั้นตอนนี้ผมไม่ได้เขียนนะครับ ลองไปทำกันดูนะครับ

ต่อมา ตัว Netlify ก็มีปัญหาเช่นกัน คือมันไม่ได้ rewrite url ต่างๆ ถ้าสมมติเราเข้า /products/50 มันจะไม่เจอ สิ่งที่เราต้องทำคือ สร้างไฟล์ _redirects ขึ้นมา ในโฟลเดอร์ dist นั่นแหละ ข้างในเป็นแบบนี้

/*	/index.html 200

เป็นการกำหนดว่าให้ทุกๆ url ที่เข้ามา ให้มันไป /index.html ให้หมด (เพื่อให้ client routing มันทำงาน)

จากนั้นก็ upload dist ไปทับของเก่า และลองทดสอบเว็บอีกครั้งครับ, ตัวเลือกอื่นๆในการ Deploy เช่น Vercel, Cloudflare, Github Pages, Firebase Hosting

congrats 🎉🎉🎉🎉

Source Code

สุดท้าย ผมขอฝากคำถาม จริงๆ เป็นกึ่งๆ คำถาม กึ่งๆ quiz ให้ลองไปฝึกทำเพิ่มเติมกันดูครับ

  • ลองทำเว็บ Portfolio โปรโมทตัวเอง สร้างกี่หน้าก็ได้ ตามใจเลย
  • ปรับแต่ง CSS ตามใจชอบ
  • อยากใช้ Bootstrap, MUI หรือ Tailwind CSS ตั้งค่ายังไง?
  • ลอง fetch data มาแสดงผลได้มั้ย?
  • ลองใช้แบบ useEffect() หรือลอง loader ของ React Router?
  • ถ้าจะ Deploy ด้วย Netlify แบบใช้ Git ทำยังไง?
  • หากเราจะทำ custom domain เอง ทำได้มั้ย?
  • ถ้าเราไม่ชอบ Netlify อยากใช้ตัวเลือกอื่นเช่น Vercel ทำได้มั้ย ทำยังไง?

หลังจากเรียนจบคอร์สนี้แล้ว แม้ว่าคอร์สนี้จะไม่ได้สอนทุกอย่าง และบางเรื่องก็เป็นพื้นฐานเท่านั้น แต่ก็คิดว่าเพียงพอให้นำความรู้ที่ได้ไปต่อยอดเพื่อเติมได้ สิ่งสำคัญคือการฝึกฝน และลงมือเขียนครับ แค่อ่าน หรือดู Video เพียงอย่างเดียว คงไม่พอ

ใครที่เรียนจบแล้ว มาถึงตรงนี้ ลองทำเว็บของตัวเองขึ้นมา และก็ลอง Deploy เอา link มาแชร์กันได้นะครับ ที่ Discord หรือ ส่ง link Github เพื่อให้ผมหรือเพื่อนๆใน Community ช่วย Review ได้ครับ

หากใครมีคำถาม ติดปัญหา หรือไม่เข้าใจตรงไหน สามารถสอบถาม หรือมีข้อแนะนำ ข้อเสนอแนะ สามารถพูดคุยกันได้นะครับ

Happy Coding ❤️

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com