Tìm hiểu về React Hooks

React đã giới thiệu một tính năng mới cho phép bạn sử dụng state và các tính năng khác của React mà không cần phải viết class (functional component) , Hooks - một đề xuất mới trong React 16.8.0, Ý tưởng này hứa hẹn sẽ thay đổi khá nhiều bộ mặt của React, khiến các components trở nên gọn nhẹ hơn, giảm đáng kể số lượng code.

React Hooks

Trong bài viết này, mình sẽ tập trung vào 3 Hooks cơ bản trong React:

useState
useEffect
useContext

Ngoài ra còn các Hooks phức tạp như:

useReducer
useCallback
useMemo
useRef
useImperativeMethods
useLayoutEffect

Trong React, việc sử dụng higher-order components và render props tương đối phổ biến khi bạn cần chia sẻ logic giữa các components với nhau. Chẳng hạn, ta dùng kỹ thuật render props trong React Context: Context.Consumer sử dụng nhận giá trị từ Context.Provider. Nếu bạn không truyền state mới cho Context.Provider thì giá trị mặc định khi gọi React.createContext() sẽ được sử dụng. Chẳng may đến một ngày, ta sẽ gawpr phải “wrapper hell” như thế này:

Để khắc phục vấn đề đó React đã đưa ra giải pháp đó là Hooks.

React Hooks là gì?

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

Hooks là những hàm cho phép bạn “kết nối” React state và lifecycle vào các components sử dụng hàm. Với Hooks bạn có thể sử dụng state và lifecycles mà không cần dùng ES6 class. ( ES6 class thật ra nhiều lúc không thân thiện lắm, nhất là về khoản: "What this is this?")

Các hooks cơ bản:

useState Bây giờ, bạn có thể thêm state cho một functional component. Chúng ta không cần phải viết một lớp riêng cho việc đó.
Chúng ta hãy tạo một component đơn giản không có state và áp dụng Hook useState:

export const Counter = () => {
   const [count, setCount] = useState(0);
   
   return (
       <div>
           <p>Counter value: </p>
           <button onClick={() => {setCount(count + 1)}}>Increase counter</button>
           <button onClick={() => {setCount(count -1)}}>Decrease counter</button>
       </div>
   )
}

Đó là chức năng Counter đơn giản. Bộ đếmsẽ tăng và giảm giá trị nếu người dùng nhấn kích hoạt sự kiện onClick.

useState Hook trả về một mảng. Phần tử đầu tiên của mảng là giá trị hiện tại của state, ở vd trên nó sẽ là 0. phần tử thứ hai của mảng là một hàm để cập nhật state.
Có thể gọi hàm cập nhật đấy ở bất cứ đâu trong component, nó tương thự như this.setState trong class.

Trong useState ta chỉ có một giá trị tham số duy nhất đó là giá trị mặc định của state đấy. Trong ví dụ bên trên nó mang giá trị tham số bằng 0. Khác với this.state mang kiểu dữ liệu là một object thì state này có thể là bất cứ kiểu dữ liệu nào mà bạn muốn. Đây là một ví dụ để dùng nhiều hơn là một state trong một component:

const [name, setName] = useState('TTB');
const [info, setInfo] = useState([{ name: 'TTB', age: '18'}]);

useEffect Để tiếp cận vào các lifecycles, chúng ta có useEffect. Nó cho phép thực hiện các tác động vào bên trong component, để dễ hiểu nó sẽ thay thế cho các lifecycle componentDidMount, componentDidUpdatecomponentWillUnmount trong React Class được gom lại thành một hàm duy nhất.

Tiếp tục với VD ở trên ta thêm chức năng checkbox:

const [count, setCount] = useState(0);
const [checked, changeCheckbox] = useState(true)
useEffect(() => {
   console.log('hello from useEffect')
})

Hiện tại, văn bản 'hello from useEffect' sẽ hiển thị mỗi khi chúng ta thay đổi giá trị checked của checkbox (nó sẽ được gọi mỗi khi chức năng thay đổi DOM gồm cả lần render Dom đầu tiên).

Effect được khai báo ngay bên trong component và có quyền truy cập được các state và props

Sức mạnh thực sự của useEffect là chúng ta có thể sử dụng một đối số tùy chọn thứ hai, đó là một mảng. Sau đó, chúng ta có thể chỉ định rằng chúng ta chỉ muốn gọi Effect này trong tình huống khi chúng ta thay đổi giá trị của đối số đó.

useEffect(() => {
   console.log('hello from useEffect')
}, [count])

Bây giờ, useEffect sẽ chỉ được gọi trong trường hợp trạng thái của số đếm count sẽ thay đổi . Thật tuyệt phải không?

useContext

Hãy tưởng tượng, vấn đề ở đây là ta có thông tin người dùng như tên, họ, ... từ file index.js được truyền lại dưới dạng prop cho các component. Chúng ta sẽ tạo hai thành phần function components là: HeaderLoginInfo. Các thành phần sẽ chỉ hiển thị giá trị được truyền dưới dạng prop từ Dashboard.

Dashboard không sử dụng info, nhưng phải truyền xuống Header và truyền xuống LoginInfo để hiển thị info người dùng. Cách tiếp cận này là ổn, nhưng chúng tôi phải truyền info thông qua một loạt các thành phần (trong trường hợp này component Dashboard còn không quan tâm sử dụng đến nó).

Một cách để làm cho nó trông tốt hơn là sử dụng createContext, để tạo một new context và trả về giá trị context hiện tại, như được cung cấp bởi context provider gần nhất cho context cụ thể.
Vì vậy, hãy tạo thêm và xuất đối tượng Context :

import React from 'react'
const Context = React.createContext()
export default Context

Trong file index, chúng ta import file khởi tạo Context và bao bọc toàn bộ thành phần dom của componen bằng <Context.Provider> và truyền infongười dùng từ state như truyền một prop. Bây giờ, chúng ta có quyền truy cập vào info người dùng trong Context consumers từ state mà chúng ta không cần phải truyền nó qua các component dưới dạng prop.

// file index.js
import React, { Component } from 'react';
import './App.css';
import { Dashboard } from "./Dashboard";
import Context from './Context'

class App extends Component {
   state = {
       user: 'TTB'
   }
   render() {
       const {user} = this.state
       return (
           <Context.Provider value={user}>
               <Dashboard />
           </Context.Provider>
       );
   }
}
export default App;

Ngay bây giờ, chúng ta sử dụng useContext Hook và wrap Context, Ở đây value là value được truyền từ Provider (trong file index). Gán nó cho biến user:

import React, { useContext } from 'react'
import LoginInfo from './LoginInfo'
import Context from './Context'

const Header = () => {
    const user = useContext(Context)
    return (
        <div>
            <h1>Welcome {user}</h1>
            <LoginInfo />
        </div>
    )
}

export default Header

Tương tự cho LoginInfo. Chúng tôi khai báo một giá trị user bằng cách sử dụng useContext Hook và giá trị được truyền từ Provider (trong file index)

import React, { useContext } from 'react'
import Context from './Context'

const LoginInfo = () => {
    const user = useContext(Context)
    return (
        <h6>Logged as {user}</h6>
    )
}

export default LoginInfo

Trong LoginInfo và Header bây giờ chúng ta có đc info của user .

Một số hạn chế Hooks.

Mặc dù trông rất đẹp, nhưng Hooks chỉ có thể được gọi từ các function component, Hooks nên được gọi ở cấp cao nhất, không nên gọi trong các vòng lặp, điều kiện hoặc các hàm lồng nhau.