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à firstName
và lastName
, button Submit
sau khi Click sẽ hiển thị alert()
fistName
và lastName
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ằnguseRef
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àprops
vàref
. - Sử dụng
React.forwardRef(MyInput)
để forwardingref
vào componentMyInput
. - Sau khi
ref
được forward, chúng ta có thể sử dụng đượcref.current
trongbutton
vàinput
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()
và close()
. Hãy dùng một props là isOpen
để điều khiển Dialog
đó :).
Happy Coding !! 👨💻