React Router v4

Khải Phan
Aug 9, 2018 · 8 min read

React Router là gì ?

React Router là một thư viện điều hướng tiêu chuẩn trong React. Nó giúp cho UI được đồng bộ với URL. Nó có API đơn giản nhưng mạnh mẽ, có thể giúp giải quyết được rất nhiều vấn đề.

Cài đặt React Router

React Router được chia ra làm nhiều package nhỏ như sau:

  • react-router
  • react-router-dom
  • react-router-native

Chúng ta hầu như chẳng bao giờ cần phải cài đặt package react-router. Package này chứa những core functions/components của React Router, 2 thư viện còn lại được chỉ định dùng cho WebMobile App tương ứng với react-router-dom & react-router-native.

Ở đây chúng ta sẽ đi xây dựng một website nên chúng ta sẽ cần cài đặt package react-router-dom.

yarn add react-router-dom

Chọn loại Router

Khi bắt đầu một dự án mới, bạn thường phải chọn xem loại router nào mình sẽ sử dụng. Cho những dự án web, có các loại router là <BrowserRouter><HashRouter>. Với <BrowserRouter> chúng ta nên sử dụng nó khi chúng ta đã có phần code ở server để xử lý những requests động, còn <HashRouter> dùng cho những trang web tĩnh.

Nhưng thông thường, chúng ta luôn được khuyến khích sử dụng <BrowserRouter>, vì đa số dự án React thường đi kèm luôn với Node.JS để xử lý thêm nhiều vấn đề phức tạp. Nhưng nếu chúng ta có dự định chỉ cần trang web tĩnh là đủ thì <HashRouter> là giải pháp tốt hơn.

Cho những dự án sắp tới, ở đây chúng ta sẽ giả định là website sẽ implement luôn ở phía server nên sự lựa chọn ở đây là <BrowserRouter>.

Nói cho dài dòng, ngắn gọn thì quên thằng <HashRouter> đi, xài thằng <BrowserRouter> là được rồi, kiểu gì cũng cân được tất.

History

Mỗi router đều tạo một đối tượng (object) history, được dùng để kiểm soát vị trí hiện tại của ta (location) và sẽ re-render lại website mỗi khi có sự thay đổi. Những component được xây dựng bởi React Router sẽ dựa vào object history này để render. Bất kì component nào được xây dựng bởi React Router mà không được bọc bởi Router (<BrowserRouter> hoặc <HashRouter> ) sẽ không hoạt động.

Cách dể render một <Router>

Mặc định một Router (<BrowserRouter> hoặc <HashRouter>) chỉ chấp nhận 1 Component con duy nhất nên cách tốt nhất là bọc nó bên ngoài Component <App>. Ví dụ như với boilerplate create-react-app

Bên trong file src/index.js

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

),document.getElementById('root'));
registerServiceWorker();

Component <App>

Component <App> sẽ chứa toàn bộ nội dung website của chúng ta. Để cho đơn giản chúng ta sẽ chia nó thành 2 phần. <Header><Main>.

  • <Header> component sẽ chứa links để liên kết và luân chuyển giữa các page trong website.
  • <Main> component chủ yếu để hiển thị nội dung xuyên suốt giữa các page.

Ghi chú: Các bạn thích định nghĩa kiểu gì thì định nghĩa nhưng ở đây làm đơn giản nhất có thể để các bạn dễ hình dung.

import React, { Component } from 'react';
import Header from './Header/Header'
import Main from './Main/Main'
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<Header />
<Main />

</div>
);
}
}
export default App;

<Route> Component

Component <Route>là thành phần chính của React Router. Để render một cái component theo pathname của location chúng ta sẽ phải sử dụng component <Route>.

Path

Path hay còn lại là đường dẫn, mỗi một component <Route> đều yêu cầu prop path. Nó là một cái chuỗi dùng để định nghĩa đường dẫn mà component đó trỏ đến.

Ví dụ:

<Route path='/trang-chu'/>
// Khi pathname là '/', thì component bên trong Route trên không truy cập được.
// Khi pathname là '/trang-chu' hoặc '/trang-chu/abc', thì truy cập được.
// Nếu ta muốn đường dẫn trên trỏ chính xác đến pathname '/trang-chu' thì thêm prop extact vào, lúc này truy cập vào `/trang-chu/abc' thì component bên trong Route dưới không truy cập được.
<Route exact path='/trang-chu'/>

