Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu về React Hooks và các hooks thường dùng trong ReactJS.
Khi làm việc với các React Component chúng ta cần phải thao tác rất nhiều với state, props hay life cycle. Và kể từ phiên bản 16.8 trở đi React cung cấp một chức năng mới đó là React Hooks, chức năng này cho phép thay thế việc sử dụng state thông thường bằng các khái niệm mới như useState, useEffect..
1. Giới thiệu về React Hooks
Vậy thì React Hooks thực sự nó là gì!?
Chúng ta có thể hiểu React Hooks là một chức năng được xây dựng trong React cho phép chúng ta có thể sử dụng state và life cycle bên trong một functional components.
Hooks đem lại một vài lợi ích khi làm việc như :
- Cải thiện hiệu suất làm việc bằng cách có thể tái sử dụng code.
- Các thành phần được trình bày khoa học hơn.
- Sử dụng một cách linh hoạt trong component tree.
React Hooks đem lại cho functional components các tính năng cần thiết của component, nó có thể thay thế gần như hoàn toàn việc sử dụng class components. Ở đây mình có ví dụ để chứng minh điều đó.
Ở ví dụ này, mình xây dụng một component counter, có state là một number, có thể onClick để tăng hoặc giảm giá trị của state, và đây là cách viết bằng class thông thường.
import React, { Component } from 'react'
export default class Counter extends Component {
constructor(props) {
super(props)
//Khởi tạo state
this.state = {
count: 0
}
}
increment() {
this.setState({ count: this.state.count + 1 })
}
decrement() {
this.setState({ count: this.state.count - 1 })
}
render() {
return (
<h1>{this.state.count}</h1>
<button onClick={() => this.increment()}>Increment!</button>
<button onClick={() => this.decrement()}>Decrement!</button>
)
}
}
Nhưng khi chúng ta sử dụng React Hooks (cụ thể ở ví dụ bên dưới là sử dụng hook useState) sẽ nhanh hơn rất nhiều.
import React, { useState } from "react"
export default function Counter() {
const [count, setCount] = useState(0)
return (
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Increment!</button>
<button onClick={() => setCount(count - 1)}>Decrement!</button>
)
}
Hai cách viết trên đều có một chức năng giống nhau nhưng khi chúng ta sử dụng React Hooks sẽ giúp giảm các đoạn mã và tài nguyên.
2. Các hooks thường dùng trong ReactJS
useState
Việc sử dụng useState()
cho phép chúng ta có thể làm việc với state bên trong functional component
mà không cần chuyển nó về class component
. Ở ví dụ bên trên mình cũng đã sử dụng useState() để cập nhật state.
useEffect
useEffect()
là function nắm bắt tất cả các sự thay đổi của code. Nó giúp chúng ta xử lý các side effects, useEffect sẽ tương đương với các hàm componentDidMount, componentDidUpdate và componentWillUnMount trong LifeCycle. Ví dụ:
import React, { useEffect } from 'react'
export default function Something {
useEffect(() => {
fetch(...) //call api
}, [])
return (...)
}
Ta có thể thấy, callback trong useEffect hoạt đống giống như ComponentDidMount khi sử dụng class. Để tránh việc hàm useEffect luôn chạy vào mỗi khi có thay đổi State thì ta có thể truyền vào tham số thứ 2 trong useEffect đó là 1 array, trong array này ta có thể truyền vào đó những giá trị mà useEffect sẽ subcribe nó, tức là chỉ khi nào những giá trị đó thay đổi thì hàm useEffect mới được thực thi. Hoặc bạn cũng có thể truyền vào 1 array rỗng thì khi đó nó sẽ chỉ chạy 1 lần đầu tiên sau khi render giống với hàm ComponentDidMount.
useEffect(() => {
console.log(counter)
}, [counter])
Ngoài ra, useEffect còn có thẻ return một cleanup function
tương đương với componentWillUnMount, và đây là một trong những trường hợp sử dụng cleanup function
.
useEffect(() => {
const handler = () => {...}
window.addEventListener('scroll', handler)
return () => {
window.removeEventListener('scroll', handler)
}
}, [])
useContext
useContext là một hooks trong React Hooks cho phép chúng ta có thể làm việc với React Context trong một functional component, đây là cách khởi tạo một context.
const AppContext = React.createContext({ foo: 'bar' })
và đây là cách lấy giá trị từ context thông qua useContext
const value = useContext(AppContext)
useMemo
useMemo giúp chúng ta hạn chế việc thực hiện những tính toán phức tạp mỗi lần component re-render. Bằng cách truyền dependencies ở đối số thứ 2, chỉ khi giá trị một trong các dependencies thay đổi thì việc tính toán mới thực hiện lại.
import { useMemo } from 'react'
const learningProgress = ({ position, lecturesCount }) => {
//Phép tính sẽ luôn được thực thi khi conponent re-render
const progress = Math.trunc(Number(position) / lecturesCount * 100)
//Phép tính chỉ thực thi lại khi position hoặc lecturesCount thay đổi
const progress = useMemo(() => Math.trunc(Number(position) / lecturesCount * 100), [position, lecturesCount])
return (...)
}
useCallback
Tương tự như useMemo, nhưng useCallback có tác dụng là ngăn chặn việc một hàm bị khởi tạo lại mỗi lần re-render, useCallback sẽ sử dụng cùng với React.memo để tránh việc re-render không cần thiết.
import { useState } from 'react'
import Child from './child'
const App() {
const [count, setCount] = useState(0)
const handleSetValue = (text) => {
console.log(text)
}
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child handleSetValue={handleSetValue} />
</div>
)
}
và đây là child component với React.memo
import { memo, useState } from 'react'
const Child = ({ handleSetValue }) => {
const [text, setText] = useState('')
console.log('re-render')
return (
<div>
<input type='text' onChange={(e) => setText(e.target.value)} />
<button onClick={() => handleSetValue(text)}>Click</button>
</div>
)
}
export default memo(Child)
Với React.memo, ta mong muốn khi component App
re-render, cụ thể là setCount
thì component Child
sẽ không re-render. Nhưng không, khi App
re-render thì hàm handleSetValue
cũng được khởi tạo lại với phạm vi mới, nên Child
vẫn sẽ re-render. Cách khắc phục là sử dụng useCallback.
import { useState, useCallback } from 'react'
import Child from './child'
const App() {
const [count, setCount] = useState(0)
const handleSetValue = useCallback((text) => {
console.log(text)
}, [])
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child handleSetValue={handleSetValue} />
</div>
)
}
Và bây giờ các bạn đã có kết quả như mong muốn.
useReducer
useReducer được sử dụng để xử lý các state phức tạp và việc chia sẻ state giữa các component, flow làm việc của useReducer cũng giống với redux.
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
throw new Error()
}
}
function Counter() {
const [count, dispatch] = useReducer(reducer, 0)
return (
<>
<h1>{count}</h1>
<button onClick={() => dispatch({type: 'DECREMENT'})}>Decrement</button>
<button onClick={() => dispatch({type: 'INCREMENT'})}>Increment</button>
</>
)
}
Tạm kết
Đến đây các bạn đã biết được được công dụng và những điểm mạnh tuyệt vời của react hooks đúng không nào <3. Ngoài những hooks
mình đã giới thiệu ở trên, còn nhiều hooks
khác như: useLayoutEffect, useImperativeHandle,... . Mình sẽ giới thiệu ở những bài viết sau về chi tiết các chức năng của từng hooks
. Các bạn cũng có thể tìm hiểu cụ thể hơn tại đây.