Trong 2 bài trước chúng ta đã cùng nhau tìm hiểu về hàm useState, useEffect và useContext trong bài này Dũng sẽ cùng các bạn tìm hiểu về các hàm useRefuseReducer nhé.

Hàm useRef

Một trong những vấn đề của react hook đó là khi có bất cứ trạng thái nào được cập nhật thì cả hàm sẽ được cập nhật lại giống như chúng ta khởi tạo lúc đầu, nên ví dụ chúng ta khai báo 1 biến sum kiểu này:

import { useEffect, useRef, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import Next from './Next';

function App() {
  const [count, setCount] = useState(0);
  const [messages, setMessages] = useState<Array<string>>();
  const [next, setNext] = useState(false);
  let sum = 0;
  console.log("1. init");

  useEffect(() => {
    return () => {
      console.log("unsubscribe events")
    };
  }, [next]);

  useEffect(() => {
    console.log("3. useEffect, count = ", count);
    if (count == 30) {
      setNext(true);
    }
    sum++;
  }, [count]);

  console.log("2. render");
  if (next) {
    console.log("next");
    return <Next />
  }
  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <button>
          sum is {sum}
        </button>
        <button onClick={() => setMessages(["1", "2", "3"])}>
          messages are {messages}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

Chúng ta kỳ vọng rằng mỗi lần chúng ta nhấn nút count is thì sum sẽ tăng lên. Tuy nhiên điều này không xảy ra bởi vì khi biến trạng thái count thay đổi thì hàm lại được gọi lại và sum lại được set lại bằng 0.
Đây là nguyên nhân mà hàm useRef ra đời, hàm này cho phép chúng ta lưu giữ trạng thái của một biến để không bị reset khi một state thay đổi, đồng thời biến ref này cũng không làm thay đổi trạng thái và không kích hoạt việc render html, chúng ta có thể sửa lại mã nguồn cho biến sum như sau:

import { useEffect, useRef, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import Next from './Next';

function App() {
  const [count, setCount] = useState(0);
  const [messages, setMessages] = useState<Array<string>>();
  const [next, setNext] = useState(false);
  const sum = useRef(0);
  console.log("1. init");

  useEffect(() => {
    return () => {
      console.log("unsubscribe events")
    };
  }, [next]);

  useEffect(() => {
    console.log("3. useEffect, count = ", count);
    if (count == 30) {
      setNext(true);
    }
    sum.current ++;
  }, [count]);

  console.log("2. render");
  if (next) {
    console.log("next");
    return <Next />
  }
  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <button>
          sum is {sum.current}
        </button>
        <button onClick={() => setMessages(["1", "2", "3"])}>
          messages are {messages}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

Khi chúng ta nhấn vào nút count is nó cũng làm nút sum is tăng giá trị.

Hàm useReducer

Sẽ có nhiều trường hợp khi biến trạng thái của chúng ta cần tổng hợp tương đối phức tạp, lúc này chúng ta có 2 lựa chọn, 1 là dùng hàm trung gian, 2 là chúng ta có thể sử useReducer để cập nhật biến trạng thái.
Hãy nói chúng ta có một biến trạng thái kiểu:

interface Data {
  value: number;
}

Trên giao diện của chúng ta cũng có 2 nút thế này:

Khi chúng ta nhấn vào nút Add thì Value sẽ tăng lên 2 đơn vị còn khi chúng nhấn vào nút Multiple Value sẽ tăng gấp đôi. Chúng ta có thể tạo ra một hàm reducer như sau:

interface Data {
  value: number;
}

interface Action {
  type: string;
}

const dataState = { value: 0 };

const reducer = (data: Data, action: Action): Data => {
  switch (action.type) {
      case 'add':
          return { value: data.value + 2 };
      case 'multiply':
          return { value: data.value * 2 };
      default:
          throw new Error();
  }
};

Sau đó chúng ta có thể khai báo một biến trạng thái data kiểu thế này:

const [data, dispatch] = useReducer(reducer, dataState);

Và cách mà chúng ta sẽ kích hoạt việc cập nhật thay đối biến trạng thái data sẽ như sau:

<p>Value: {data.value}</p>
  <button onClick={() => dispatch({ type: 'add' })}>Add</button>
  <button onClick={() => dispatch({ type: 'multiply' })}>Multiple</button>
<p>

Khi người dùng nhấn vào nut Add hoặc Multiple thì hàm reducer sẽ được kích hoạt và tạo ra giá trị mới cho biến trạng thái data. Mã nguồn hoàn chỉnh sẽ như sau:

import { useEffect, useReducer, useRef, useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import Next from './Next';

interface Data {
  value: number;
}

interface Action {
  type: string;
}

const dataState = { value: 0 };

const reducer = (data: Data, action: Action): Data => {
  switch (action.type) {
      case 'add':
          return { value: data.value + 2 };
      case 'multiply':
          return { value: data.value * 2 };
      default:
          throw new Error();
  }
};

function App() {
  const [count, setCount] = useState(0);
  const [messages, setMessages] = useState<Array<string>>();
  const [next, setNext] = useState(false);
  const sum = useRef(0);
  console.log("1. init");

  useEffect(() => {
    return () => {
      console.log("unsubscribe events")
    };
  }, [next]);

  useEffect(() => {
    console.log("3. useEffect, count = ", count);
    if (count == 30) {
      setNext(true);
    }
  }, [count]);

  const [data, dispatch] = useReducer(reducer, dataState);

  console.log("2. render");
  if (next) {
    console.log("next");
    return <Next />
  }
  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <button>
          sum is {sum.current}
        </button>
        <button onClick={() => setMessages(["1", "2", "3"])}>
          messages are {messages}
        </button>
        <p>Value: {data.value}</p>
            <button onClick={() => dispatch({ type: 'add' })}>Add</button>
            <button onClick={() => dispatch({ type: 'multiply' })}>Multiple</button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

Và khi bạn nhấn nút Add hoặc Multiply thì Value cũng sẽ thay đổi.

Tổng kết

Như vậy chúng ta đã cùng nhau tìm hiểu về 2 hàm useRef và useReducer của React Hooks, các hàm còn lại chúng ta sẽ tìm hiểu trong bài sau nhé.


Cám ơn bạn đã quan tâm đến bài viết này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a