Ghi chú: Mặc cho ta sử dụng thuộc tính exact để trỏ chính xác đến một đường dẫn nhất định thì nó vẫn chỉ lấy cái pathname của đường dẫn đấy thôi, đống params ở đằng sau nó không quan tâm.

Ví dụ

http://www.example.com/trang-chu/abc?xyz=false

Thì nó chỉ check đúng phần sau /trang-chu/abc.

Hiểu về việc trỏ đúng path

React Router sử dụng package path-to-regexp để định nghĩa nếu có một Route trỏ đúng với đường dẫn hiện tại trên trình duyệt. Nó compile cái chuỗi bên trong prop path ra regular expression.

Chi tiết về path-to-regexp xem ở đây:

https://github.com/pillarjs/path-to-regexp

Khi một đường dẫn của route được trỏ chính xác đến địa chỉ bên trong trình duyệt, một cái object tên là match sẽ được tạo ra, bao gồm những thuộc tính sau:

  • url — phần giống nhau giữa path và đường dẫn hiện tại trên trình duyệt.
  • path — phần path đã được định nghĩa trong route.
  • isExact — kiểm tra xem có khớp không nếu gắn thêm prop exact vào.
  • params — một cái object chứa những giá trị từ pathname được bắt bởi thư viện path-to-regexp.

Có thể dùng tool bên dưới để kiểm tra xem Route mình khởi tạo có khớp với URL mà mình định trỏ tới hay không:

https://pshrmn.github.io/route-tester/#/

Tạo những component route đầu tiên

<Route>có thể được tạo ở bất cứ đâu, nhưng thường chúng ta sẽ để nó chung ở một nơi cho dễ quản lý. Ta có thể dùng component<Switch> để nhóm<Route> lại. Component<Switch> sẽ tạo vòng lặp, lặp qua những elementchildren của nó(đám routes) và chỉ render cái Route nào đúng với đường dẫn hiện tại.

Ở ví dụ lần này cúng ta sẽ tạo ra những Route sau:

  1. / — trang chủ
  2. /user— trang user
  3. /user/:id— thông tin chi tiết của từng user
  4. /about— trang thông tin
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/user' component={User}/>
<Route path='/about' component={About}/>
</Switch>

Vậy component <Route> sẽ render cái gì ?

Routes sẽ có 3 props để định nghĩa việc chúng ta sẽ render cái gì, và chỉ duy nhất 1 prop path được phép truyền vào <Route> component.

Dưới đây là danh sách 3 props

  1. component — Truyền vào React Component. Khi đường dẫn trên trình duyệt đúng với prop path nó sẽ render ra Component này.
  2. render —Truyền vào một function, function này sẽ trả về React Component. Nó cũng sẽ được gọi khi trỏ đúng đường dẫn. Cái này tương tự như prop nhưng tiện hơn khi cần render một cái component theo dạng inline. Hoặc khi cần pass thêm những props khác.
  3. children — Cũng là một function trả về một React Component. Nhưng không giống 2 cái trên, Component bên trong props này sẽ luôn luôn được render, mặc cho có đúng đường dẫn dẫn hay không.

Ví dụ:

<Route path='/page' component={Page} />const extraProps = { color: 'red' }
<Route path='/page' render={(props) => (
<Page {...props} data={extraProps}/>
)}/>
<Route path='/page' children={(props) => (
props.match
? <Page {...props}/>
: <EmptyPage {...props}/>
)}/>

Thường thì, cả componentrender prop nên được sử dụng. Còn propchildren thỉnh thoảng cũng, nên thường sẽ khuyến khích sử dụng khi cần nhét thêm câu lệnh điều kiện để render ra trang trống như ví dụ ở trên.

<Main>

Giờ chúng ta đã hình dung được cấu trúc gốc, vậy chúng ta chỉ cần render nó thôi. Cho ví dụ lần này chúng ta sẽ render component <Switch><Route> bên trong component <Main> của chúng ta, nó sẽ thay thế toàn bộ HTML bên trong thẻ <main> mỗi khi đường dẫn khớp với một Component thích hợp.

import { Switch, Route } from 'react-router-dom'const Main = () => (
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/user' component={User}/>
<Route path='/about' component={About}/>
</Switch>
</main>
)

Ghi chú: Route cho trang chủ có thêm propexact. Tính năng của nó thì đã được nói ở trên, việc này nhằm tránh khi ta gõ /user /about /blahblahblah thì nó sẽ không gây ra conflict.

Route lồng nhau

