Categories
Tech

React JS: Tìm hiểu về các hooks trong React Hooks

Đối với ai đã từng làm việc với React thì chắc hẳn đã có những lúc cảm thấy bối rối không biết nên dùng stateless (functional) component hay là stateful component. Nếu có dùng stateful component thì cũng sẽ phải loay hoay với đống LifeCycle 1 cách khổ sở. Rất may là những nhà phát triển React đã kịp nhận ra vấn đề này và họ đã cho ra mắt 1 tính năng mới tên gọi là React hooks.

1. Giới thiệu về React Hooks

Vậy thì React Hooks thực sự nó là gì!?

Hooks chính thức được giới thiệu trong phiên bản React 16.8. Nó cho phép chúng ta sử dụng state và các tính năng khác của React mà không phải dùng đến Class

Điều này có nghĩa là từ phiên bản 16.8 trở đi, chúng ta đã có thể sử dụng state trong stateless (functional) component, việc mà từ trước tới nay ta bắt buộc phải khai báo Class. Có thể thấy, các nhà phát triển React họ đang muốn hướng đến 1 tương lai Functional Programming thay vì sử dụng những Class mà chỉ nghe cái tên thôi là ta đã nghĩ ngay đến OOP. Cộng với việc không sử dụng Class kế thừa từ React Component nữa nên giờ đây kích thước bundle sẽ được giảm đáng kể bởi code sử dụng Hooks.

Để có thể thay thế được Class thì React Hooks cung cấp cho chúng ta một bộ các built-in Hooks, giúp chúng ta sử dụng được các thành phần tạo nên React, có 2 loại built-in đó là: Basic Hooks và Additional Hooks.

Trước khi chúng ta tiếp tục, lưu ý rằng Hook:

  • Hoàn toàn không bắt buộc. Bạn có thể dùng Hook trong một vài component mà không phải viết lại bất cứ đoạn code hiện tại nào. Bạn không buộc phải học hoặc sử dụng Hook bây giờ nếu bạn không muốn.
  • 100% tương thích phiên bản cũ. Hook không chứa bất kỳ thay đổi nào ảnh hưởng phiên bản trước đó.
  • Đã chính thức được công bố. Hook đã sẵn sàng với phiên bản v16.8.0.

Không có kế hoạch xóa class component khỏi React.

Hooks không thay đổi kiến thức của bạn về các khái niệm của React. Thay vì thế, Hook cung cấp các API trực tiếp tới các khái niệm React mà bạn đã biết: prop, state, context, refs, và lifecycle. Chúng tôi sẽ chỉ ra sau, Hook cũng đưa ra 1 cách mới, mạnh mẽ hơn để kết hợp với chúng.

2. Các loại Hooks của React:

Basic Hooks: useState, useEffect, useContext.

Additional Hooks: useReducer, useCallback, useMemo, useRef, useImperativeHanlde, useLayoutEffect, useDebugValue.

2.1  Basic Hooks:

useState

Hàm này nhận đầu vào là giá trị khởi tạo của 1 state và trả ra 1 mảng gồm có 2 phần tử, phần tử đầu tiên là state hiện tại, phần tử thứ 2 là 1 function dùng để update state (giống như hàm setState cũ vậy). Ví dụ:

Ngày trước dùng Class Components thì viết như này:

constructor(props) {
    super(props);
    this.state = { count: 0 }
 }

 const setCount = (number) => {
     this.setState({
         count: number,
     });
 }

Bây giờ thì sử dụng như vậy:

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(0)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

Khi muốn update state cho count1 thì chỉ cần gọi đến hàm setCount(number) .
Nếu như đang làm việc với React-Redux để quản lý State thì chỉ nên sử dụng useState để quản lý các UI State (là những state có giá trị boolean nhằm mục đích render ra UI) để tránh việc conflict với cả Redux State và maintain sau này.

Demo: useState

useEffect

#syntax useEffect(effectFunction, arrayDependencies)

Như đã giới thiệu trong phần mở đầu về sự phức tạp trong các hàm LifeCycle thì để thay thế nó chúng ta sẽ có hàm useEffect. Nó giúp chúng ta xử lý các side effects, useEffect sẽ tương tự với các hàm componentDidMountcomponentDidUpdate và componentWillUnMount trong LifeCycle khi khai báo arrayDependencies[]

