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.
Mengoper 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>
));
// Sekarang Anda bisa mendapatkan ref langsung ke button DOM:
const ref = React.createRef();
<FancyButton ref={ref}>Klik saya!</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.
Mengoper refs dalam higher-order components
Teknik ini juga bisa menjadi sangat berguna untuk higher-order components (biasa disebut dengan HOCs). Mari kita mulai dengan contoh HOC yang mengeluarkan component props ke log konsol:
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;
}
HOC “logProps” meneruskan semua props
ke komponen yang dibungkusnya, sehingga keluaran yang dirender akan sama. Misalnya, kita dapat menggunakan HOC ini untuk mengeluarkan semua props ke log konsol yang diteruskan ke “fancy button” komponen kita:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Daripada mengekspor FancyButton, kita mengekspor LogProps.
// LogProps tetap akan me-render FancyButton.
export default logProps(FancyButton);
Ada satu peringatan untuk contoh diatas: refs tidak akan diteruskan. Itu karena ref
bukan sebuah prop. Seperti key
, ini ditangani secara berbeda oleh React. Jika Anda menambahkan ref ke HOC, maka ref akan merujuk ke komponen container terluar, bukan komponen yang dibungkus.
Ini berarti bahwa ref yang dimaksudkan untuk komponen FancyButton
kita sebenarnya akan dilampirkan ke LogProps
komponen:
import FancyButton from './FancyButton';
const ref = React.createRef();
// Komponen FancyButton yang kita impor adalah HOC LogProps.
// Meskipun keluaran yang dirender akan sama,
// Ref kita akan menunjuk ke komponen LogProps daripada komponen FancyButton!
// Ini berarti kita tidak dapat memanggil ref, seperti ref.current.focus()
<FancyButton
label="Klik saya!"
handleClick={handleClick}
ref={ref}/>;
Untungnya, kita dapat secara eksplisit meneruskan refs kedalam komponen FancyButton
menggunakan React.forwardRef
API. React.forwardRef
menerima sebuah fungsi render yang menerima parameter props
dan ref
dan mengembalikan React node. Sebagai contoh:
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;
// Masukan prop kustom "forwardedRef" sebagai ref
return <Component ref={forwardedRef} {...rest} />; }
}
// Catat param kedua "ref" yang disediakan oleh React.forwardRef.
// Kita dapat meneruskannya ke LogProps sebagai regular prop, misalnya "forwardedRef"
// Dan kemudian dapat dilampirkan ke Komponen.
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} />;
}
// Beri komponen ini nama tampilan yang lebih berguna di DevTools.
// misalnya, "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}