Tự học ReactJS: Sử dụng useReducer

Có thể bạn đã thành thạo sử dụng useState trong React để quản lí component state. Thỉnh thoảng nếu bạn có nhiều thành phần trong một state, giống như bạn có một object bao gồm nhiều state cùng một lúc, ví dụ như:

const [state, setState] = useState({
 firstName: 'Thau',
 lastName: 'Nguyen',
 age: 30
})

Hay chúng ta thường dùng useReducer để quản lí một số state liên quan vơi nhau như khi fetching data từ API: status, data, error. Quản lí chung các state này như một object.

const [state, setState] = useState({
 status: 'idle',
 data: null,
 error: null
})

Đây là trường hợp mà useReducer có thể giúp bạn quản lí state này dễ hơn. Nếu bạn đã quen với Redux thì có thể bạn sẽ hiểu được ý tưởng rất nhanh, nhưng nếu chưa quen bạn cũng sẽ từ từ nắm được useReducer hoạt động như thế nào.

Sau đây chúng ta sẽ tìm hiểu kỹ hơn về useReducer. Về cơ bản, bạn sẽ sử dụng một object để lưu state. Nhưng ví dụ sau đây là một component Counter đơn giản, nó chỉ lưu state là một số đơn giản.

Sau đây là một ví dụ đơn giản khi sử dụng useReducer để quản lí name input.

function nameReducer(previousName, newName) {
  return newName
}

const initialNameValue = 'Joe'

function NameInput() {
  const [name, setName] = React.useReducer(nameReducer, initialNameValue)
  const handleChange = event => setName(event.target.value)
  return (
    <>
      <label>
        Name: <input defaultValue={name} onChange={handleChange} />
      </label>
      <div>You typed: {name}</div>
    </>
  )
}

Lưu ý rằng useReducer (nameReducer ở trên) được gọi với 2 tham số:

  1. state hiện tại
  2. tham số của action (setName) khi nó được gọi ( tham số của nó ở đây là event.target.value)

useReducer với Counter

Trong bài tập này chúng ta chỉ đơn giản sử dụng useReducer để quản lí count trong Counter component. Có thể bạn nghĩ trong trường hợp này chung ta đang dùng dao mổ trâu để giết gà, vì đã sử dụng useReducer cho một thứ quá đơn giản. Nhưng ý tưởng ở đây là để bạn hiểu được cách nó hoạt động như thế nào.

Bạn có thể tham khảo hai bài viết sau của Kent C.Dodds để hiểu hơn về useReducer:

Bạn có nên sử dụng useReducer?

Cài đặt useState với useReducer như thê nào?

So sánh useReducer vs. useState

import * as React from 'react'

function Counter({initialCount = 0, step = 1}) {
  // 🐨 thay thế React.useState bằng React.useReducer.
  // 💰 React.useReducer(countReducer, initialCount)
  const [count, setCount] = React.useState(initialCount)

  // 💰 Nhơ rằng
  //   Tham số đầu tiên là "state" - giá trị ban đầu của count
  // Tham số thứ hai là "newState" - giá trị truyền vào setCount
  const increment = () => setCount(count + step)
  return <button onClick={increment}>{count}</button>
}

function App() {
  return <Counter />
}

export default App

Đáp án:

import * as React from 'react'

const countReducer = (state, newState) => newState

function Counter({initialCount = 0, step = 1}) {
  const [count, setCount] = React.useReducer(countReducer, initialCount)
  const increment = () => setCount(count + step)
  return <button onClick={increment}>{count}</button>
}

function App() {
  return <Counter />
}

export default App

Truyền vào step cho action

Chúng ta thay đổi yêu cầu một xíu như sau nhé:

const [count, changeCount] = React.useReducer(countReducer, initialCount)
const increment = () => changeCount(step)

Bạn phải thay đổi countReducer để khi gọi changeCount nhận thêm tham số step. Đây là cách để bạn có thể truyền bất cứ tham số gì vào action.

Đáp án:

import * as React from 'react'

const countReducer = (count, change) => count + change

function Counter({initialCount = 0, step = 1}) {
  const [count, changeCount] = React.useReducer(countReducer, initialCount)
  const increment = () => changeCount(step)
  return <button onClick={increment}>{count}</button>
}

function Usage() {
  return <Counter />
}

export default Usage

Giả lập setState với một object

Chắc bạn đã biết hàm setState trong class component trước kia. Bạn sẽ truyền vào một object cho hàm setState, ví dụ như: setState({ count: 2 }). Bạn hãy sử dụng useReducer để thử cài đặt lại hàm setState trước đây nhé.

Yêu cầu code như sau:

const [state, setState] = React.useReducer(countReducer, {
  count: initialCount,
})
const {count} = state
const increment = () => setState({count: count + step})

Đáp án:

import * as React from 'react'

const countReducer = (state, action) => ({...state, ...action})

function Counter({initialCount = 0, step = 1}) {
  const [state, setState] = React.useReducer(countReducer, {
    count: initialCount,
  })
  const {count} = state
  const increment = () => setState({count: count + step})
  return <button onClick={increment}>{count}</button>
}

function App() {
  return <Counter />
}

export default App

Giả lập setState với object hoặc function

this.setState từ class component có thể chấp nhận một function. Bạn hãy dùng useReducer để giả lập một hàm setState như sau:

const [state, setState] = React.useReducer(countReducer, {
  count: initialCount,
})
const {count} = state
const increment = () =>
  setState(currentState => ({count: currentState.count + step}))

Đáp án:

import * as React from 'react'

const countReducer = (state, action) => ({
  ...state,
  ...(typeof action === 'function' ? action(state) : action),
})

function Counter({initialCount = 0, step = 1}) {
  const [state, setState] = React.useReducer(countReducer, {
    count: initialCount,
  })
  const {count} = state
  const increment = () =>
    setState(currentState => ({count: currentState.count + step}))
  return <button onClick={increment}>{count}</button>
}

function App() {
  return <Counter />
}

export default App

Sử dụng dispatch và switch

Một trong những cách phổ biến nhất mà mọi người hay sử dụng là dùng dispatchswitch trong reducer function như sau:

const [state, dispatch] = React.useReducer(countReducer, {
  count: initialCount,
})
const {count} = state
const increment = () => dispatch({type: 'INCREMENT', step})

Bạn hãy viết lại useReducer ở trên để có thể sử dụng cú pháp như Redux thông thường nhé.

Đáp án:

import * as React from 'react'

function countReducer(state, action) {
  const {type, step} = action
  switch (type) {
    case 'increment': {
      return {
        ...state,
        count: state.count + step,
      }
    }
    default: {
      throw new Error(`Unsupported action type: ${action.type}`)
    }
  }
}

function Counter({initialCount = 0, step = 1}) {
  const [state, dispatch] = React.useReducer(countReducer, {
    count: initialCount,
  })
  const {count} = state
  const increment = () => dispatch({type: 'increment', step})
  return <button onClick={increment}>{count}</button>
}

function App() {
  return <Counter />
}

export default App

thaunguyen.com via Epic React by Kent C.Dodds

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *