Forwarding Refs
Ref forwarding adalah sebuah teknik untuk meneruskan ref secara otomatis melalui komponen ke salah satu anaknya. Ini biasanya tidak diperlukan untuk sebagian besar komponen dalam aplikasi. Namun, ini bisa berguna untuk beberapa jenis komponen, terutama di pustaka komponen yang dapat digunakan kembali. Skenario paling umum dijelaskan di bawah ini.
Forwarding refs ke komponen DOM
Consider a FancyButton
component that renders the native button
DOM element:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
React components hide their implementation details, including their rendered output. Other components using FancyButton
usually will not need to obtain a ref to the inner button
DOM element. This is good because it prevents components from relying on each other’s DOM structure too much.
Although such encapsulation is desirable for application-level components like FeedStory
or Comment
, it can be inconvenient for highly reusable “leaf” components like FancyButton
or MyTextInput
. These components tend to be used throughout the application in a similar manner as a regular DOM button
and input
, and accessing their DOM nodes may be unavoidable for managing focus, selection, or animations.
Ref forwarding is an opt-in feature that lets some components take a ref
they receive, and pass it further down (in other words, “forward” it) to a child.
Pada contoh di bawah ini, FancyButton
menggunakan React.forwardRef
to obtain the ref
passed to it, and then forward it to the DOM button
that it renders:
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>;
This way, components using FancyButton
can get a ref to the underlying button
DOM node and access it if necessary—just like if they used a DOM button
directly.
Berikut adalah penjelasan langkah demi langkah tentang apa yang terjadi pada contoh di atas:
- We create a React ref by calling
React.createRef
and assign it to aref
variable. - We pass our
ref
down to<FancyButton ref={ref}>
by specifying it as a JSX attribute. - React passes the
ref
to the(props, ref) => ...
function insideforwardRef
as a second argument. - We forward this
ref
argument down to<button ref={ref}>
by specifying it as a JSX attribute. - When the ref is attached,
ref.current
will point to the<button>
DOM node.
Catatan
The second
ref
argument only exists when you define a component withReact.forwardRef
call. Regular function or class components don’t receive theref
argument, and ref is not available in props either.Ref forwarding is not limited to DOM components. You can forward refs to class component instances, too.
Catatan untuk pengelola pustaka komponen
Saat Anda mulai menggunakan forwardRef
di pustaka komponen, Anda harus memperlakukannya sebagai sebuah breaking change dan merilis versi mayor baru pustaka Anda. Ini karena pustaka Anda kemungkinan besar memiliki perilaku yang sangat berbeda (seperti refs apa yang ditetapkan, dan tipe apa yang diekspor), dan ini bisa merusak aplikasi dan pustaka lain yang bergantung pada perilaku lama.
Menerapkan React.forwardRef
secara kondisional jika ada juga tidak disarankan karena alasan yang sama: ini mengubah cara pustaka Anda berperilaku dan bisa merusak aplikasi pengguna Anda saat mereka meningkatkan versi React itu sendiri.
Forwarding refs dalam higher-order components
This technique can also be particularly useful with higher-order components (also known as HOCs). Let’s start with an example HOC that logs component props to the console:
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;
}
The “logProps” HOC passes all props
through to the component it wraps, so the rendered output will be the same. For example, we can use this HOC to log all props that get passed to our “fancy button” component:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
There is one caveat to the above example: refs will not get passed through. That’s because ref
is not a prop. Like key
, it’s handled differently by React. If you add a ref to a HOC, the ref will refer to the outermost container component, not the wrapped component.
This means that refs intended for our FancyButton
component will actually be attached to the LogProps
component:
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}/>;
Fortunately, we can explicitly forward refs to the inner FancyButton
component using the React.forwardRef
API. React.forwardRef
accepts a render function that receives props
and ref
parameters and returns a React node. For example:
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} />; });}
Menampilkan nama custom di DevTools
React.forwardRef
menerima fungsi render. React DevTools menggunakan fungsi ini untuk menentukan apa yang akan ditampilkan untuk komponen ref forwarding.
Sebagai contoh, komponen berikut akan muncul sebagai ”ForwardRef” di DevTools:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
Jika Anda menamai fungsi render, DevTools juga akan menyertakan namanya (misalnya ”ForwardRef(myFunction)”):
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
Anda bahkan dapat menyetel properti displayName
pada fungsi untuk menyertakan komponen yang Anda bungkus:
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);
}