ทำ Routing ให้กับ React ด้วย React Router v4

Published on
React
2018/02/basic-web-with-react-router-v4
Discord

สวัสดีครับ สำหรับบทความนี้เป็นภาคต่อจาก มาเริ่มต้นเขียน React ด้วย Create React App กันดีกว่า ละกัน ว่าด้วยเรื่องการทำ Routing ด้วยการใช้ React Router กัน เพื่อเอามาทำเว็บไซต์ที่มีหลายๆหน้า การ render แต่ละหน้า การ navigate ไปหน้าอื่นๆ นั้นทำยังไงบ้าง ซึ่งเนื้อหาทั้งหมด ก็ตามด้านล่างเลย

Table of Contents

Step 1 : Getting Started

เริ่มต้นสร้างโปรเจ็ค React ด้วย create-react-app ได้เลย

npx create-react-app basic-web-react-router

cd basic-web-react-router
yarn

สำหรับบทความพื้นฐาน React สามารถอ่านเพิ่มเติมได้ที่ : มาเริ่มต้นเขียน React ด้วย Create React App กันดีกว่า

เมื่อได้โปรเจ็ค React มาแล้ว ต่อมาผมจะทำการ Format Code เป็น Standard Style (คือลบพวก comma) จาก Project ที่สร้างจาก create-react-app สำหรับใครที่ไม่ต้องการ format อยากได้แบบเดิม ก็ข้ามขั้นตอนนี้ได้เลยครับ ไป Step 2 : Basic Router

1. เพิ่มไฟล์ .eslintrc

{
  "extends": ["react-app", "standard"],
  "rules": {
    "space-before-function-paren": 0
  }
}

2. ทำการเพิ่ม dependencies ต่างๆ

yarn add prettier standard eslint eslint-config-react-app eslint-config-standard eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-node eslint-plugin-promise eslint-plugin-standard

3. Add script

ทำการเพิ่ม scripts ไปที่ไฟล์ package.json เพื่อเวลาสั่งรัน prettier จะให้มันทำการ format code โดยไม่ใช้ semicolon และ string แบบ single quote

scripts: {
  "prettier": "./node_modules/.bin/prettier --semi=false --single-quote=true --write src/*.js"
}

4. Run prettier

รันคำสั่ง Prettier ด้วย

yarn prettier

Step 2 : Basic Router

ขึ้นตอนต่อมา ทำการติดตั้ง React Router กันเลย

yarn add react-router-dom

จากนั้นเปิดไฟล์ src/index.js จากโค๊ดด้านล่าง

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

ทำการแก้ไขโค๊ดเป็นแบบนี้

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter } from 'react-router-dom';

