فوروارد کردن ref ها
ref فوروارد کردن یک تکنیک برای ارسال خودکار یک ref از طریق یک کامپوننت به فرزندانش میباشد. این کار معمولا برای بسیاری از کامپوننتها در اپلیکیشن لازم نیست. به هر حال، در بعضی از کامپوننتها، به خصوص در کامپوننتهای کتابخانهای با قابلیت استفاده مجدد میتواند مفید باشد. رایجترین حالات ممکن در ادامه شرح داده شدهاند.
فوروارد کردن ref ها به کامپوننتهای DOM
یک کامپوننت FancyButton
که المنت محلی button
در DOM را رندر میکند در نظر بگیرید:
function FancyButton(props) {
return (
<button className="FancyButton">
{props.children}
</button>
);
}
کامپوننتهای ریاکت جزئیات پیاده سازی، شامل خروجی رندر شده خود را پنهان میکنند. کامپوننتهای دیگر که از FancyButton
استفاده میکنند، معمولا به بدست آوردن یک ref به المنت DOM button
داخلی نیازی نخواهند داشت و این به علت جلوگیری از تکیه بیش از اندازه کامپوننتها به ساختار DOM یکدیگر خوب است.
اگرچه چنین کپسولهسازی برای کامپوننتهایی در سطح اپ مانند FeedStory
یا Comment
مطلوب است، برای کامپوننتهای کوچک که قابلیت استفاده مجدد بسیار زیادی دارند مانند FancyButton
یا MyTextInput
نامناسب میباشد. این کامپوننتها میل به استفاده شدن در اپ به یک شیوه مشابه به عنوان یک button
و input
معمولی DOM دارند و دسترسی به نودهای DOM آن ها برای مدیریت انیمیشنها، focus و selection ممکن است اجتناب ناپذیر باشد.
فوروارد کردن ref یک قابلیت انتخابی است که به بعضی کامپوننت ها اجازه گرفتن یک ref
و انتقال آن به فرزندان (به عبارت دیگر, “فوروارد کردن” آن) را میدهد.
در مثال زیر، FancyButton
از React.forwardRef
برای گرفتن ref
که به آن منتقل شده استفاده میکند، و سپس آن را به button
DOM که رندر میکند فوروارد میکند.
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>;
با این روش، کامپوننتهایی که از FancyButton
استفاده میکنند، میتوانند یک ref به نود button
در DOM بگیرند و در صورت نیاز، به همان شکلی که از یک button
در DOM مستقیما استفاده می کنند، از این کامپوننت نیز بهره ببرند.
در زیر، یک توضیح مرحله به مرحله از آنچه در مثال بالا اتفاق افتاده است را مشاهده مینمایید:
- ما با فراخوانی
React.createRef
و اختصاص آن به متغیرref
یک ref ریاکت ایجاد میکنیم. - با مشخص کردن
ref
به عنوان یک خصوصیت JSX، آن را به<FancyButton ref={ref}>
منتقل میکنیم. - ریاکت آن
ref
را به عنوان آرگومان دوم تابع(props, ref) => ...
درونforwardRef
پاس میدهد. - ما این آرگومان
ref
را با تعیین کردن آن به عنوان یک خصوصیت JSX به<button ref={ref}>
فوروارد میکنیم. - وقتی که ref متصل شدهاست،
ref.current
به نود<button>
در DOM اشاره میکند.
نکته
آرگومان دوم
ref
فقط زمانی که یک کامپوننت را باReact.forwardRef
فراخوانی کنید موجود است. کامپوننتهای تابعی و بر پایه کلاس معمولی، آرگومانref
را دریافت نمیکنند و ref در props نیز در دسترس نمیباشد.فوروارد کردن ref محدود به کامپوننتهای DOM نیست. شما میتوانید ref ها را به instance های کامپوننتهای بر پایه کلاس نیز فوروارد کنید.
نکته برای نگهدارندگان کتابخانه کامپوننتها
زمانی که شما شروع به استفاده از forwardRef
در کامپوننت یک کتابخانه میکنید، باید به عنوان یک تغییر مخرب با آن رفتار کنید و یک ورژن major جدید از کتابخانه خود منتشر نمایید. به این علت که کتابخانه شما، یک رفتار متفاوت قابل توجه (مانند چیزی که ref به آن تخصیص داده شده و چه type هایی export شدهاند) خواهد داشت و باعث تخریب برنامهها و کتابخانههای دیگر که به رفتار قدیمی وابسته هستند میشود.
استفاده از React.forwardRef
مشروط به اینکه وجود داشتهباشد نیز به دلیل یکسان پیشنهاد نمیشود: چرا که چگونگی رفتار کتابخانه شما را تغییر می دهد و برنامه کاربرانتان را زمانی که ریاکت خود را ارتقا میدهد خراب میکند.
فوروارد کردن ref ها در کامپوننتهای مرتبه بالاتر
این تکنیک در کامپوننت های مرتبه بالاتر (شناخته شده به نام HOC ها) نیز، می تواند مفید باشد. بیایید با یک مثال از HOC که prop های کامپوننت را در کنسول، نمایش میدهد شروع کنیم:
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;
}
کامپوننت مرتبه بالاتر “logProps” کل props
را به کامپوننتی که پوشش دادهاست منتقل میکند، بنابراین خروجی رندر شده یکسان خواهد بود. برای مثال، میتوانیم از این HOC برای مشاهده تمامی prop هایی که به کامپوننت “fancy button” ما منتقل شدهاست استفاده کنیم:
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
فقط یک اخطار در مورد مثال بالا وجود دارد: ref ها منتقل نمیشوند. به علت اینکه ref
یک prop نیست. مانند key
، توسط ریاکت به شکل متفاوتی کنترل میشود. اگر شما یک ref به HOC اضافه کنید، ref به جای کامپوننت دربر گیرنده، به بیرونی ترین کامپوننت پوشاننده (container) اشاره می کند.
این به این معناست که ref تعیین شده برای FancyButton
، در اصل به کامپوننت LogProps
متصل خواهد شد:
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}/>;
خوشبختانه، با استفاده از API های React.forwardRef
، می توانیم به طور صریح ref ها را به کامپوننت داخلی FancyButton
فوروارد کنیم. React.forwardRef
یک تابع رندر که پارامترهای props
و ref
را دریافت میکند و یک نود ریاکت را به عنوان خروجی میدهد را میپذیرد. برای مثال:
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} />; });}
نمایش یک نام سفارشی در DevTools
React.forwardRef
یک تابع رندر میپذیرد. React DevTools از این تابع برای بررسی اینکه چه چیزی را برای کامپوننت ref forwarding نمایش دهد استفاده میکند.
برای مثال، کامپوننت زیر به عنوان ”ForwardRef” در DevTools ظاهر میشود:
const WrappedComponent = React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
اگر شما تابع رندر را نامگذاری کنید، DevTools نام آنرا نیز بهکار میبرد. (برای مثال ”ForwardRef(myFunction)”):
const WrappedComponent = React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
شما حتی میتوانید ویژگی 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);
}