Tìm hiểu về forwardRef của React: Hiểu về chuyển tiếp tham chiếu, truy cập các nút DOM của thành phần con, tạo thành phần có thể tái sử dụng.
React forwardRef: Hướng dẫn Toàn diện về Chuyển tiếp Tham chiếu
Trong React, việc truy cập trực tiếp một nút DOM của một thành phần con có thể là một thách thức. Đây là lúc forwardRef xuất hiện, cung cấp một cơ chế để chuyển tiếp một ref đến một thành phần con. Bài viết này cung cấp một cái nhìn sâu sắc về forwardRef, giải thích mục đích, cách sử dụng và lợi ích của nó, đồng thời trang bị cho bạn kiến thức để tận dụng nó một cách hiệu quả trong các dự án React của bạn.
forwardRef là gì?
forwardRef là một API React cho phép một thành phần cha nhận một ref đến một nút DOM trong một thành phần con. Nếu không có forwardRef, các ref thường bị giới hạn trong thành phần nơi chúng được tạo. Giới hạn này có thể gây khó khăn khi tương tác trực tiếp với DOM cơ bản của một thành phần con từ thành phần cha của nó.
Hãy nghĩ về nó như thế này: hãy tưởng tượng bạn có một thành phần nhập tùy chỉnh và bạn muốn tự động tập trung vào trường nhập khi thành phần được gắn kết. Nếu không có forwardRef, thành phần cha sẽ không có cách nào để truy cập trực tiếp nút DOM của đầu vào. Với forwardRef, thành phần cha có thể giữ một tham chiếu đến trường nhập và gọi phương thức focus() trên nó.
Tại sao nên sử dụng forwardRef?
Dưới đây là một số tình huống phổ biến trong đó forwardRef chứng tỏ là vô giá:
- Truy cập các Nút DOM con: Đây là trường hợp sử dụng chính. Các thành phần cha có thể trực tiếp thao tác hoặc tương tác với các nút DOM trong các thành phần con của chúng.
- Tạo các thành phần có thể tái sử dụng: Bằng cách chuyển tiếp các ref, bạn có thể tạo ra các thành phần linh hoạt hơn và có thể tái sử dụng, có thể dễ dàng tích hợp vào các phần khác nhau của ứng dụng của bạn.
- Tích hợp với các Thư viện của Bên thứ ba: Một số thư viện của bên thứ ba yêu cầu quyền truy cập trực tiếp vào các nút DOM.
forwardRefcho phép bạn tích hợp liền mạch các thư viện này vào các thành phần React của bạn. - Quản lý Tiêu điểm và Lựa chọn: Như đã minh họa trước đó, việc quản lý tiêu điểm và lựa chọn trong các hệ thống phân cấp thành phần phức tạp trở nên đơn giản hơn nhiều với
forwardRef.
Cách forwardRef hoạt động
forwardRef là một thành phần bậc cao (HOC). Nó lấy một hàm kết xuất làm đối số và trả về một thành phần React. Hàm kết xuất nhận props và ref làm đối số. Đối số ref là ref mà thành phần cha chuyển xuống. Bên trong hàm kết xuất, bạn có thể gắn ref này vào một nút DOM trong thành phần con.
Cú pháp cơ bản
Cú pháp cơ bản của forwardRef như sau:
const MyComponent = React.forwardRef((props, ref) => {
// Logic thành phần ở đây
return ...;
});
Hãy phân tích cú pháp này:
React.forwardRef(): Đây là hàm bao bọc thành phần của bạn.(props, ref) => { ... }: Đây là hàm kết xuất. Nó nhận các thuộc tính của thành phần và ref được truyền từ thành phần cha.<div ref={ref}>...</div>: Đây là nơi điều kỳ diệu xảy ra. Bạn gắnrefnhận được vào một nút DOM trong thành phần của bạn. Nút DOM này sau đó sẽ có thể truy cập được cho thành phần cha.
Ví dụ thực tế
Hãy khám phá một số ví dụ thực tế để minh họa cách forwardRef có thể được sử dụng trong các tình huống thực tế.
Ví dụ 1: Tập trung vào một Trường nhập
Trong ví dụ này, chúng ta sẽ tạo một thành phần nhập tùy chỉnh tự động tập trung vào trường nhập khi nó được gắn kết.
import React, { useRef, useEffect } from 'react';
const FancyInput = React.forwardRef((props, ref) => {
return (
<input ref={ref} type="text" className="fancy-input" {...props} />
);
});
function ParentComponent() {
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<FancyInput ref={inputRef} placeholder="Focus me!" />
);
}
export default ParentComponent;
Giải thích:
FancyInputđược tạo bằng cách sử dụngReact.forwardRef. Nó nhậnpropsvàref.refđược gắn vào phần tử<input>.ParentComponenttạo mộtrefbằng cách sử dụnguseRef.refđược truyền đếnFancyInput.- Trong hook
useEffect, trường nhập được tập trung khi thành phần được gắn kết.
Ví dụ 2: Nút Tùy chỉnh với Quản lý Tiêu điểm
Hãy tạo một thành phần nút tùy chỉnh cho phép thành phần cha kiểm soát tiêu điểm.
import React, { forwardRef } from 'react';
const MyButton = forwardRef((props, ref) => {
return (
<button ref={ref} className="my-button" {...props}>
{props.children}
</button>
);
});
function App() {
const buttonRef = React.useRef(null);
const focusButton = () => {
if (buttonRef.current) {
buttonRef.current.focus();
}
};
return (
<div>
<MyButton ref={buttonRef} onClick={() => alert('Button Clicked!')}>
Click Me
</MyButton>
<button onClick={focusButton}>Focus Button</button>
</div>
);
}
export default App;
Giải thích:
MyButtonsử dụngforwardRefđể chuyển tiếp ref đến phần tử nút.- Thành phần cha (
App) sử dụnguseRefđể tạo một ref và truyền nó đếnMyButton. - Hàm
focusButtoncho phép thành phần cha tập trung theo chương trình vào nút.
Ví dụ 3: Tích hợp với Thư viện của Bên thứ ba (Ví dụ: react-select)
Nhiều thư viện của bên thứ ba yêu cầu quyền truy cập vào nút DOM cơ bản. Hãy trình bày cách tích hợp forwardRef với một tình huống giả định bằng react-select, nơi bạn có thể cần truy cập phần tử nhập của select.
Lưu ý: Đây là một minh họa giả định đơn giản. Tham khảo tài liệu react-select thực tế để biết các cách được hỗ trợ chính thức để truy cập và tùy chỉnh các thành phần của nó.
import React, { useRef, useEffect } from 'react';
// Giả sử một giao diện react-select đơn giản để trình bày
import Select from 'react-select'; // Thay thế bằng import thực tế
const CustomSelect = React.forwardRef((props, ref) => {
return (
<Select ref={ref} {...props} />
);
});
function MyComponent() {
const selectRef = useRef(null);
useEffect(() => {
// Giả định: Truy cập phần tử nhập trong react-select
if (selectRef.current && selectRef.current.inputRef) { // inputRef là một prop giả định
console.log('Input Element:', selectRef.current.inputRef.current);
}
}, []);
return (
<CustomSelect
ref={selectRef}
options={[
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
]}
/>
);
}
export default MyComponent;
Lưu ý quan trọng đối với Thư viện của Bên thứ ba:
- Tham khảo Tài liệu của Thư viện: Luôn kiểm tra tài liệu của thư viện của bên thứ ba để hiểu các cách được khuyến nghị để truy cập và thao tác các thành phần bên trong của nó. Việc sử dụng các phương pháp không có tài liệu hoặc không được hỗ trợ có thể dẫn đến hành vi không mong muốn hoặc sự cố trong các phiên bản sau này.
- Khả năng truy cập: Khi truy cập trực tiếp các nút DOM, hãy đảm bảo rằng bạn duy trì các tiêu chuẩn về khả năng truy cập. Cung cấp các cách thay thế để người dùng dựa vào công nghệ hỗ trợ có thể tương tác với các thành phần của bạn.
Các phương pháp hay nhất và Cân nhắc
Mặc dù forwardRef là một công cụ mạnh mẽ, nhưng điều cần thiết là sử dụng nó một cách thận trọng. Dưới đây là một số phương pháp hay nhất và cân nhắc cần ghi nhớ:
- Tránh Lạm dụng: Không sử dụng
forwardRefnếu có các phương án thay thế đơn giản hơn. Hãy cân nhắc sử dụng các thuộc tính hoặc các hàm gọi lại để giao tiếp giữa các thành phần. Việc lạm dụngforwardRefcó thể khiến mã của bạn khó hiểu và bảo trì hơn. - Duy trì Đóng gói: Hãy lưu ý đến việc phá vỡ đóng gói. Việc thao tác trực tiếp các nút DOM của các thành phần con có thể khiến mã của bạn trở nên dễ vỡ và khó cấu trúc lại hơn. Hãy cố gắng giảm thiểu thao tác DOM trực tiếp và dựa vào API bên trong của thành phần bất cứ khi nào có thể.
- Khả năng truy cập: Khi làm việc với ref và các nút DOM, hãy luôn ưu tiên khả năng truy cập. Đảm bảo rằng các thành phần của bạn có thể sử dụng được bởi những người khuyết tật. Sử dụng HTML ngữ nghĩa, cung cấp các thuộc tính ARIA thích hợp và kiểm tra các thành phần của bạn bằng các công nghệ hỗ trợ.
- Hiểu vòng đời của Thành phần: Hãy nhận biết khi nào ref khả dụng. Ref thường có sẵn sau khi thành phần được gắn kết. Sử dụng
useEffectđể truy cập ref sau khi thành phần đã được kết xuất. - Sử dụng với TypeScript: Nếu bạn đang sử dụng TypeScript, hãy đảm bảo gõ chính xác các ref và các thành phần của bạn sử dụng
forwardRef. Điều này sẽ giúp bạn phát hiện lỗi sớm và cải thiện tính an toàn chung của kiểu cho mã của bạn.
Các phương án thay thế cho forwardRef
Trong một số trường hợp, có các phương án thay thế cho việc sử dụng forwardRef có thể phù hợp hơn:
- Thuộc tính và Callbacks: Việc truyền dữ liệu và hành vi xuống thông qua các thuộc tính thường là cách đơn giản nhất và được ưu tiên nhất để giao tiếp giữa các thành phần. Nếu bạn chỉ cần truyền dữ liệu hoặc kích hoạt một hàm trong thành phần con, các thuộc tính và callbacks thường là lựa chọn tốt nhất.
- Ngữ cảnh: Để chia sẻ dữ liệu giữa các thành phần được lồng nhau sâu, API Ngữ cảnh của React có thể là một lựa chọn tốt. Ngữ cảnh cho phép bạn cung cấp dữ liệu cho toàn bộ cây con của các thành phần mà không cần truyền các thuộc tính theo cách thủ công ở mọi cấp độ.
- Xử lý mệnh lệnh: Hook useImperativeHandle có thể được sử dụng kết hợp với forwardRef để hiển thị API giới hạn và được kiểm soát cho thành phần cha, thay vì hiển thị toàn bộ nút DOM. Điều này duy trì việc đóng gói tốt hơn.
Cách sử dụng nâng cao: useImperativeHandle
Hook useImperativeHandle cho phép bạn tùy chỉnh giá trị thể hiện được hiển thị cho các thành phần cha khi sử dụng forwardRef. Điều này cung cấp nhiều quyền kiểm soát hơn đối với những gì thành phần cha có thể truy cập, thúc đẩy việc đóng gói tốt hơn.
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
},
}));
return <input ref={inputRef} type="text" {...props} />;
});
function ParentComponent() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
const handleGetValue = () => {
alert(inputRef.current.getValue());
};
return (
<div>
<FancyInput ref={inputRef} placeholder="Enter text" />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleGetValue}>Get Value</button>
</div>
);
}
export default ParentComponent;
Giải thích:
- Thành phần
FancyInputsử dụnguseRefđể tạo một ref bên trong (inputRef) cho phần tử nhập. useImperativeHandleđược sử dụng để xác định một đối tượng tùy chỉnh sẽ được hiển thị cho thành phần cha thông qua ref được chuyển tiếp. Trong trường hợp này, chúng tôi đang hiển thị một hàmfocusvà một hàmgetValue.- Thành phần cha sau đó có thể gọi các hàm này thông qua ref mà không cần truy cập trực tiếp nút DOM của phần tử nhập.
Khắc phục sự cố Các vấn đề thường gặp
Dưới đây là một số vấn đề phổ biến bạn có thể gặp phải khi sử dụng forwardRef và cách khắc phục sự cố chúng:
- Ref là null: Đảm bảo rằng ref được truyền xuống đúng cách từ thành phần cha và thành phần con gắn đúng ref vào một nút DOM. Ngoài ra, hãy đảm bảo bạn đang truy cập ref sau khi thành phần đã được gắn kết (ví dụ: trong hook
useEffect). - Không thể đọc thuộc tính 'focus' của null: Điều này thường cho biết rằng ref không được gắn chính xác vào nút DOM hoặc nút DOM chưa được hiển thị. Kiểm tra kỹ cấu trúc thành phần của bạn và đảm bảo rằng ref đang được gắn vào phần tử chính xác.
- Lỗi kiểu trong TypeScript: Đảm bảo rằng các ref của bạn được gõ chính xác. Sử dụng
React.RefObject<HTMLInputElement>(hoặc loại phần tử HTML thích hợp) để xác định loại ref của bạn. Ngoài ra, hãy đảm bảo rằng thành phần sử dụngforwardRefđược gõ chính xác bằngReact.forwardRef<HTMLInputElement, Props>. - Hành vi không mong muốn: Nếu bạn gặp phải hành vi không mong muốn, hãy xem xét cẩn thận mã của bạn và đảm bảo rằng bạn không vô tình thao tác với DOM theo những cách có thể cản trở quá trình kết xuất của React. Sử dụng React DevTools để kiểm tra cây thành phần của bạn và xác định mọi vấn đề tiềm ẩn.
Kết luận
forwardRef là một công cụ có giá trị trong kho vũ khí của nhà phát triển React. Nó cho phép bạn thu hẹp khoảng cách giữa các thành phần cha và con, cho phép thao tác DOM trực tiếp và tăng cường khả năng tái sử dụng thành phần. Bằng cách hiểu mục đích, cách sử dụng và các phương pháp hay nhất của nó, bạn có thể tận dụng forwardRef để tạo các ứng dụng React mạnh mẽ, linh hoạt và có thể bảo trì hơn. Hãy nhớ sử dụng nó một cách thận trọng, ưu tiên khả năng truy cập và luôn cố gắng duy trì việc đóng gói bất cứ khi nào có thể.
Hướng dẫn toàn diện này đã cung cấp cho bạn kiến thức và các ví dụ để tự tin triển khai forwardRef trong các dự án React của bạn. Chúc bạn viết mã vui vẻ!