البته این Lifecycle Hooks ای که در اینجا نام برده شدند، بیشترین استفاده را دارند. در مستندات React مواردی دیگری نیز ذکر شدهاند که در عمل آنچنان مورد استفاده قرار نمیگیرند.
مرحلهی Mount
در کامپوننت App، یک constructor را اضافه میکنیم تا بتوان مرحلهی Mount را بررسی کرد. این سازنده تنها یکبار در زمان وهله سازی این کامپوننت فراخوانی میشود. یکی از کاربردهای آن میتواند مقدار دهی اولیهی خواص این وهله باشد. برای مثال یکی از کاربردهای آن میتواند مقدار دهی اولیهی state بر اساس مقادیر props رسیده باشد. در اینجا است که میتوان خاصیت state را مستقیما مقدار دهی کرد (مانند this.state = this.props.something) و در این حالت نیازی به فراخوانی متد this.setState نیست و اگر فراخوانی شود، یک خطا را دریافت میکنیم. از این جهت که this.setState را تنها زمانیکه کامپوننتی رندر شده و در DOM قرار گرفته باشد، میتوان فراخوانی کرد.
یک نکته: فراخوانی this.state = this.props.something در سازندهی کلاس میسر نیست، مگر اینکه props را به صورت پارامتر به سازندهی کلاس و سازندهی base class توسط متد super ارسال کنیم:
constructor(props) { super(props); console.log("App - constructor"); this.state = this.props.something; }
class App extends Component { constructor() { super(); console.log("App - constructor"); } componentDidMount() { // Ajax calls console.log("App - mounted"); }
سومین lifecycle hooks در مرحلهی mounting، متد رندر است که در اینجا به ابتدای آن، یک console.logرا جهت بررسی بیشتر اضافه میکنیم:
render() { console.log("App - rendered");
در اینجا ترتیب فراخوانی این متدها را مشاهده میکنید. ابتدا سازندهی کلاس فراخوانی شدهاست. سپس در مرحلهی رندر، یک المان React که در DOM مجازی React قرار میگیرد، بازگشت داده میشود. سپس React این DOM مجازی را با DOM اصلی هماهنگ میکند. پس از آن مرحلهی Mount فرا میرسد. یعنی در این مرحله، کامپوننت در DOM اصلی قرار دارد. اینجا است که اعمال Ajax ای دریافت اطلاعات از سرور باید انجام شوند.
یک نکته: در مرحلهی رندر، تمام فرزندان یک کامپوننت نیز به صورت بازگشتی رندر میشوند. برای نمایش این ویژگی، به متد Render کامپوننتهای NavBar، Counters و Counter، متد console.log ای را جهت درج این مرحله در کنسول، اضافه میکنیم:
class Counter extends Component { render() { console.log("Counter - rendered"); //... class Counters extends Component { render() { console.log("Counters - rendered"); //... const NavBar = ({ totalCounters }) => { console.log("NavBar - rendered"); //...
پس از این تغییرات و ذخیره سازی برنامه، با بارگذاری مجدد آن در مرورگر، چنین خروجی در کنسول توسعه دهندگان مرورگر ظاهر میشود:
همانطور که مشاهده میکنید، پس از فراخوانی App - rendered، تمام فرزندان کامپوننت App رندر شدهاند و در آخر به App - mounted رسیدهایم.
مرحلهی Update
مرحلهی Update زمانی رخ میدهد که state و یا props یک کامپوننت تغییر میکنند. برای مثال با کلیک بر روی دکمهی Increment، وضعیت کامپوننت به روز رسانی میشود. پس از آن فراخوانی خودکار متد رندر در صف قرار میگیرد. به این معنا که تمام فرزندان آن نیز قرار است مجددا رندر شوند. برای آزمایش آن، یکبار لاگهای کنسول توسعه دهندگان مرورگر را پاک کنید. سپس بر روی دکمهی Increment کلیک کنید:
همانطور که ملاحظه میکنید با کلیک بر روی دکمهی Increment، کل Component tree برنامه مجددا رندر شدهاست. البته این مورد به معنای به روز رسانی کل DOM اصلی در مرورگر نیست. زمانیکه کامپوننتی رندر میشود، فقط یک React element حاصل آن خواهد بود که در نتیجهی آن DOM مجازی React به روز رسانی خواهد شد. سپس React، کپی DOM مجازی قبلی را با نمونهی جدید آن مقایسه میکند. در آخر، محاسبهی تغییرات صورت گرفته و تنها بر اساس موارد تغییر یافتهاست که DOM اصلی را به روز رسانی میکند. به همین جهت زمانیکه بر روی دکمهی Increment کلیک میشود، فقط span کنار آن در DOM اصلی به روز رسانی میشود. برای اثبات آن در مرورگر بر روی المان span که شمارهها را نمایش میدهد، کلیک راست کرده و گزینهی inspect را انتخاب کنید. سپس بر روی دکمهی Increment کلیک نمائید. مرورگر قسمتی را که به روز میشود، با رنگی مشخص و متمایز، به صورت لحظهای نمایش میدهد.
متد componentDidUpdate، پس از به روز رسانی کامپوننت فراخوانی میشود. به این معنا که در این حالت وضعیت و یا props جدیدی را داریم. در این حالت میتوان این اشیاء به روز شده را با نمونههای قبلی آنها مقایسه کرد و در صورت وجود تغییری، برای مثال یک درخواست Ajax ای را به سمت سرور برای دریافت اطلاعات تکمیلی ارسال کرد و در غیراینصورت خیر. بنابراین میتوان به آن به عنوان یک روش بهینه سازی نگاه کرد. برای نمایش این قابلیت میتوان متد componentDidUpdate را که مقادیر قبلی props و state را دریافت میکند، لاگ کرد:
class Counter extends Component { componentDidUpdate(prevProps, prevState) { console.log("Counter - updated", { prevProps, prevState }); if (prevProps.counter.value !== this.props.counter.value) { // Ajax call and get new data } }
همانطور که مشاهده میکنید، مقدار شیء counter، پیش از کلیک بر روی دکمهی Increment، مساوی 4 بودهاست. در یک چنین حالتی میتوان مقدار قبلی prevProps.counter.value را با مقدار جدید this.props.counter.value مقایسه کرد و در صورت نیاز یک درخواست Ajax ای را برای دریافت اطلاعات به روز، صادر کرد.
مرحلهی Unmount
در این مرحله تنها یک lifecycle hook به نام componentWillUnmount قابل تعریف است که درست پیش از حذف یک کامپوننت از DOM فراخونی میشود.
class Counter extends Component { componentWillUnmount(){ console.log("Counter - Unmount"); }
در اینجا پس از حذف یک کامپوننت، state کامپوننت App تغییر کردهاست. به همین جهت کل Component tree رندر مجدد شدهاست. اینبار یک DOM مجازی جدید را داریم که تعداد Counterهای آن 3 مورد است. سپس React این DOM مجازی جدید را با نمونهی قبلی خود مقایسه کرده و متوجه میشود که یکی از Counterها حذف شدهاست. در ادامه متد componentWillUnmount را پیش از حذف این Counter از DOM، فراخوانی میکند. به این ترتیب فرصت خواهیم یافت تا رهاسازی منابع را در صورت نیاز انجام دهیم تا برنامه دچار نشتی حافظه نشود.
یک مثال: افزودن دکمهی Decrement به کامپوننت Counter
در ادامه میخواهیم دکمهای را برای کاهش مقدار یک شمارشگر، به کامپوننت Counter اضافه کنیم. همچنین اگر مقدار value شمارشگر مساوی صفر بود، دکمهی کاهش مقدار آن باید غیرفعال شود و برعکس. به علاوه از سیستم طرحبندی بوت استرپ نیز برای تعریف دو ستون، یکی برای نمایش مقدار شمارشگرها و دیگری برای نمایش دکمهها استفاده خواهیم کرد.
برای پیاده سازی آن ابتدا متد رندر کامپوننت Counter را به صورت زیر تغییر میدهیم:
class Counter extends Component { render() { console.log("Counter - rendered"); return ( <div className="row"> <div className="col-1"> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> </div> <div className="col"> <button onClick={() => this.props.onIncrement(this.props.counter)} className="btn btn-secondary btn-sm" > + </button> <button onClick={() => this.props.onDecrement(this.props.counter)} className="btn btn-secondary btn-sm m-2" disabled={this.props.counter.value === 0 ? "disabled" : ""} > - </button> <button onClick={() => this.props.onDelete(this.props.counter.id)} className="btn btn-danger btn-sm" > Delete </button> </div> </div> ); }
در این بین، دکمهی جدید کاهش مقدار را که با یک - مشخص شدهاست نیز مشاهده میکنید. رویدادگردان onClick آن به this.props.onDecrement اشاره میکند. همچنین ویژگی disabled نیز به آن اضافه شدهاست تا بر اساس مقدار value شیء counter، در مورد فعال یا غیرفعالسازی دکمه تصمیم گیری کند.
پس از آن نیاز است این this.props.onDecrement را تعریف کنیم. به همین جهت به والد آن که کامپوننت Counters است مراجعه کرده و آنرا به صورت زیر تغییر میدهیم:
<Counter key={counter.id} counter={counter} onDelete={this.props.onDelete} onIncrement={this.props.onIncrement} onDecrement={this.props.onDecrement} />
<Counters counters={this.state.counters} onReset={this.handleReset} onIncrement={this.handleIncrement} onDecrement={this.handleDecrement} onDelete={this.handleDelete} />
handleDecrement = counter => { console.log("handleDecrement", counter); const counters = [...this.state.counters]; // cloning an array const index = counters.indexOf(counter); counters[index] = { ...counter }; // cloning an object counters[index].value--; console.log("this.state.counters", this.state.counters[index]); this.setState({ counters }); };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-09.zip