در این قسمت میخوام مطلب مفاهیمی که باید در React اونا رو بدونین رو ادامه بدم و اطلاعات بیشتری رو در مورد مفاهیم React در اختیارتون قرار بدم.
در جلسه قبل در مورد Lifecycle methods و چرخه زندگی componentها توضیح دادیم و با نحوه عملکرد اونا آشنا شدیم. در این جلسه میخوام ادامه مفاهیمی که در قسمت قبل معرفی شد رو توضیح بدم که اولین مورد اون Higher-order component یا به اختصار HOC هست.
2. Higher-order component
به احتمال زیاد شما از Higher-order component در پروژههاتون استفاده کردید. متد connect مربوط به Redux یک تابع هست که یک HOC رو برگشت میده یا return میکنه. حالا میخوایم بررسی کنیم که HOCها دقیقا چی هستند و چه قابلیتهایی رو در اختیار ما قرار میدن.
در مستندات React بصورت زیر HOC تعریف شده است:
Higher-order component یک تابع هست که یک component رو به عنوان ورودی میگیره و یک component جدید رو return میکنه.
حالا به عنوان مثال به ساختار متد connect مربوط به Redux دقت کنید:
const hoc = connect(state => state); const WrappedComponent = hoc(SomeComponent);
وقتی تابع connect رو فراخوانی کنید، یک HOC در اختیار ما قرار میگیره که میتونیم componentهای دیگه رو به اون پاس بدیم و wrap کنیم و یک component جدید به وجود بیاد که اون رو در بالا در متغیر WrappedComponent قرار دادیم.
HOC به شما این امکان رو میده که کدها و logic هایی که بین componentها مشترک هستند رو فقط در یک جا تعریف کنید و از اون در هر چندتا component که بخواید استفاده کنید.
یک مثال خوب برای استفاده از HOC اجازه داشتن یا Authorization هست. شما میتونین کدهای مربوط به اعتبارسنجی رو جداگانه در هر component ای که بخواید بنویسید و قرار بدین. با اینکار کدهای تکراری و غیرضروری زیاد میشه و کدهای شما بیهوده افزایش پیدا میکنه.
اول بیاید ببینیم چطور ممکنه بدون استفاده از HOC مباحث مربوط به Authentication رو در component پیادهسازی کنیم. componentها ممکنه بصورت زیر تعریف بشن:
class RegularComponent extends React.Component { render() { if (this.props.isLoggedIn) { return <p>hi</p> } return <p>You're not logged in ☹️</p> } } // repeated code! class OtherRegularComponent extends React.Component { render() { if (this.props.isLoggedIn) { return <p>hi</p> } return <p>You're not logged in ☹️</p> } } // notice how we need different logic for functional components const FunctionalComponent = ({ isLoggedIn }) => ( isLoggedIn ? <p>Hi There</p> : <p>You're not logged in ☹️</p> )
همونطور که میبینید در همه componentهای بالا، کدهای تکراری وجود داره و خیلی جالب نیست و باید فکری به حال این همه تکرار کد بکنیم. اینجا هست که HOC به ما کمک میکنه. با استفاده از HOC میتونین componentهای بالا رو بصورت زیر تعریف کنید:
function AuthWrapper(WrappedComponent) { return class extends React.Component { render() { if (this.props.isLoggedIn) { return <WrappedComponent {...this.props} />; } return <p>You're not logged in ☹️</p>; }; }; } class RegularComponent extends React.Component { render() { return <p>hi</p>; }; } class OtherRegularComponent extends React.Component { render() { return <p>hello</p>; }; } const FunctionalComponent = () => (<p>Hi There</p>); const WrappedOne = AuthWrapper(RegularComponent); const WrappedTwo = AuthWrapper(OtherRegularComponent); const WrappedThree = AuthWrapper(FunctionalComponent);
همونطور که میبینید logic مشترک رو استخراج کرده و اون رو در یکجا تعریف کردیم. همونطور که میبینید یک HOC با نام AuthWrapper تعریف کردیم که در اون مسائل مربوط به Authentication چک میشه و حالا میتونین هر component ای که بخواید رو به این HOC پاس بدین و با اینکار componentهای مورد نظر از Authentication پشتیبانی خواهند کرد. با اینکار اگر نیاز به تغییر logic مربوط به Authentication باشه با تغییر دادن یک جا در کد همه موارد به روز رسانی میشه و لازم نیست که همه componentها رو تغییر بدین.
بهتره که HOCها رو به خوبی یاد گرفته و از اونا در پروژتون استفاده کنید.
3. کار با state و setState
به احتمال زیاد شما در پروژههاتون از state استفاده کردهاید. دانستن این نکته که با تغییر State به هر نحوی، component دوباره رندر میشه خیلی مهم هست (شما میتونین با استفاده از متد shouldComponentUpdate جلوی رندر شدنهای خاصی رو بگیرید).
حالا میخوایم در مورد تغییر دادن State توضیح بدیم. تنها روشی که برای تغییر دادن state وجود داره، استفاده از متد setState هست. این متد یک object میگیره و اون رو با state فعلی merge یا ادغام میکنه. در مورد متد setState نکاتی هست که باید اونا رو بدونین.
اولین نکتهای که باید بدونین اینه که متد setState بصورت Asynchronous عمل میکنه. یعنی هر زمان که شما متد setState رو فراخوانی میکنید، همون زمان state بروز رسانی نمیشه که اگر این نکته رو ندونین ممکنه که اذیتتون کنه و رفتار متناقضی رو از اون ببینید. کد زیر رو ببینید:
class App extends React.Component { constructor(props) { super(props); this.state = { counter: 0, }; } handleClick = () => { this.setState({ counter: this.state.counter + 1, }); console.log(this.state.counter); } render() { return ( <div> <button onClick={this.handleClick}>Click me</button> <p>{this.state.counter}</p> </div> ); }; }
همونطور که میبینید مقدار counter رو در State صفر قرار دادیم و در متد handleClick یکی یکی به اون اضافه کردیم و اگر بر روی دکمه کلیک کنیم، counter یکی یکی زیاد میشه و نمایش داده میشه. همونطور که میبینید در آخر متد handleClick یک console.log قرار داده شده که مقدار this.state.counter رو چاپ کنه. ولی مقداری که انتظار داریم در console چاپ نمیشه و به خاطر Asynchronous بودن متد setState نمیشه اون رو به این شکل چاپ کرد.
همانند متدهای Asynchronous دیگه میتونین یک callback برای اون تعریف کنید و بصورت مستقیم و فوری بعد از تعریف کردن state به اون دسترسی داشته باشیم. برای اینکار بصورت زیر عمل میکنیم:
class App extends React.Component { constructor(props) { super(props); this.state = { counter: 0, }; } handleClick = () => { this.setState({ counter: this.state.counter + 1, }, () => { console.log(this.state.counter); }); } render() { return ( <div> <button onClick={this.handleClick}>Click me</button> <p>{this.state.counter}</p> </div> ); }; }
با اینکار مشکل مورد نظر برطرف میشه. در مواردی که نیاز هست از مقدار state قبلی در state جدید استفاده بشه بهتره که به شکل بالا عمل نکنید و به جای پاس دادن یک object یا شئ به متد setState یک تابع رو پاس بدیم و از state قبلی و اطلاعات اون استفاده کنیم. کد بالا رو میتونیم بصورت زیر بازنویسی کنیم:
class App extends React.Component { constructor(props) { super(props); this.state = { counter: 0, }; } handleClick = () => { this.setState((prevState) => { return { counter: prevState.counter + 1, }; }, () => { console.log(this.state.counter); }); } render() { return ( <div> <button onClick={this.handleClick}>Click me</button> <p>{this.state.counter}</p> </div> ); }; }
این روش مشکل رو برطرف میکنه ولی چرا به جای یک object یک تابع رو به setState پاس دادیم؟ بخاطر اینکه setState بصورت Asynchronous عمل میکنه و بروز شدن مقادیر درون اون تلههایی داره که با استفاده از تابع در این تلهها نمیوفتیم و مشکلی پیش نمیاد. مثلا فرض کنید که 2 تا setState پشت سر هم قرار بدیم و هر دو میخوان مقدار counter رو یکی بالا ببرند.
استفاده از تابع به جای object در متد setState دو مزیت داره. اولین مورد اینکه یک کپی استاتیک از State رو در اختیارمون قرار میده و تغییر نمیکنه. دومین مزیت اینه که فراخوانی متدهای setState رو در صف قرار میده و اونا رو پشت سر هم اجرا میکنه.
در ابتدا میخوام مثالی رو بررسی کنم که 2 تا setState پشت سر هم تعریف بشن. برای اینکار بصورت زیر عمل میکنیم:
class App extends React.Component { constructor(props) { super(props); this.state = { counter: 0, }; } handleClick = () => { this.setState({ counter: this.state.counter + 1 }); this.setState({ counter: this.state.counter + 1 }); } render() { console.log(this.state.counter); return ( <div> <button onClick={this.handleClick}>Click me</button> <p>{this.state.counter}</p> </div> ); }; }
همونطور که میبینید در هر دو setState بالا، میخوان یکی به
اضافه کنند و همونطور که قبلا هم گفته شد، نیز بعد از استفاده از setState باز هم صفر میمونه و اگر console رو ببینیم، مقدار counter با کلیک کردن بر روی Click me یکی یکی اضافه میشه و 2 تا 2 تا اضافه نمیشه.برای حل این مشکل بجای object از تابع استفاده میکنیم. بصورت زیر:
class App extends React.Component { constructor(props) { super(props); this.state = { counter: 0, }; } handleClick = () => { this.setState((prevState) => ({ counter: prevState.counter + 1 })); this.setState((prevState) => ({ counter: prevState.counter + 1 })); } render() { console.log(this.state.counter); return ( <div> <button onClick={this.handleClick}>Click me</button> <p>{this.state.counter}</p> </div> ); }; }
با این کار setStateها صفبندی میشن و پشت سر هم اجرا میشن و علاوه بر این ویژگی یک snapshot یا کپی از State رو در اختیارمون قرار میدن و دیگه از مقدار فعلی و بروز نشده state استفاده نمیکنند. با اینکار نتیجه مورد نظر به دست میاد و با کلیک بر روی دکمه Click me مقدار counter دو تا دو تا بالا میره و در console چاپ میشه.
با دونستن این موارد حالا شما تقریبا همه چیز رو در مورد state و setState در React میدونین و میتونین به خوبی از اونا استفاده کنید.
خب فقط یک مورد از مفاهیم React که قرار بود در مورد اون بهتون توضیح بدم باقی میمونه که اون هم React context هست که در قسمت بعدی بصورت کامل در مورد اون توضیح میدم.
سلام..خیلی خیلی ممنونم
تو سایت های ایرانی و همینطور مقاله های مختلف رو خوندم به جرات میگم مقاله ی شما در مورد HOC یکی از بهترین ها بود برای فهمیدنش
با مثال ساده و عالی خیلی خوب توضیح دادین ممنونم از شما
ممنون از مطالب خوبتون