DEV Community

mikebui
mikebui

Posted on

Hướng dẫn hoàn chỉnh để sử dụngRef () và Refs trong React

Bài viết dịch từ trang:
https://dmitripavlutin.com/react-useref-guide/

Trong bài đăng này, bạn sẽ học cách sử dụng hook React.useRef () để tạo các giá trị có thể thay đổi liên tục (còn được gọi là tham chiếu hoặc refs), cũng như truy cập các phần tử DOM.

1. Mutable values

useRef (initialValue) là một hook React tích hợp sẵn chấp nhận một đối số làm giá trị ban đầu và trả về một tham chiếu (còn gọi là ref). Ref là một đối tượng có thuộc tính đặc biệt là current.

import { useRef } from 'react';
function MyComponent() {
  const reference = useRef(initialValue);
  const someHandler = () => {
    // Access reference value:
    const value = reference.current;
    // Update reference value:
    reference.current = newValue;
  };
  // ...
}
Enter fullscreen mode Exit fullscreen mode

reference.current truy cập vào giá trị tham chiếu và reference.current = newValue cập nhật giá trị tham chiếu. Khá đơn giản.

Có 2 quy tắc cần nhớ về references:

  1. Giá trị của tham chiếu được duy trì (giữ nguyên) giữa các lần re-rendering component;
  2. Cập nhật tham chiếu không làm re-rendering component.

Bây giờ, chúng ta hãy xem cách sử dụng useRef () trong thực tế.

1.1 Trường hợp sử dụng: ghi nhật ký button khi click

Component LogButtonClicks sử dụng một tham chiếu để lưu trữ số lần ấn vào button:

import { useRef } from 'react';
function LogButtonClicks() {
  const countRef = useRef(0);

  const handle = () => {
    countRef.current++;
    console.log(`Clicked ${countRef.current} times`);
  };
  console.log('I rendered!');
  return <button onClick={handle}>Click me</button>;
}
Enter fullscreen mode Exit fullscreen mode

const countRef = useRef (0) tạo một tham chiếu countRef được khởi tạo bằng 0.

Khi button được ấn, hàm xử lý được gọi và giá trị tham chiếu được tăng lên: countRef.current ++. Giá trị tham chiếu được ghi vào console.

Cập nhật giá trị tham chiếu countRef.current ++ không làm component re-rendering. Điều này được chứng minh bằng thực tế là 'I rendered!' được ghi vào console chỉ một lần, ở lần hiển thị đầu tiên và không có render nào xảy ra khi tham chiếu được cập nhật.

Bây giờ có một câu hỏi: sự khác biệt chính giữa tham chiếu và state là gì?

Khác biệt giữa tham chiếu và state

Hãy sử dụng lại component LogButtonClicks từ phần trước, nhưng lần này sử dụng hook useState () để đếm số lần ấn vào button:

import { useState } from 'react';
function LogButtonClicks() {
  const [count, setCount] = useState(0);

  const handle = () => {
    const updatedCount = count + 1;
    console.log(`Clicked ${updatedCount} times`);
    setCount(updatedCount);
  };
  console.log('I rendered!');
  return <button onClick={handle}>Click me</button>;
}

Enter fullscreen mode Exit fullscreen mode

Ở link trên ấn vào button. Mỗi lần bạn ấn vào, bạn sẽ thấy trong console có thông báo 'I rendered!' - nghĩa là mỗi khi state được cập nhật, component sẽ re-render.

Vì vậy, 2 điểm khác biệt chính giữa tham chiếu và trạng thái:

  1. Cập nhật tham chiếu không làm re-render, trong khi cập nhật state làm cho component re-render;

  2. Cập nhật tham chiếu là đồng bộ (giá trị tham chiếu được cập nhật có sẵn ngay lập tức), trong khi cập nhật state là không đồng bộ (state được cập nhật sau khi re-render).

Từ một quan điểm cao hơn, các tham chiếu lưu trữ dữ liệu cơ sở hạ tầng về side-effect, trong khi state lưu trữ thông tin được hiển thị trực tiếp trên màn hình.

1.2 Trường hợp sử dụng: triển khai đồng hồ bấm giờ

Bạn có thể lưu trữ bên trong một cơ sở hạ tầng dữ liệu tham khảo về các side-effect. Ví dụ: bạn có thể lưu trữ vào các con trỏ tham chiếu: timer ids, socket ids, etc.

Component Stopwatch sử dụng chức năng hẹn giờ setInterval(gọi lại, thời gian) để tăng số đếm của đồng hồ bấm giờ mỗi giây. Id bộ hẹn giờ được lưu trữ trong timerIdRef:

import { useRef, useState, useEffect } from 'react';
function Stopwatch() {
  const timerIdRef = useRef(0);
  const [count, setCount] = useState(0);
  const startHandler = () => {
    if (timerIdRef.current) { return; }
    timerIdRef.current = setInterval(() => setCount(c => c+1), 1000);
  };
  const stopHandler = () => {
    clearInterval(timerIdRef.current);
    timerIdRef.current = 0;
  };
  useEffect(() => {
    return () => clearInterval(timerIdRef.current);
  }, []);
  return (
    <div>
      <div>Timer: {count}s</div>
      <div>
        <button onClick={startHandler}>Start</button>
        <button onClick={stopHandler}>Stop</button>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

startHandler (), được gọi khi ấn vào button Start, khởi động bộ hẹn giờ và lưu id bộ hẹn giờ trong tham chiếu timerIdRef.current = setInterval (...).

Để dừng đồng hồ bấm giờ, người dùng ấn vào button Stop. Trình xử lý button Stop stopHandler () truy cập id bộ hẹn giờ từ tham chiếu và dừng bộ hẹn giờ clearInterval(timerIdRef.current).

Ngoài ra, nếu component unmount khi đồng hồ bấm giờ đang hoạt động, chức năng dọn dẹp của useEffect () cũng sẽ dừng đồng hồ bấm giờ.

Trong ví dụ về đồng hồ bấm giờ, tham chiếu được sử dụng để lưu trữ dữ liệu cơ sở hạ tầng - id bộ hẹn giờ hoạt động.

2. Truy cập các phần tử DOM

Một ứng dụng hữu ích khác của hook useRef () là truy cập các phần tử DOM. Điều này được thực hiện trong 3 bước:

  1. Xác định tham chiếu để truy cập phần tử const elementRef = useRef ();
  2. Gán tham chiếu đến thuộc tính ref của phần tử: <div ref = {elementRef}> </div>;
  3. Sau khi gắn kết, elementRef.current trỏ đến phần tử DOM.
import { useRef, useEffect } from 'react';
function AccessingElement() {
  const elementRef = useRef();
   useEffect(() => {
    const divElement = elementRef.current;
    console.log(divElement); // logs <div>I'm an element</div>
  }, []);
  return (
    <div ref={elementRef}>
      I'm an element
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

2.1 Trường hợp sử dụng: focus vào input

Ví dụ, bạn sẽ cần truy cập các phần tử DOM để focus vào input khi component mount.

Để làm cho nó hoạt động, bạn sẽ cần tạo một tham chiếu đến input, gán tham chiếu cho thuộc tính ref và sau khi gắn kết, hãy gọi phương thức đặc biệt element.focus () trên phần tử.

Đây là cách triển khai có thể có của component <InputFocus>:

import { useRef, useEffect } from 'react';
function InputFocus() {
  const inputRef = useRef();
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <input 
      ref={inputRef} 
      type="text" 
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

const inputRef = useRef () tạo một tham chiếu để giữ phần tử input.

inputRef sau đó được gán cho thuộc tính ref của input: <input ref = {inputRef} type = "text" />.

React sau đó, sau khi mounting, đặt inputRef.current là phần tử input. Bây giờ bạn có thể đặt focus qua: inputRef.current.focus ().

Tham chiếu không có giá trị khi hiển thị ban đầu

Trong quá trình hiển thị ban đầu, tham chiếu được cho là giữ phần tử DOM là rỗng:

import { useRef, useEffect } from 'react';
function InputFocus() {
  const inputRef = useRef();
  useEffect(() => {
    // Logs `HTMLInputElement` 
    console.log(inputRef.current);
    inputRef.current.focus();
  }, []);
  // Logs `undefined` during initial rendering
  console.log(inputRef.current);
  return <input ref={inputRef} type="text" />;
}
Enter fullscreen mode Exit fullscreen mode

Trong quá trình hiển thị ban đầu, React vẫn xác định đâu là đầu ra của component, vì vậy chưa có cấu trúc DOM nào được tạo. Đó là lý do tại sao inputRef.current đánh giá là không xác định trong quá trình hiển thị ban đầu.

useEffect (callback, []) hook gọi lại ngay sau khi mount, khi phần tử input đã được tạo trong DOM.

hàm callback của useEffect (callback, []) là nơi thích hợp để truy cập inputRef.current vì nó được đảm bảo rằng DOM được xây dựng.

3. Hạn chế cập nhật refs

Phạm vi chức năng của component chức năng nên tính toán đầu ra hoặc gọi các hook.

Đó là lý do tại sao việc cập nhật một tham chiếu (cũng như cập nhật state) không nên được thực hiện bên trong phạm vi ngay lập tức của chức năng của component.

Tham chiếu phải được cập nhật bên trong callback useEffect () hoặc bên trong các handlers (event handlers, timer handlers, etc).

import { useRef, useEffect } from 'react';
function MyComponent({ prop }) {
  const myRef = useRef(0);
  useEffect(() => {
    myRef.current++; // Good!
    setTimeout(() => {
      myRef.current++; // Good!
    }, 1000);
  }, []);
  const handler = () => {
    myRef.current++; // Good!
  };
  myRef.current++; // Bad!
  if (prop) {
    myRef.current++; // Bad!
  }
  return <button onClick={handler}>My button</button>;
}
Enter fullscreen mode Exit fullscreen mode

4. Tóm tắt

useRef () hook tạo ra các tham chiếu.

Gọi const reference = useRef(initialValue) với initialValue trả về một đối tượng đặc biệt có tên là tham chiếu. Đối tượng tham chiếu có thuộc tính current: bạn có thể sử dụng thuộc tính này để đọc giá trị tham chiếu reference.current hoặc cập nhật tham chiếu reference.current = newValue.

Giữa các lần component re-rendering, giá trị của tham chiếu là không đổi.

Cập nhật tham chiếu, trái với cập nhật state, không làm component re-rendering.

Ref cũng có thể truy cập các phần tử DOM. Gán tham chiếu đến thuộc tính ref của phần tử bạn muốn truy cập: <div ref = {reference}> Element</div> - và phần tử sẽ có tại reference.current.

Top comments (0)