const AppWithRouter = () => (
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

ReactDOM.render(<AppWithRouter />, document.getElementById('root'));
registerServiceWorker();

จากโค๊ด จะเห็นว่าเรามีการเพิ่ม BrowserRouter จาก react-router-dom และทำการใส่ไว้ On Top ของ Component ทำการหุ้ม Component App เราอีกชั้นนึงก่อนที่จะทำการ render

  • ควรใช้ BrowserRouter : ถ้าเรามี Server เอาไว้ serve พวก url
  • ควรใช้HashRouter : ถ้า Server เราเป็นแบบ serve static file เฉยๆ

สังเกตตอนนี้ Web เราก็ยังรันได้ปกติ อาจจะต้องมี stop แล้ว yarn start อีกครั้ง เพราะว่ามันอาจจะมองไม่เห็น react-router-dom ที่เพิ่งติดตั้งลงไป

Hello React

Step 3 : Route & Switch

มาถึงขั้นตอนการทำ Route นะครับ ตอนนี้ผมจะใส่ Bulma เข้ามา เพราะว่าเป็น Stylesheet ที่จะใช้ในบทความนี้ ที่ไฟล์ public/index.html เพิ่ม Bulma และ Font เข้าไป

<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css"
/>
<link href="https://fonts.googleapis.com/css?family=Quicksand:300,700" rel="stylesheet" />

เพิ่มไฟล์ src/index.scss

body {
  margin: 0;
  padding: 0;
  font-family: 'Quicksand', sans-serif;
}

.App {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

ต่อมาที่ไฟล์ src/App.js ทำการแก้ไขไฟล์เป็นแบบนี้

import React, { Component } from 'react';
import { Route } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const Post = () => <h1>Post</h1>;
const Project = () => <h1>Project</h1>;

class App extends Component {
  render() {
    return (
      <div className="App container">
        <Route path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/posts" component={Post} />
        <Route path="/projects" component={Project} />
      </div>
    );
  }
}

export default App;

อธิบายเพิ่มเติม

const Home = () => <h1>Home</h1>
  • เป็นการสร้าง Component ที่ชื่อ Home ทำการ render คำว่า Home นั่นเอง
const About = () => <h1>About</h1>
  • สร้าง Component ชื่อ About และก็เหมือนกันกับ Post และ Project

และส่วน

<Route path="/projects" component={Project} />
  • path : เป็นการบอกว่า ถ้า URL มี path = /project จะให้มัน render component ชื่อ Project

ทีนี้เราลองเข้าเว็บใหม่ ทีนี้ลองเข้าผ่าน URL http://localhost:3000/posts หรือ http://localhost:3000/about

React Router

Route

อยากที่บ้านบนอธิบายไปแล้ว ตัว Route ถือเป็นส่วนนึงที่สำคัญของ React Router เลยก็ว่าได้ เป็นส่วนที่เราจะเอาไว้กำหนดว่า URL location ที่เราเข้ามาผ่านทาง Browser นั้นตรงกันกับที่เราประกาศไว้ใน Route หรือไม่ ถ้าตรง ก็จะทำการ render ตัว Component ที่เราได้ทำการกำหนดไว้

ซึ่งนอกจาก props path หรือ component แล้ว ตัว Route ก็ยังมี props ที่สำคัญอีก อันนึงคือ exact

ซึ่งจากด้านบน จะเห็นว่า เวลาเราเข้า /project หรือว่า /about จะเห็นคำว่า Project และ About เวลาเข้าหน้าของมัน แต่ก็ดันมีคำว่า Home โผล่มาด้วย นั่นก็เพราะว่า <Route path="/"> นั้นมัน match ทั้ง 2 path ครับ (คือมี / ใน url ไม่สนว่าต่อท้ายเป็นอะไร มัน match อะ)

วิธีการแก้ไขคือ ปรับเป็นแบบนี้

<Route exact path="/" component={Home} />

เป็นการบอกว่า ทีนี้ ต้อง match แบบว่า http://localhost:3000/ ยย่างเดียวนะ ถึงจะ render Home ถ้ามี เช่น /projects ถือว่ามันไม่ match ลองดูผลลัพะ์ที่หน้าเราอีกที

React Router

Switch

ต่อมา Switch ตัวนี้จริงๆ ทำงานคล้ายๆ Route แต่ต่างกันที่ถ้าเป็น Switch เมื่อมัน match กับ location ไหนแล้ว มันก็จะ render ตัวนั้นไปเลย ตัวอื่นๆ จะไม่สน โดยไล่จากบนลงล่าง เช่น

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

render() {
  <Switch>
    <Route path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/posts" component={Post} />
    <Route path="/projects" component={Project} />
  </Switch>
}

ซึ่งส่วนใหญ่แล้ว Switch ผมจะใช้เอาไว้ในบล็อก ที่มันต้อง render อย่างน้อย 1 Component เช่น บางทีจะวาง Route ที่เป็น Error 404 เอาไว้ เช่น ถ้าไม่เจอ location อะไรเลย ก็ให้มัน render ตัวนี้ไป เช่น

<Switch>
  <Route path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/posts" component={Post} />
  <Route component={NotFoundPage} />
</Switch>

Step 4 : Navigation

ต่อมาจะเห็นว่าเรากำหนด Route เรียบร้อยแล้ว ทีนี้จะเข้าไปแต่ละหน้า ต้องมานั่งพิมพ์ URL เอาหรอ? ไม่สะดวกแน่นอน ทำไมเราไม่ใช้ <a href="#"> ละ เพื่อจะ link ไปหน้า อื่นๆ

ก็ทำการเพิ่ม <a> กันเลย ที่ไฟล์ src/App.js ผมทำการเพิ่ม Navbar ของ Bulma ลงไปด้วย ก็เลยได้ประมาณนี้

import React, { Component } from 'react';
import { Route } from 'react-router-dom';

const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
const Post = () => <h1>Post</h1>;
const Project = () => <h1>Project</h1>;

class App extends Component {
  render() {
    return (
      <div className="my-app">
        <nav className="navbar is-light" role="navigation" aria-label="main navigation">
          <div className="container">
            <div className="navbar-brand">
              <a className="navbar-item" href="https://devahoy.com">
                <img
                  src="https://devahoy.com/assets/images/devahoy-text-logo.png"
                  alt="DEVAHOY LOGO"
                  width="112"
                  height="28"
                />
              </a>
            </div>
            <div className="navbar-menu">
              <div className="navbar-end">
                <a href="/" className="navbar-item">
                  Home
                </a>
                <a href="/posts" className="navbar-item">
                  Posts
                </a>
                <a href="/projects" className="navbar-item">
                  Projects
                </a>
                <a href="/about" className="navbar-item">
                  About
                </a>
                <a class="navbar-item" href="https://github.com/phonbopit" target="_blank">
                  Star on <i className="fab fa-github"></i>
                </a>
              </div>
            </div>
          </div>
        </nav>
        <div className="App container">
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/post" component={Post} />
          <Route path="/project" component={Project} />
        </div>
      </div>
    );
  }
}

export default App;

ทีนี้เวลาเรากด Link บน Navbar ก็จะ เปลี่ยนหน้า และ match location ที่เราต้องการได้

React Router

แต่!!

จะเห็นว่าทุกๆครั้งที่เรากด Link มันจะทำการ refresh หน้าใหม่ทุกครั้งเลย ไม่ใช่ตาม concept SPA ที่จะ route โดยไม่ต้อง refresh วิธีแก้ก็คือการใช้ Link ครับ

ตัว Link จะเป็นเหมือนกับ <a> เพียงแต่ว่าจะเป็นของ React Router และเปลี่ยนจาก href เป็น to แทน เช่น

<Link to="/projects">Projects</Link>

เราลองมาเปลี่ยน <a href=""> ทั้งหมด เป็น Link ก็จะได้เป็นแบบนี้

<div className="navbar-end">
  <Link to="/" className="navbar-item">
    Home
  </Link>
  <Link to="/posts" className="navbar-item">
    Posts
  </Link>
  <Link to="/projects" className="navbar-item">
    Projects
  </Link>
  <Link to="/about" className="navbar-item">
    About
  </Link>
  <a className="navbar-item" href="https://github.com/phonbopit" target="_blank">
    Star on <i className="fab fa-github"></i>
  </a>
</div>

ลองทำการกดใหม่ ทีนี้หน้าเราจะไม่ Refresh แล้ว เป็นอันเรียบร้อย

ต่อมา NavLink ตัวนี้จริงๆก็เหมือนกับ Link เลย เพียงแต่ว่ามันสามารถกำหนด active style, active class ให้กับ Link ได้ เช่น ถ้ามัน location match กัน ก็จะให้มันเป็น class is-active อะไรอย่างงี้

ตอนนี้ตัว Bulma มันจะมี helper class ที่เอาไว้ highlight กรณีที่ active ก็คือคลาส is-active ผมก็เลยใส่ กรณีถ้า match location เลยได้เป็นแบบนี้

<div className="navbar-end">
  <NavLink exact to="/" activeClassName="is-active" className="navbar-item">
    Home
  </NavLink>
  <NavLink to="/posts" activeClassName="is-active" className="navbar-item">
    Posts
  </NavLink>
  <NavLink to="/projects" activeClassName="is-active" className="navbar-item">
    Projects
  </NavLink>
  <NavLink to="/about" activeClassName="is-active" className="navbar-item">
    About
  </NavLink>
  <a className="navbar-item" href="https://github.com/phonbopit" target="_blank">
    Star on <i className="fab fa-github"></i>
  </a>
</div>

Note: <NavLink /> หรือ <Link /> เราสามารถใส่ exact ได้แบบเดียวกับ <Route />

React Router step 4

สุดท้าย ผมทำการย้ายพวก Component ที่ประกาศไว้ใน src/App.js ไปแยกไว้แต่หน้าไฟล์ ได้เป็น

  • src/pages/About/index.js
  • src/pages/Project/index.js
  • src/pages/Home/index.js
  • src/pages/Post/index.js

และก็ได้เพิ่มเนื้อหาใส่ไปเล่นๆ ให้หน้าแต่ละหน้าดูมีอะไรนิดหน่อย สุดท้ายก็ได้เว็บเรียบๆง่ายๆ แบบนี้ครับ

React Router Final

Conclusion

สรุปบทความนี้ก็เป็นตัวอย่างการใช้งาน React Router แบบง่ายๆ ยังไม่ได้ประยุกต์อะไรที่มันซับซ้อนมากนัก ลองนำไปใช้กันดูครับ ซึ่งก็ลองไปดูการทำ Route แบบ Authentication หรือว่าการ nested routing กันดู

สุดท้าย Source Code ของบทความนี้ครับ

Reference

Buy Me A Coffee
Authors
Discord