ทำ Routing ให้กับ React ด้วย React Router v4
สวัสดีครับ สำหรับบทความนี้เป็นภาคต่อจาก มาเริ่มต้นเขียน 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
ที่เพิ่งติดตั้งลงไป
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
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 ลองดูผลลัพะ์ที่หน้าเราอีกที
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 ที่เราต้องการได้
แต่!!
จะเห็นว่าทุกๆครั้งที่เรากด Link มันจะทำการ refresh หน้าใหม่ทุกครั้งเลย ไม่ใช่ตาม concept SPA ที่จะ route โดยไม่ต้อง refresh วิธีแก้ก็คือการใช้ Link
ครับ
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
ต่อมา 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 />
สุดท้าย ผมทำการย้ายพวก Component ที่ประกาศไว้ใน src/App.js
ไปแยกไว้แต่หน้าไฟล์ ได้เป็น
src/pages/About/index.js
src/pages/Project/index.js
src/pages/Home/index.js
src/pages/Post/index.js
และก็ได้เพิ่มเนื้อหาใส่ไปเล่นๆ ให้หน้าแต่ละหน้าดูมีอะไรนิดหน่อย สุดท้ายก็ได้เว็บเรียบๆง่ายๆ แบบนี้ครับ
Conclusion
สรุปบทความนี้ก็เป็นตัวอย่างการใช้งาน React Router แบบง่ายๆ ยังไม่ได้ประยุกต์อะไรที่มันซับซ้อนมากนัก ลองนำไปใช้กันดูครับ ซึ่งก็ลองไปดูการทำ Route แบบ Authentication หรือว่าการ nested routing กันดู
สุดท้าย Source Code ของบทความนี้ครับ
Reference
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit