+5

useHistory trong react-router-dom

react-router-dom là thư viện giúp cho việc điều hướng url tới các component (nôm na là như vậy) trong react js

Link

Trong thư viện này useHisrtory được sử dụng để thay đổi url, ví dụ như khi bạn đang ở mà hình login chẳng hạn, \users\login, sau khi đã authen xong thì cần redirect tới màn dashboard, ta dùng

const history = useHistory();

history.push('\dashboard`);

Vậy 2 dòng code trên đã được react-router-dom xử lý ra sao?

Để dùng react-router-dom ta thường dùng wrapper như sau (có thể có những cách custom khác nhau nhưng về cơ bản tương tự)

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";

const SomeFunctionComponent = () => {
  ...
  <Router> 
    ...
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      <Route path="/about">
        <About />
      </Route>
      <Route path="/dashboard">
        <Dashboard />
      </Route>
    </Switch>
    ...
  <Router>
  ...
}

Các componenrt được wrap bởi provider là Router (thực ra nó là BrowserRouter), hãy thử thay đổi 1 chút trên phần import, chuyển BrowserRouter as Router thành Router

import { Router, Switch, Route, Link } from "react-router-dom";

Ta sẽ thấy ngay errors là:

TypeError
props.history is undefined
...
Router
https://bnpsd.csb.app/node_modules/react-router/esm/react-router.js:166:7
...

Muốn sử dụng Router của react-router-dom ta phải truyền vào nó history (trường này dc required), đây chính là prop để dùng cho useHistory mà chúng ta đang nói đến.

Vậy BrowserRouter khác Router ở điểm gì?, thực chất là nó chỉ wrap Router và pass history vào Router mà thôi.

...
import { createBrowserHistory as createHistory } from "history";
...

class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}

...

Đây là đoạn code của BrowserRouter, vậy muốn dùng Router giống như BrowserRouter ta chỉ cần viết như sau

import { createBrowserHistory } from "history";

  ...
  <Router history={createBrowserHistory()}>
  ...
  </Router>
  ...

history ở đây được tạo ra bởi createBrowserHistory của history lib, còn đây là về history lib:

The history library lets you easily manage session history anywhere JavaScript runs. A history object abstracts away the differences in various environments and provides a minimal API that lets you manage the history stack, navigate, and persist state between sessions.

Mình để nguyên tiếng Anh cho nó sát nghĩa 😃

Link: https://github.com/ReactTraining/history

Link hàm createBrowserHistory của history: https://github.com/ReactTraining/history/blob/master/packages/history/index.ts#L397

trong method này, hàm push thực chất là gọi pushState của window.histoy, các bạn có thể inspect console trong browser rồi gõ history là ra

History
​
length: 1
​
scrollRestoration: "auto"
​
state: null
​
<prototype>: HistoryPrototype { go: go(), back: back(), forward: forward(), … }

còn hàm push thì như sau:

function push(to: To, state?: State) {
    let nextAction = Action.Push;
    let nextLocation = getNextLocation(to, state);
    function retry() {
      push(to, state);
    }

    if (allowTx(nextAction, nextLocation, retry)) {
      let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);

      // TODO: Support forced reloading
      // try...catch because iOS limits us to 100 pushState calls :/
      try {
        globalHistory.pushState(historyState, '', url);
      } catch (error) {
        // They are going to lose state here, but there is no real
        // way to warn them about it since the page will refresh...
        window.location.assign(url);
      }

      applyTx(nextAction);
    }
  }

còn globalHistory thì chính là window.history;

Hàm pushState của window.history làm thay đổi url nhưng không tạo ra 1 HTTP request, nó khác với việc khi bạn dùng window.location.href = ...

Cuối cùng là làm sao khi gọi

const history = useHistory();

thì lấy được history của wrap Router bên ngoài, thực ra cái này các lib khi muốn truyền những tham số vào child component đều sử dụng phương pháp là dùng context, thực chất useHistory() tạo ra 1 component có contextType là Router để lấy context từ Router Provider

const useContext = React.useContext;

export function useHistory() {
  if (__DEV__) {
    invariant(
      typeof useContext === "function",
      "You must use React >= 16.8 in order to use useHistory()"
    );
  }

  return useContext(HistoryContext);
}

Tóm lại useHistory() được cấu trúc như sau để sử dụng:

  • Provider <Router> với history được tạo sẵn với hàm push sử dụng pushState của window.history
  • useHistory() tạo ra 1 component với contextType là Router để lấy history của wraper component (Router) và trả về để thực thi các method createHref, push, replace, ...

Cảm ơn và hi vọng bài viết có ích trong công việc của bạn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí