San Phan

Mở rộng tương tác với component sử dụng ref trong ReactJS

ứng dụng refuseImperativeHandlereactjs

Người ta nói không sai, "đi một ngày đàng học một sàng khôn". Bài viết này được viết sau khi review code của đồng nghiệp.

Đặt vấn đề #

Thông thường, bạn nghĩ nếu muốn tương tác với một component con chúng ta sẽ dùng những cách gì?

Không phải bàn cãi cách chúng ta vẫn hay làm nhất là sử dụng props, theo cái cách mỗi khi props thay đổi thì sẽ rerender lại component con. Đây là nền tảng trong phát triển ứng dụng dùng ReactJS rồi. Tất nhiên, trong bài này không phải nói về điều này.

Còn sử dụng context (hoặc các state management khác như redux), nhưng nó lại là một cách khá phức tạp mà tôi không muốn nói trong bài này. Ngoài ra, đây là một phương pháp mà không lạ gì nên càng không phải cái tôi muốn nói.

Cách còn lại bạn sẽ thấy qua các ví dụ trong bài viết. Về cơ bản nó rất đơn giản thôi.

Bắt đầu từ ví dụ đơn giản #

Bắt đầu với 1 ví dụ đơn giản, tôi có một form input mà mỗi khi form đó được render trên view nó sẽ tự động focus vào một fancy input.

Nếu input đó đơn thuần chỉ là một thẻ HTML <input/> chúng ta có thể sử dụng ref để tương tác trực tiếp với input html khi component đó được render ra.

function Form() {
const inputRef = useRef(null);

useEffect(() => {
inputRef.current.focus(); //Chỗ này là để focus vào input đó.
}, []);

return (
<form>
<input ref={inputRef} />
</form>
);
}

Nhưng ở đây, input này là một component mà bên trong nó thực hiện gì gì đó nói chung để tạo ra được một fancy input. Trong trường hợp này ta có thể sử dụng forwardRef để forward ref ở FancyInput xuống input html, nó sẽ được thực hiện như sau:

const FancyInput = React.forwardRef((props, ref) => {
//Logic của component
...

return <input ref={ref} />;
});

//Trong component Form ta thực hiện tương tự như trên nhé
function Form() {
const inputRef = useRef(null);

useEffect(() => {
inputRef.current.focus(); //Chỗ này là để focus vào input đó.
}, []);

return (
<form>
<FancyInput ref={inputRef} />
</form>
);
}

Nâng cấp vấn đề một chút #

Giả sử chúng giờ ngoài việc tự động focus vào input đó, tôi lại muốn làm thêm một số action khác mà không muốn phải dùng props hoặc context. Chẳng hạn giả sử vẫn ví dụ trên với FancyInput, FancyInput này có một local state cho việc hiển thị message gì đó và vấn đề ta đang muốn là có thể thay đổi message đó khi cần thiết từ component cha. Tất nhiên, vẫn phải có chức năng focus. (Có thể ví dụ này không hữu dụng cho lắm nhưng chỉ là cách để bạn hiểu vấn đề thôi.)

Với vấn đề này, ta sẽ sử dụng một hook mà thư viện React cung cấp đó là: useImperativeHandle

Đoạn code trên sẽ trở thành thế này

Từ ví dụ trên bạn đã thấy được tác dụng của useImperativeHandle. Nó cho phép ta expose các function hoặc cả value để component cha có thể lấy một cách dễ dàng. Nó gần như expose ra instance của component đó cho component cha có thể dễ dàng tương tác sử dụng.

Tuy nhiên nó nên được sử dụng khi nào? #

Đứng trên quan điểm của tôi thì việc sử dụng nó chỉ nên áp dụng khi cần tương tác giữa component con và component cha trực tiếp thôi. Nếu bạn phải xử lý những tác vụ giữa nhiều lớp component thì tôi nghĩ là không nên dùng cách này.

Kết luận #

Từ các ví dụ này cho bạn thấy cách để bạn có thể tương tác với component con từ component cha một cách dễ dàng. Từ ví dụ đơn giản chỉ là focus vào input và phức tạp hơn với cách dụng useImperativeHandle hook cho phép bạn không chỉ tương tác với HTML mà con expose nhiều hàm cho để tương tác với component (ví dụ như thay đổi local state của component chẳng hạn).