Vậy cú pháp thì như vậy ! Thực tế nó sẽ tương đương như thế nào so các lifecycle trong class component

  • componentDidUpdate:

Khi chúng ta muốn dùng componentDidUpdate trong function component chúng ta sẽ dùng như sau: useEffect(function)

() => {
  const [count, setCount] = useState(0);
  const [something, setSomething] = useState(false);
  const handleClick = () => setCount(count + 1);
 
  useEffect(() => {
    console.log('Watch Here');
    document.title = 'Count is: ' + count;
  })

  return <div>
    <p>Title page will be change when we click the button</p>
    <button onClick={handleClick}>Increment Count</button>
    <button onClick={() => setSomething(!something)}>Change something </button>
  </div>
}

NOTE:
Như trong ví dụ trên thì thấy hàm `useEffect`  luôn được gọi mỗi khi component được render xong.
Vậy arrayDependencies thì sao? Nếu ta không truyền bất kì biến nào vào arrayDependencies thì bất kể state nào thay đổi cũng bị React chạy lại Hook và dưới console mọi người sẽ thấy Watch Here
Demo

  • componentDidMount:

Ở trên em có nói tới đối số thứ 2 là arrayDependencies khi thay đổi thì đối số thứ nhất mới thực thi (function) vậy nếu arrayDependencies không thay đổi tức ta gán bằng [] thì điều này có nghĩa nó tương tự với componentDidMount vì khi này function trong useEffect chỉ gọi 1 lần.

  • componentWillUnMount:

Và một lifecycle là componentWillUnMount thì thực tế khi sử dụng useEffect nó sẽ như thế nào?

Thì như mọi người đã biết thì componentWillUnMount chạy mỗi khi một component chuẩn bị remove khỏi tree dom. Do đó mình chỉ cần return 1 function.

useEffect cho phép chúng ta return 1 function, function này sẽ thực thi trước khi mà component đó được unmounted.

const MouseTracker = () => {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
  const onMouseMove = ({ clientX, clientY }) => {
    setMousePosition({ x: clientX, y: clientY });
  }
  useEffect(() => {
    document.addEventListener("mousemove", onMouseMove);
    return () => {
      document.removeEventListener("mousemove", onMouseMove);
    };
  }, []);

  return (
    <pre
      style={{
        position: "absolute",
        width: 100,
        height: 100,
        borderRadius: "50%",
        background: "skyblue",
        top: mousePosition.y + 30,
        left: mousePosition.x + 30
      }}
    >
      {JSON.stringify(mousePosition, null, 2)}
    </pre>
  );
};

Giải thích:
Nhìn component trên ta sẽ hiểu. Sau khi load hết DOM thì React sẽ add 1 event di chuyển chuột. Event này sẽ gọi tới 1 function là set position. Khi component này bị remove khỏi cây DOM thì sẽ remove event di chuyển chuột đi.
Do em set arrayDependencies là []. Nên effect document.addEventListener("mousemove", onMouseMove); chỉ chạy 1 lần.

Tóm lại: nếu set 1 biến state trong arrayDependencies thì React sẽ chạy lại Hook này khi biến state đó thay đổi. Nếu chỉ muốn chạy 1 lần thì set []
Link Demo: useEffect

useContext

React context API là một cách cơ bản để tạo các biến toàn cục có thể được truyền qua trong ứng dụng React. đây là phương pháp thay thế cho “prop drilling”, hoặc truyền props từ ông nội sang cha và sang con, v..v.. Context thường được coi là đơn giản hơn, nhẹ nhàng hơn thay vì sử dụng Redux cho quản lý state.

Tạo Context, và gọi nó là ExampleContext. Điều này sẽ cung cấp ExampleContext.Provider. Những gì thành phần này làm đơn giản là :

  • Provider : thành phần cung cấp giá trị
Providing Context

Provider luôn cần tồn tại như một trình bao bọc xung quanh phần tử cha, bất kể bên trong có các giá trị như nào.

Trong ví dụ trên, em khai báo ExampleContext=createContext("Initial Value");

Sau đó e sử dụng ExampleContext.Provider có prop value = {initData} bao lại các component con.
Sau khi gọi các component con bên trong provider này. Mình chỉ cần gọi const valueFromContext = useContext(ExampleContext);

Kết quả: value={initData} tự động được define ở các component con khi sử dụng useContext

Demo: useContext

2.2 Additional Hooks

useReducer

