Photo by Ferenc Almasi on Unsplash

Refs, DOM và Forwarding Refs trong React

📅February 17, 2021

Bài viết này dành cho những bạn đang lơ mơ về Refs (như mình trước đây 🤣). Nên mình sẽ giải thích và lấy ví dụ cực kỳ đơn giản cho các bạn dễ hình dung. Đảm bảo không dài dòng.

Refs là gì? 🤔

Following by React Documentation:

Refs provide a way to access DOM nodes or React elements created in the render method.

Hiểu nôm na là có thể truy suất được DOM node hoặc React Element trong render method. Refs === References nghĩa là trỏ đến một thứ gì đó, theo mình hiểu thứ gì đó ở đây là DOM node 🤔

Ngày trước học React ngại mỗi thằng Refs này, cứ tránh tránh nó vì khó hiểu, một phần vì ít khi sử dụng và thời đó gà mờ, đọc docs hơi ngu nên đọc hoài không hiểu 🤣. Mãi về sau này làm nhiều quen rồi nên mới hiểu được cách dùng, khi nào cần sử dụng.

Đi vào ví dụ thực tế cho dễ hình dung nhỉ, thử khởi tạo 1 Refs xem sao nhé, chúng ta có thể khởi tạo Refs bằng cách thuần túy (trong Class Component, Function Component), nhưng mình sẽ dùng Hooks luôn.

const refContainer = useRef(initialValue)

useRef là một trong nhiều Hooks API được đưa vào React từ version 16.8, cho phép sử dụng state và các feature khác của React(Life Cycle, …) mà không cần viết Class.

Về cơ bản thì useRef như là một cái hộp để lưu trữ mutable value (Để hiểu thêm về mutable và immutable value thì mình có bài viết ở 👉 đây)

Sau khi khởi tạo ref xong thì cùng đi vào một ví dụ cụ thể nhé.

👇

import React, { useRef } from 'react'

function App() {
  const firstNameRef = useRef(null)  const lastNameRef = useRef(null)  const submitRef = useRef(null)
  const onSubmitKeyDown = () => {
    alert(`Hello: ${firstNameRef.current.value} ${lastNameRef.current.value}`)
  }

  const onFirstKeyDown = e => {
    if (e.key === 'Enter') {
      lastNameRef.current.focus()
    }
  }

  const onLastKeyDown = e => {
    if (e.key === 'Enter') {
      submitRef.current.focus()
    }
  }

  return (
    <div>
      <input
        type="text"
        name="firstName"
        placeholder="Enter Fistname..."
        ref={firstNameRef}
        onKeyDown={onFirstKeyDown}
      />

      <input
        type="text"
        name="lastName"
        placeholder="Enter Lastname..."
        ref={lastNameRef}
        onKeyDown={onLastKeyDown}
      />

      <button onKeyDown={onSubmitKeyDown} ref={submitRef} type="button">
        Submit
      </button>
    </div>
  )
}

Giải thích một chút, ở đây mình có 2 Element <input> có attributes name là firstNamelastName, button Submit sau khi Click sẽ hiển thị alert() fistNamelastName vừa nhập.

Mình sử dụng firstNameRef, lastNameRef gắn vào <input> element để truy suất và điểu khiển 1 số thuộc tính của input element.

onFirstKeyDown sau khi người dùng nhập thông tin vào input và nhấn Enter lập tức focus con trỏ chuột xuống input bên dưới. Tương tự với onLastKeyDown, sau khi nhấn Enter sẽ focus xuống Submit button thông qua method focus().

const onFirstKeyDown = e => {
  if (e.key === 'Enter') {
    lastNameRef.current.focus()  }
}

Method focus() này được access thông qua ref.current. Ngoài ra chúng ta còn có thể access 1 số property của <input> như type, name, placeholder, value, … Tùy thuộc vào type của từng node mà value có thể khác nhau (Các bạn có thể xem thêm tại 👉 đây)

const onFirstKeyDown = e => {
  if (e.key === 'Enter') {
    lastNameRef.current.focus()    // Get property name 👉 firstName
    console.log(firstNameRef.current.name)
    // Get property placeholder of input 👉 Enter Firstname...
    console.log(firstNameRef.current.placeholder)
  }
}

Forwarding Refs với forwardRef

forwardRef là gì và tại sao lại phải dùng forwardRef ? 🤔

Cái form submit ở trên bây giờ mình muốn tách ra thành MyInput Component riêng để dễ reuse sau này, mình làm như sau:

function MyInput({ type, name, placeholder, onKeyDown }, ref) {
  return (
    <input
      ref={ref}
      type={type}
      name={name}
      onKeyDown={onKeyDown}
      placeholder={placeholder}
    />
  )
}

export default MyInput

Sử dụng MyInput trong App.js

import MyInput from './MyInput'
...

<div>
  <h3>Login</h3>
  <MyInput
    type="text"
    name="firstName"
    placeholder="Enter Firstname..."
    ref={firstNameRef}
    onKeyDown={onFirstKeyDown}
  />
  <MyInput
    type="text"
    name="lastName"
    placeholder="Enter Lastname..."
    ref={lastNameRef}
    onKeyDown={onLastKeyDown}
  />
  <button ref={submitRef} onKeyDown={onSubmitKeyDown}>
    Submit
  </button>
</div>

Combile and Run … 👉 Check Console Log của Browser, lúc này sẽ báo lỗi:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Đó là lý do forwardRefs ra đời, khi pass ref từ Parent Component xuống Child Component, chúng ta phải forwarding Ref đó để React có thể hiểu được.

Sửa lại MyInput Component thành

function MyInput(props, ref) {
  const { type, name, placeholder, onKeyDown } = props
  return (
    <input
      ref={ref}
      type={type}
      name={name}
      onKeyDown={onKeyDown}
      placeholder={placeholder}
    />
  )
}

const forwardedMyInput = React.forwardRef(MyInput)
export default forwardedMyInput

Toàn bộ quá trình trên được hiểu như sau:

  • Đầu tiên khởi tạo React ref bằng useRef hook.
  • Lần lượt truyền ref vào <MyInput ref={ref}>.
  • Lúc này ở MyInput component sẽ nhận 2 argument là propsref.
  • Sử dụng React.forwardRef(MyInput) để forwarding ref vào component MyInput.
  • Sau khi ref được forward, chúng ta có thể sử dụng được ref.current trong buttoninput element 😀

Done

Khi nào thì nên sử dụng Ref?

Thằng Refs này có usecase cụ thể để sử dụng nhé chứ không phải thích là dùng đâu nhé các bạn 😁

Một số use cases phù hợp cho sử dụng refs như làm việc với animation, text selection, focus, media playback hoặc tích hợp với third-party DOM libraries.

Refs trỏ trực tiếp vào DOM thật trong khi React sử dụng Virtual DOM (DOM ảo) nên mình cũng khuyên các bạn không nên quá làm dụng Refs.

Tránh sử dụng refs với mọi trường hợp trong khi nó có thể được thực hiện bằng cách khai báo biến thông thường

Ví dụ, có một Dialog Component, thay vì dùng refs để điều khiển Dialog Component bằng method open()close(). Hãy dùng một props là isOpen để điều khiển Dialog đó :).

Happy Coding !! 👨‍💻

References

https://reactjs.org/docs/forwarding-refs.html

https://reactjs.org/docs/hooks-reference.html#useref


Hey. I'm Thuan.
I write anything to help others.