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
Pertimbangkan sebuah komponen FancyButton
yang me-render native DOM element yaitu button
:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
Komponen React menyembunyikan detail implementasi mereka, termasuk keluaran render mereka. Komponen-komponen lainnya yang menggunakan FancyButton
biasanya tidak perlu untuk mendapatkan sebuah ref ke dalam DOM element button
. Ini bagus karena hal ini mencegah komponen untuk bersandar pada struktur DOM komponen lain yang berlebihan.
Meskipun enkapsulasi seperti itu diperlukan oleh komponen yang berada di application-level seperti FeedStory
atau Comment
, namun hal tersebut dapat menyulitkan komponen-komponen “leaf” yang sering digunakan kembali seperti FancyButton
atau MyTextInput
. Komponen ini cenderung digunakan di seluruh aplikasi dengan cara yang sama seperti regular DOM button
dan input
, dan mungkin tidak dapat dihindari mengakses DOM nodes mereka untuk mengelola focus, selection, atau animation.
Ref forwarding adalah sebuah fitur keikutsertaan yang memperbolehkan beberapa komponen-komponen mengambil sebuah ref
yang didapatkan, dan menurunkannya (dengan kata lain, “meneruskan” nya) kepada child.
Pada contoh di bawah ini, FancyButton
menggunakan React.forwardRef
untuk mendapatkan ref
yang diteruskan kepadanya, lalu meneruskannya ke button
DOM yang di render:
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>;
Dengan cara ini, komponen-komponen yang menggunakan FancyButton
bisa mendapatkan ref ke DOM node button
yang mendasarinya dan mengaksesnya jika perlu - sama seperti jika mereka menggunakan button
DOM secara langsung.
Berikut adalah penjelasan langkah demi langkah tentang apa yang terjadi pada contoh di atas:
- Kita buat sebuah React ref dengan memanggil
React.createRef
dan masukan kedalam variabelref
. - Kita teruskan
ref
tersebut ke<FancyButton ref={ref}>
dengan menulisnya sebagai atribut JSX. - React meneruskan
ref
ke fungsi(props, ref) => ...
di dalamforwardRef
sebagai argumen kedua. - Kita teruskan argumen
ref
tersebut ke<button ref={ref}>
dengan menulisnya sebagai atribut JSX. - Jika ref sudah terpasang,
ref.current
akan mengarah ke DOM node<button>
.
Catatan
Argumen
ref
kedua hanya ada saat Anda mendefinisikan komponen denganReact.forwardRef
. Regular function atau class components tidak menerima argumenref
tersebut, dan ref juga tidak teredia di props.Ref forwarding tidak terbatas pada komponen DOM. Anda juga dapat meneruskan refs ke class component instances.
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);
}