Thực tế khi sử dụng useState thì nó sẽ trả về 1 phiên bản đơn giản của useReducer, vậy nên chúng ta có thể coi useReducer như một phiên bản nâng cao hơn dùng để thay thế cho việc sử dụng useState. Nếu đã làm việc với React-Redux thì chắc hẳn sẽ dễ dàng nhận ra flow quen thuộc này. Giống như reducer trong Redux thì useReducer cũng nhận vào một reducer dạng (state, action) và trả ra một newState. Khi sử dụng chúng ta sẽ nhận được một cặp bao gồm current state và dispatch function. Ví dụ:

const initialState = {count: 0}

function Reducer(state, action) {
  const [count, setCount] = useState(0);
  switch (action.type) {
    case 'INCREMENT':
      return setCount( count + 1);
    case 'DECREMENT':
      return setCount( count - 1);
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <>
      <StyledLogo src={logo} count={count}/>
      <Count count={count} />
      <div style={{display: 'flex'}}>
        <button onClick={() => dispatch({type: 'DECREMENT'})}> - </button>
        <button onClick={() => dispatch({type: 'INCREMENT'})}> + </button>
      </div>
    </>
  )
}

useMemo

useMemo Hook cho phép bạn cache lại kết quả tính toán giữa các lần render của component bằng cách “ghi nhớ” lại giá trị của lần render trước.

useMemo giúp ta kiểm soát việc được render dư thừa của các component con, nó khá giống với hàm shouldComponentUpdate trong LifeCycle. Bằng cách truyền vào 1 tham số thứ 2 thì chỉ khi tham số này thay đổi thì useMemo mới được thực thi. Ví dụ:

Link Demo: useMemo

Ở ví dụ trên ta sẽ thấy khi on load dưới console sẽ chạy rendered again. Khi ta click button Hello console không chạy lại rendered again. Nhưng khi ta click Hola thì console log lại render  rendered again.

Nếu không sử dụng useMemo thì khi click 1 trong 2 button cũng đều render lại ChilComponent.

sử dụng useMemo thì ChildComponent sẽ được render lần đầu tiên và cache lại. ChilComponent sẽ render lại khi biến text thay đổi.

useCallback

useCallback là một Hook được giới thiệu trong React từ bản 16.8.

useCallback trả về một function và một array chứa các dependencies (những biến số được truyền vào từ bên ngoài mà function này phụ thuộc khi chạy).

useCallback sử dụng cơ chế memorization – ghi nhớ kết quả của một function vào trong memory và sẽ trả về function được ghi nhớ trong trường hợp các dependencies này không thay đổi.

useCallback có nhiệm vụ tương tự như useMemo nhưng khác ở chỗ function truyền vào useMemo bắt buộc phải ở trong quá trình render.

Link Demo: useCallback

Để ý ví dụ ở trên, chúng ta có ParentComponent và ChildComponent.

Nếu chúng ta không xài useCallback mà chỉ xài function chỗ loggingStatus

const loggingStatus = () => {
    console.log("Run from ChildComponent");
  };

Khi nhấn vào button ở ParentComponent, component này sẽ bị render lại do có sự thay đổi về state. Kéo theo đó, ChildComponent cũng bị render lại theo cho dù không có sự thay đổi gì (props truyền vào y nguyên).

useCallback được sinh ra để giải quyết vấn đề này. Nó sẽ ghi nhớ lại function được truyền vào và danh sách các dependencies. Lúc này, khi nhấn vào button, React sẽ render lại ParentComponent và so sánh xem props của ChildComponent có thay đổi không để render tiếp. Vì loggingStatus hoàn toàn không phụ thuộc vào dependency nào, nên giá trị của nó không thay đổi và ChildComponent sẽ không bị render lại.

Kết luận

Ngoài những hooks cơ bản hay được sử dụng ngoại trừ useContext ở trên thì vẫn còn 1 số hook khác như là useRefuseLayoutEffectuseDebugValueuseImperativeHandle mọi người có thể vào trang chủ của react hooks để tìm hiểu thêm nhé.

Để có thể sử dụng hooks một cách hiệu quả và tối ưu nhất thì mọi người hãy lưu ý rằng hạn chế sử dụng hooks trong các vòng lặp hay nested function bởi vì như vậy nó sẽ làm mất đi tính đúng đắn của hooks. Một điều nữa là  chỉ nên gọi hooks trong React component, đừng gọi hooks trong một function javascript thông thường.