Route hiển thị chi tiết thông user như đã nói ở trên/user/:idsẽ không được include vào component<Switch> ở component <Main>. Thay vào đó nó sẽ được render bởi<User> component, có nghĩa là nó sẽ render bất kì lúc nào khi mà đường dẫn bắt đầu bằng/user.

Bên trong component<User> chúng ta sẽ render 2 phần:

  1. /user— Cái này chỉ hiển thị khi đường dẫn khớp hoàn toàn với đường dẫn/user, vì vậy nên chúng ta sẽ pass propexact vào chỗ này.
  2. /user/:id— Route này sẽ bắt những params đằng sau đường dẫn /user.
const User = () => (
<Switch>
<Route exact path='/user' component={UserList}/>
<Route path='/user/:id' component={UserProfile}/>
</Switch>
)

Khá là hữu dụng khi chúng ta cần nhóm một đám route mà thường share nhau điểm chung gì đó. Việc này giúp chúng ta tiết kiệm thời gian và tránh việc duplicate code khi làm việc với những route giống nhau.

Ví dụ, <User> có thể hiển thị thêm cái title xuyên suốt những đường dẫn nào bắt đầu với/user.

const User= () => (
<div>
<h2>Welcome to User Page !!!!</h2>
<Switch>
<Route exact path='/user' component={UserList}/>
<Route path='/user/:id' component={UserProfile}/>
</Switch>
</div>
)

Path Params

Thỉnh thoảng sẽ có những cái biến bên trong một đường dẫn mà ta muốn bắt. Ví dụ ở đây ta có trang User và ID của từng user đó và để bắt trúng từng thông tin của user thông qua id chúng ta có thể thêm params vào cái chuỗi mà ta truyền vào prop path.

Đoạn :idtrong /user/:idsẽ được bắt mà lưu trữ bên trongmatch.params.id. Ví dụ ta có đường dẫn như sau/user/1 sẽ sinh ra object params như sau.

console.log(match.params);
---> { id: '1' } // giá trị bắt vào sẽ là một chuỗi (string)

Component<UserProfile> có thể sử dụng objectprops.match.params để xác định xem user nào sẽ được hiện lên

// an API that returns a player object
import UserAPI from './UserAPI'
const UserProfile = (props) => {
const userProfile = UserAPI.get(
parseInt(props.match.params.id, 10) // chỗ này là chuỗi nên cần lại Convert lại thành số cho đúng để gửi http request.
)
if (!userProfile) {
return <div>User not found !</div>
}
return (
<div>
<h1>{user.name} (#{user.id})</h1>
<h2>{user.email}</h2>
</div>
)

Cùng với Component <UserProfile> chúng ta còn có<UserList>, <About>, và<Home>.

const UserList= () => (
<div>
<ul>
{
UserAPI.getAll().map(u => (
<li key={u.id}>
<Link to={`/user/${u.id}`}>{u.name}</Link>
</li>
))
}
</ul>
</div>
)
const About= () => (
<div>
<h1>Welcome to About Page</h1>
</div>
)
const Home = () => (
<div>
<h1>Welcome to the Homepage !!!</h1>
</div>
)

Links

Cuối cùng, trong ứng dụng của chúng ta, chúng ta cần một cách để “di chuyển” giữa các page. Nếu chúng ta tạo ra một cái link theo cách thông thường <a href="/user" /> sẽ gây ra hiện tượng cả page đều load lại. Vậy là đi ngược lại với tinh thần xây dựng một cái Single Page Application. (nhưng thỉnh thoảng cũng sẽ gặp trường cần cần reload lại page). Và để giải quyết vấn đề đó, React Router cung cấp component <Link> để ngăn chuyện trên xảy ra. Khi nhấp vào component <Link>, page sẽ được chuyển tới trang cần thiết mà không cần phải load lại.

import { Link } from 'react-router-dom'const Header = () => (
<header>
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/user'>User</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
)

<Link>sử dụng prop to để định nghĩa việc click vào nó sẽ dẫn tới đâu, nó có thể là một chuỗi hoặc là object location (bao gồm pathname, search, hash, và state). Khi truyền vào một chuỗi, nó sẽ tự convert thành object location.

<Link to={{ pathname: '/user/1' }}>User #1</Link><Link to="/user/2">User #2</Link>

Chốt

Ok, tới đây thì chắc ai cũng hiểu và nhúng được React Router vào project của riêng mình rồi.

Good luck.

Next lesson: REEDUXXX.

    Khải Phan

    Written by

    Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
    Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
    Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade