Học ReactJs từ số 0 - Advance - P3 - Forwarding Refs
Bài đăng này đã không được cập nhật trong 4 năm
Forwarding Refs
Forwarding Refs là kỹ thuật cho phép chúng ta sử dụng ref
từ một phần tử con của component, thông thường thì điều này không cần thiết nhưng ở trong một vài trường hợp thì nó lại khá là hữu ích trong việc tái sử dụng một số thành phần.
Forwarding refs to DOM components
Chúng ta cùng xem ví dụ sau:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
Trong ví dụ trên thì React sẽ ẩn đi các thành phần con của component FancyButton
, khi một component khác sử dụng FancyButton
thì sẽ không cần quan tâm tới trong FancyButton
có gì và không cần thiết phải điều khiển các thành phần con của FancyButton
. Điều này là tốt bởi vì nó sẽ giảm tải việc phụ thuộc lẫn nhau giữa các component và đúng với tư tưởng tái sử dụng của React.
Việc đóng gói các component riêng biệt là cần thiết và hữu ích nhưng trong một số trường hợp thì nó lại trở nên bất tiện, ví dụ như trong một ứng dụng bao gồm nhiều component ví dụ như FancyButton
, Input
thì việc quản lý các hiệu ứng như focus
, selection
, animations
khá là khó khăn và bất tiện.
Ref forwarding
là một tính năng có thể giúp chúng ta xử lý những case như nói trên nó cho phép chúng ta nhận tạo ra 1 ref
và đưa chúng xuống các thành phần con của Component.
Trong ví dụ dưới đây thì FancyButton
sử dụng React.forwardRef
để tạo ra 1 ref
và nó được đưa xuống thành phần con là button
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
Với cách này thì các component sử dụng FancyButton
có thể sử dụng button
và truy cập trực tiếp vào nó.
Sau đây là các bước mà đã xảy ra ở Component trên
- Trước tiên chúng ta Sử dụng
React.createRef
để tạo ra 1 biếnref
- Sau khi đã tạo xong biến
ref
thì chúng ta đưa nó vàoFancyButton
như đoạn code<FancyButton ref={ref}>Click me!</FancyButton>;
- React sẽ đưa ref đó vào Component giống như là một parameter thứ 2, ngang hàng với props, chứ không phải nằm trong props nha
- Ở trong
FancyButton
thì chúng ta tiếp tục đưaref
này xuống button như là một attributes<button ref={ref}>
- Và khi chúng ta sử dụng
ref.current
thì đồng nghĩa với việc chúng ta đang sử dụng button trongFancyButton
Các bạn chú ý parameter thứ 2 ở component
FancyButton
chỉ xuất hiện khi chúng ta định nghĩa component bằngReact.forwardRef
, còn với các function component hoặc class bình thường thì sẽ không nhận và trong props cũng sẽ không tồn tại biếnref
nha.
Forwarding refs in higher-order components
Đây là kỹ thuật khá là hữu ích với higher-order components (HOC)
. Cùng bắt đầu với một HOC component, component này cho phép chúng ta log lại sự thay đổi của props:
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return LogProps;
}
Component logProps
sẽ chuyển toàn bộ các props được truyền vào component mà nó đang bọc nên các component được bọc trong nó khi render sẽ không bị thay đổi gì, nhưng với logProps
chúng ta có thêm một chức năng nữa là log lại được sự thay đổi của props.
Và nó được sử dụng như này
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
Nhưng khi sử dụng HOC các bạn sẽ không sử dụng đc forwardRef
, vì sao lại như vậy, như đã nói ở ví dụ phía trên thì muốn sử dụng được forwardRef
thì component phải được khai báo với React.forwardRef
như ví dụ
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
Và nếu như bạn đưa ref vào HOC thì chắc chắn sẽ không sử dụng được bởi vì
- Thường thì HOC không phải là loại component
forwardRef
nên việc đưaref
vào sẽ ko nhận trong props - Khi sử dụng
forwardRef
thì sẽ ảnh hưởng tới HOC chứ không phải là các component được nó bọc.
Giống như ví dụ này:
import FancyButton from './FancyButton';
const ref = React.createRef();
// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
Khi các bạn đưa ref
vào FancyButton
thì thực tế ref
đang được đưa vào LogProps
và chúng ta ko thể sử dụng được.
May mắn thay, chúng ta có thể chuyển tiếp rõ ràng các ref đến thành phần FancyButton bên trong bằng API React.forwardRef. React.forwardRef chấp nhận chức năng render và nhận các props và ref và trả về React node
Với trường hợp trên chúng ta có thể xử lý như sau:
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Assign the custom prop "forwardedRef" as a ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
Displaying a custom name in DevTools
React.forwardRef
chấp nhận render function. React DevTools sử dụng function này để xác định những gì sẽ hiển thị cho ref component.
Ví dụ:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
Với ví dụ trên thì ở trên DevTool
sẽ hiển thị là ForwardRef
Còn nếu render với function thì DevTool
sẽ thêm tên của function vào
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
DevTool
sẽ hiển thị là ForwardRef(myFunction)
Và bạn có thể đặt tên cho dễ debug qua property displayName
function logProps(Component) {
class LogProps extends React.Component {
// ...
}
function forwardRef(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
// Give this component a more helpful display name in DevTools.
// e.g. "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name;
forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}
All rights reserved