Ring UI library gives you the power of complex UI controls that have been developed at JetBrains over the years. It contains over 50 React controls, ranging from simple links and buttons to sophisticated controls, such as Date Picker or Data List.
تا به اینجا مثالهایی که زدهایم تاثیر کامپوننتهای React را بر روی UI، نشان دادند. در این بخش به رویدادهای سمت UI و ورودیهای کاربر میپردازیم.
رویدادهای ترکیبی React
React روش مدیریت رویدادهای خودش را دارد و به آنها رویدادهای Synthetic یا ترکیبی گفته میشود. در زیر مقایسهای داریم از رویدادهای معمول در JavaScript و رویدادهای React و تفاوتها را بررسی میکنیم.
<!-- HTML Buttons --> <button type="button" onclick="console.log('Button Clicked')">Click Me</button> // React Buttons <button type="button" onClick={console.log("Button Clicked")}>Click Me</button>
- باید نام رویدادها را بصورت camelCase تایپ کنیم.
- از جاوااسکریپت به طور مستقیم استفاده میکنیم؛ نه بین quotation markها.
- برای رویدادها از توابع استفاده میکنیم و بهتر است تابع اجرایی هر رویداد در خود کامپوننت ساخته شود.
- رویداد onClick در React به نوعی override شده رویداد onclick مرورگر است و به جای آن عمل میکند.
رفتار رویدادهای React در مرورگرهای مختلف یکسان است. برای مثال رویداد onChange هر تغییری را برای هر نوع تگ ورودی اعمال میکند. هر کلیدی که در یک input یا textarea زده شود، اگر یک check box را انتخاب یا از انتخاب خارج کنیم و یا اگر موردی را از یک drop-down انتخاب کنیم، React رویداد onChange را اجرا میکند. React اکثر رویدادهای مرسوم را پوشش میدهد و همچنین رویدادهایی را برای کار با کلیپبرد، رسانههای مختلف و تصاویر دارد. برای اطلاعات بیشتر به مستندات آن رجوع کنید.
وقتی با کتابخانه React کار میکنیم، همه چیز مجازی اتفاق میافتد؛ مانند ساخت تگ و نمایش آنها، همچنین مدیریت تگها و رویدادها. اما به این معنا نیست که ارتباط React با HTML DOM در مرورگر قطع است. اگر لازم باشد به HTML DOM در کامپوننتها دسترسی داشته باشیم میتوانیم از خاصیت ref در React استفاده کنیم. برای مثال فرض کنید یک ورودی را برای ایمیل بهصورت <input type="email" /> تعریف کردهایم. میخواهیم پیش از ذخیره بدانیم آیا داده وارد شده به فرمت ایمیل هست یا نه.
const EmailForm = React.createClass({ clickHandler() { if (this.inputEmail.checkValidity()) console.log("Email is OK to save it."); else console.log("Email is not in right format."); }, render() { return ( <div> <input type="email" ref={inputEmail => this.inputEmail = inputEmail} /> <button type="submit" onClick={this.clickHandler}>Save</button> </div> ) }
در مثال بالا clickHandler وظیفه مدیریت رویداد کلیک دکمه را به عهده دارد. در ادامه، وقتی از خاصیت ref در تگ input استفاده میکنیم و مقدار آن را یک تابع قرار میدهیم، React این تابع را زمانیکه کامپوننت به طور کامل در HTML DOM ساخته شد، اجرا میکند. React همچنین ارجاعی را به عنوان پارامتر این تابع به DOM همراه با تابع ارسال میکند (inputEmail). داخل تابع ref میتوانیم به نمونه ساخته شده از کامپوننت در DOM دسترسی داشته باشیم. inputEmail که به صورت ارجاع به تابع فرستاده شده، تگ ساخته شده input را برمیگرداند، در نتیجه میتوانیم در کامپوننت به آن دسترسی داشته باشیم.
تغییر وضعیت کامپوننت
اگر از کامپوننتهای Sateful که دارای وضعیت هستند استفاده میکنیم، میتوانیم وضعیت کامپوننت را بر اساس ورودیهای کاربر تغییر دهیم. مثال بالا را به این شکل تغییر میدهیم که در ابتدا وضعیت کامپوننت، یک ایمیل پیشفرض باشد و اگر کاربر آدرس متفاوتی را وارد کرد، آدرس جدید به عنوان وضعیت جدید کامپوننت در نظر گرفته شود.
const EmailForm = React.createClass({ getInitialState() { return { currentEmail: this.props.currentEmail } }, setCurrentEmailState(se) { this.setState({ currentEmail: se.target.value }); }, clickHandler() { if (this.inputEmail.checkValidity()) console.log("Email is OK to save it."); else console.log("Email is not in right format."); }, render() { return ( <div> <input type="email" ref={inputEmail => this.inputEmail = inputEmail} value={this.state.currentEmail} onChange={this.setCurrentEmailState} /> <button type="submit" onClick={this.clickHandler}>Save</button> </div> ) } })
در خط 20 از مثال بالا با قرار دادن مقدار value برابر با ایمیل جاری (وضعیت کامپوننت)، کاربر آدرس پیشفرض را در input میبیند، اما هیچ تغییری را نمیتواند در آن ایجاد کند و input عملا تبدیل به یک تگ فقط خواندنی میشود. علت این است که React دو وضعیت را ایجاد کرده، یکی در حافظه به عنوان وضعیت پیشفرض و دیگری وضعیتی که در DOM ساخته. وقتی در سطح DOM تغییری را ایجاد میکنیم، React به صورت خودکار متوجه آن نمیشود و ما باید با روشی React را در جریان این تغییرات قرار دهیم! برای این کار رویداد onChange را برای تگی که قرار است تغییر کند پیادهسازی میکنیم. در مثال بالا متد setCurrentEmailState و رویداد onChange برای همین منظور به کار گرفته شدهاند.
در قسمت بعد که آخرین قسمت است، به مسئله چرخه زندگی (Lifecycle) کامپوننتهای React میپردازیم.
تا اینجای کار ساخت کامپوننتها را با React.createClass که تفاوتی با توسعه (ارث بری) از کلاس React.Component ندارد، انجام دادهایم. اما ساخت کامپوننتها به صورت یک تابع هم مزیتهایی را دارد. اول از همه باید بدانیم که ساخت کامپوننت توسط تابع، بدون وضعیت خواهد بود که به آن Stateless میگویند. به دلیل نداشتن وضعیت، کامپوننتهای تابعی را کمی بهتر میشود برای استفاده مجدد به کار برد. در کامپوننتهای غیر تابعی که Stateful هستند به دلیل احتمال وابستگی وضعیت کامپوننت به خارج از کلاس، مانند مثال قسمت چهارم که کامپوننت در انتظار کلیک یک دکمه خاص توسط کاربر بود، مدیریت استفاده مجدد ازکامپوننت چالش برانگیز خواهد شد.
اعتبارسنجی دادههای ورودی
برای مدیریت بهتر کامپوننتها جهت استفاده مجدد از آنها بهتر است ورودیهای کامپوننت را اعتبارسنجی کنیم. این ورودیها چه برای استفاده داخلی کامپوننت، یا جهت مشخص کردن وضعیت آن، بر رفتار کامپوننت تاثیر زیادی دارند. React مجموعهای از اعتبار سنجیها را دارد که میشود به کامپوننت اضافه کرد. باید توجه داشته باشیم که پیامهای خطای این اعتبارسنجیها فقط در حالت Development Mode قابل استفاده هستند. به زبان ساده اگر از react.min.js استفاده کنید، پیامهای خطا را نخواهید دید. باید فایلها را به نوع react.js تبدیل کنید. اعتبارسنجی React در زمان توسعه و برای توسعه دهندگان استفاده میشود.
مثال نوشیدنیها در قسمت چهارم میتوانست نام نوشیدنی و قیمت آن را نمایش دهد و همچنین میتوانستیم به لیست نوشیدنیها، موردی را اضافه کنیم. اگر ورودی قیمت، اعتبارسنجی نشود، میتوان رشتهای را بجای عدد به عنوان قیمت به کامپوننت ارسال کرد. نحوه اعتبارسنجی قیمت نوشیدنیها به صورت زیر است.
const MenuItem = props => ( <li className="list-group-item"> <span className="badge">{props.price}</span> <p>{props.item}</p> </li> ) MenuItem.propTypes = { price: React.PropTypes.number };
شیء propTypes را به کامپوننت اضافه کردهایم و در تنظیمات آن میتوانیم برای هر پارامتر ورودی یکی از اعضای PropTypes از React را که مناسب حال پارامتر است، انتخاب کنیم. در مثال بالا مشخص کردهایم که ورودی price باید عدد باشد و اگر مثلا رشتهای را بجای عدد ارسال کنیم، پیام خطای زیر را در Console خواهیم داشت.
Warning: Failed prop type: Invalid prop `price` of type `string` supplied to `MenuItem`, expected `number`. in MenuItem (created by Menu) in Menu
یا میتوانستیم از React.PropTypes.number.isRequired استفاده کنیم تا درج مقداری برای این ورودی الزامی باشد. اگر اعتبارسنجیهای React کافی نبودند میتوانیم اعتبارسنجیهای سفارشی خودمان را ایجاد کنیم. در مثال زیر میخواهیم ورودی price بیشتر از 15000 نباشد.
MenuItem.propTypes = { price: (props, price)=>{ if(props[price] > 15000){ return new Error("Too expensive!"); } } };
let MenuItem = React.createClass({ propTypes: { price: React.PropTypes.number } }); class MenuItem extends React.Component{ static propTypes = { price: React.PropTypes.number }; }
نوعهای دیگر برای اعتبارسنجی شامل موارد زیر هستند و البته مرجع تمام اعتبارسنجیهای React را میتوانید در اینجا بررسی کنید.
- React.PropTypes.array
- React.PropTypes.bool
- React.PropTypes.number
- React.PropTypes.object
- React.PropTypes.string
مقدار پیشفرض دادههای ورودی
یکی از امکانات مفید دیگر برای مدیریت مقدارهای ورودی، مشخص کردن مقدار پیشفرضی برای یک پارامتر است. برای مثال اگر برای قیمت یک نوشیدنی مقداری وارد نشد، یک حداقل قیمت برای آن در نظر بگیریم، هر چند که ایده و روشی اشتباه است!
MenuItem.defaultProps = { price: 1000 };
همانطور که میبینید روش کار مشابه با اعتبارسنجی است و برای مشخص کردن مقدار پیشفرض برای React.creatClass از متد getDefaultProps که عضوی از React است، استفاده میکنیم.
let MenuItem = React.createClass({ getDefaultProps() { return { price: 200 } }, render() { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ); } });
var image = document.createElement("img"); image.setAttribute("src", "logo.png"); React.createElement("img", { src : "logo.png" });
Virtual DOM
تفاوت در ساخت تگهای HTML به صورت مجازی بین JavaScript و React این است که React وضعیت تگهایی را که میسازد دنبال میکند. برای مثال فرض کنید نام سه محصول را در یک تگ < ul > نشان دادهایم. React وضعیت اصلی این تگ را که به مرورگر فرستاده، در حافظه دارد و همچنین در اثر تغییر منبع دادهای که برای < ul > مشخص کردهایم (که میتواند ورود اطلاعات به صورت Ajax باشد (مثلا اضافه شدن یک محصول جدید)) وضعیت جدیدی را برای تگ < ul > در حافظه ایجاد میکند. با وجود دو وضعیت برای یک تگ در حافظه، React میتواند تفاوت بین آنها را تشخیص داده و تگ را به روز کند. به این حالت عملکرد React ، اصطلاحا Virtual DOM میگویند.
React رابط کاربری را به صورت یک مدل میبیند و این مدل را با توجه به وضعیت اصلی آن در حافظه دوباره میسازد. برای React مهم نیست که ماهیت تغییر چیست. فقط وضعیتها را مثل دو عکس میبیند و میفهمد که آیا چیزی عوض شدهاست یا نه. دیالوگ React با مرورگر اینطور است: ای تگ < ul > این لیست را نشان بده (لیستی با سه محصول)، و بعد میگوید: ای تگ < ul > این لیست را نشان بده (لیستی با چهار محصول)!
کامپوننتهای React
<a href = “http://google.com”> <img src=”google.png”/> </a> // Components <clickableimage/> <linkimage/>
در کد بالا، بخش اول واضح است. عکسی که قابلیت کلیک شدن را دارد. حال فرض کنید یکی از کامپوننتهای <clickableimage/> یا <linkimage/>، همان تصویر قابل کلیک را ایجاد کنند. با نام گذاری واضح کامپوننتها، خوانایی برنامه بهتر میشود. یعنی میدانیم هر کامپوننت چه کاری را برای ما انجام میدهد. با این تصور که اگر تگهای زیاد و طولانی را در بخش رابط کاربری داریم، ارزش استفاده از کامپوننتهای React مشخص میشود.
قابلیت استفاده مجدد
در React کامپوننتها برای اساس توابع ساخته میشوند. یعنی وقتی یک کامپوننت را صدا بزنیم، در واقع یک تابع را اجرا میکنیم. در نتیجه کامپوننتها رفتار توابع را دارند؛ ورودی میگیرند و خروجی که یک DOM مجازی است را تحویل میدهند. اگر تابعی که مسئول ساخت کامپوننت است وابستگی به توابع یا متغیرهای بیرونی نداشته باشد، میتواند در جای دیگری از برنامه یا برنامهای دیگر مجددا استفاده شود. کد زیر نشان میدهد که چطور کامپوننتهای React ساخته میشوند.var ClickableImage = function(props) { return ( <a href={props.href}> <img src={props.src} /> </a> ); }; ReactDOM.render( <ClickableImage href="http://google.com" src="logo.png" />, document.getElementById("targetDivId"));
یکی از بهترین کاربردهای React، ترکیب کردن چند کامپوننت برای ساخت یک کامپوننت است. کامپوننت ساخته شده به این روش هر چند مشخص است چه کاری انجام میدهد، اما خود کامپوننت تگها را نمیسازد و شبیه یک لگو با قطعات مختلف است. مثلا در جایی از سایت میخواهیم اطاعات کاربری را نمایش دهیم. این اطلاعات به زیر مجموعههایی تقسیم میشوند؛ مثل اطلاعات شخصی، اطلاعات مربوط به شغل، اطلاعات تماس و عکس. به جای جمع کردن همه موارد در یک کامپوننت بهتر است هر بخش ویژه را در یک کامپوننت جدا و مستقل نمایش داده و ایجاد کنیم. با این کار از هر کامپوننت میشود به صورت جداگانه در قسمتهای دیگر سایت استفاده کرد. مثلا نمایش عکس کاربر به تنهایی در بخشهای دیگر سایت. در نهایت با این روش حجم کدها هم کمتر میشود. کامپوننتی که از اجزای کوچکتر ساخته میشود، مالک کامپوننتهای زیر مجموعه خود است.
کامپوننت مالک میتواند دادههای ورودی را برای اجزای سازنده خود به صورت یک سویه (one-way data binding) فراهم کند. گاهی دادههای مورد نیاز فرزندان، به طور مستقیم به مالک و از مالک به طور غیر مستقیم به فرزندان ارسال میشود. اما ممکن است لازم باشد که دادهها در اثر محاسبه در کامپوننت مالک، ایجاد و نتیجه به فرزندان ارسال شود. به هر صورت باید دقت داشته باشیم که در هر دو حالت اگر منبع داده و یا محسابه تغییر کنند، در به روز رسانیها، توسط React بازتاب آن را خواهیم داشت. در مثال زیر کامپوننت DisplayAllInfos تشکیل شده از چهار کامپوننت دیگر است.
class DisplayAllInfos extends React.Component{ render(){ return ( <div classID="something" className="something"> <ProfilePhoto src={} /> <UserPersonalInfo userInfo={}/> <UserJobInfo jobInfo={}/> <UserContactInfo contactInfo={}/> </div> ) } }
حتی میشود تگ <div> در مثال بالا را بصورت یک کامپوننت درآورد و سایر کامپوننتها را به عنوان فرزند به آن کامپوننت معرفی کرد. روش کار به صورت زیر است.
class Container extends React.Component{ render(){ <div classID="something" className="something"> {this.props.children} </div> } } class DisplayAllInfos extends React.Component{ render(){ return ( <Container> <ProfilePhoto src={} /> <UserPersonalInfo userInfo={}/> <UserJobInfo jobInfo={}/> <UserContactInfo contactInfo={}/> </Container> ) } }
توسط خاصیت this.proprs.children در کامپوننت Container فرزندانی که برای این کامپوننت در نظر گرفته شده را نمایش میدهیم و به آنها دسترسی داریم.
روش دیگر فرزند خواندگی یک کامپوننت مالک، از طریق ایجاد آرایهای از کامپوننتهای فرزند است که در مثال نوشیدنیها آورده شد. این روش میتواند دچار اشکال شود. اگر عضوی از این آرایه حذف شود، یا اعضای آن درون آرایه تغییر مکان دهند و مسائلی از این دست که برای آرایهها پیش میآید، React قادر به تشخیص ترتیب فرزندها نیست و نمیتواند آنها را دوباره فراخوانی و ایجاد کند. برای رفع چنین مشکلی باید برای هر فرزندی که به یک مالک اضافه میکنیم، یک کلید در نظر بگیریم. از این پس وقتی آرایهای از فرزندها دچار تغییر شدند، React از روی کلیدهای منحصر به فرد آنها میتواند تغییرات را تشخیص داده و دوباره کامپوننت فرزند را به درستی بسازد. به مثال زیر توجه کنید.
var hotDrinks = [ {id: 1, item: "Tea", price: "7000" }, {id: 2, item: "Espresso", price: "10000" }, {id: 3, item: "Hot Chocolate", price: "12000" } ]; // {this.state.menuList.map(item => <MenuItem {...item} />)} var Menu = React.createClass({ render: function () { return ( <div className="row"> <div className="col-md-4"> <ul className="list-group"> {this.state.menuList.map(item => <MenuItem key={item.id} {...item} />)} </ul> </div> </div> ) } });
آن خط کدی که به صورت کامنت گذاشته شده همان روش قبل در مثال نوشیدنیهاست. در حالت اصلاح شده برای هر یک از MenuItemها یک id در نظر گرفته شده. باید توجه داشته باشیم که کلیدها حتما یگانه باشند و نکته دیگر اینکه این کلیدها فقط در زمان معرفی کامپوننت استفاده میشوند و نمیتوانیم داخل خود کامپوننت آنها را داشته باشیم. برای مثال در یک کامپوننت MenuItem مقدار this.props.key قابل استفاده نیست. هیچگاه از اندیس خود اعضای آرایه به عنوان کلید استفاده نکنید، چرا که با حذف یک مورد، در عمل وضعیت کلیدها را بهم ریختهاید و شاهد رفتاری غیرقابل پیشبینی خواهید شد.
در قسمت بعدی اعتبارسنجی را در کتابخانه React بررسی میکنیم.
کتاب رایگان React.js
همانطور که در قسمت اول گفته شد، اجزای رابط کاربری (تگهای HTML) در کتابخانهی React به عنوان کامپوننتها (مؤلفههای جزء) شناخته میشوند. React تگها را به عنوان اجزایی مستقل و با وضعیتی مشخص در حافظه میشناسد. دلایل ارزشمند بودن این روش در ادامه بررسی میشود.
خوانایی بهتر (Readability)
React میتواند تگهای یگانه یا مخلوطی از تگهای به هم مرتبط را در پس زمینه ساخته و با یک نام واحد (کامپوننت) به HTML DOM ارسال کند. یعنی اگر جایی یک کامپوننت صدا زده شود، تگ یا تگهای مرتبط به آن کامپوننت را به عنوان خروجی خواهیم داشت. همانطور که میشود تگهای مختلف را به صورت تو در تو استفاده کرد، کامپوننتها را هم میشود به همین روش فراخوانی کرد. در مثال زیر روش صدا زدن چند کامپوننت و تگهایی را که ارائه میدهد، داریم.
// Components in a JavaScript file. <clickableImage href="http://google.com" src="google.png" /> <LinksContainer> <LinksList> <clickableImage href="http://yahoo.com" src="yahoo.png" /> </LinksList> </LinksContainer> <!--Output in HTML DOM--> <a href="http://google.com"> <img src="google.png" /> </a> <div> <div> <ul> <li> <a href="http://google.com"> <img src="google.png" /> </a> </li> </ul> </div> </div>
در قسمت کامپوننتها میبینیم که چطور کامپوننتها یکبار به صورت تکی و یک بار به صورت تو در تو اجرا میشوند. خروجی در قسمت Output واضح است که با نام کامپوننتها هماهنگی دارد. با این مثال چند مورد مشخص میشود.
- به هر کامپوننت قبلا گفته شده چه تگهایی را باید ایجاد کند. در نتیجه با هر بار فراخوانی در هر مکان، تگ یا تگهایی که به آن معرفی شده را میسازد.
- هر کامپوننت میتواند مقادیری را به عنوان ورودی دریافت کند و آنها را به تگها در خروجی اعمال کند. در مثال بالا href و src در فراخوانیهای مختلف، مقادیر متفاوتی را به خروجی میفرستند.
- با انتخاب نام مناسب برای کامپوننتها، بدون آنکه بدانیم چطور ساخته شدهاند میتوانیم حدس بزنیم چه تگهایی را خواهند ساخت و این دلیلی است که خوانایی برنامه افزایش میابد.
- دلیل دیگر که باعث خوانایی برنامه میشود، این است که هر یک از این کامپوننتها میتوانند تگهای زیادی را یک جا بسازند که این کار منجر به کم شدن مقدار کد برنامه میشود. برنامه هر چه کم کدتر، با خوانایی بیشتر!
قابلیت استفاده مجدد
در ادامه وقتی با روش ساخت کامپوننتها آشنا شدیم، متوجه میشویم که کامپوننتها چیزی بیشتر از یک تابع نیستند. وقتی نام یک کامپوننت را فراخوانی کنیم در واقع یک تابع را اجرا میکنیم، به آن پارامتر ورودی را میدهیم و از آن خروجی میگیریم. میدانیم که توابع را میشود یکبار ساخت و چندبار استفاده کرد. بخصوص اگر این توابع به متغیرهای سراسری و سایر توابع وابسته نباشند و به صورت مستقل عمل کنند، میشود آنها را به برنامههای دیگر هم انتقال داد.
نحوه ساخت یک کامپوننت در React
در React به سه روش میشود کامپوننتها را ایجاد کرد. در روش اول توضیحات زیاد خواهند بود، اما در دو روش بعدی فقط نکات کلیدی گفته خواهد شد.
Stateless function components
میخواهیم یک منو از نوشیدنیها را با استفاده از کامپوننتها نمایش دهیم. در یک فایل جاوااسکریپت کدهای زیر را وارد کنید. در ادامه هر بخش توضیح داده خواهد شد.
var hotDrinks = [ { item: "Tea", price: "7000" }, { item: "Espresso", price: "10000" }, { item: "Hot Chocolate", price: "12000" } ]; var MenuItem = function (props) { return ( <li className="list-group-item"> <span className="badge">{props.price}</span> <p>{props.item}</p> </li> ) }; var Menu = function (props) { return ( <div className="row"> <div className="col-md-4"> <ul className="list-group"> {props.data.map(item => <MenuItem {...item} />)} </ul> </div> </div> ) }; ReactDOM.render( <Menu data={hotDrinks} />, document.getElementById("reactTestContainer") )
- فرض میکنیم که لیست نوشیدنیها و قیمت آنها را به فرمتی که میبینید از سرور دریافت کردهایم. (hotDrinks)
- شیء MenuItem یک تابع بدون نام را اجرا میکند. از دیدگاه React این تابع یک کامپوننت است. کامپوننت با هر بار فراخوانی مقادیری را برای یک نوشیدنی و قیمت آن، دریافت میکند.کامپوننت به عنوان خروجی یک تگ <li>، پر شده با مقادیر ورودی را بازگشت میدهد.
- شیء Menu یک تابع بدون نام را اجرا میکند. از دید React این تابع یک کامپوننت است. کامپوننت با هر بار فراخوانی، مجموعهای از نوشیدنیها و قیمت آنها را دریافت میکند. متد map به کمک یک Arrow Function آرایهای از کامپوننت MenuItem ایجاد میکند که به ازای هر عضو ایجاد شده، یکبار MenuItem اجرا میشود. هر عضو (item) دارای یک نام نوشیدنی و قیمت آن است. سه نقطه در {…item} برای پر کردن جای خالی نیست! این عبارت یعنی اینکه مقادیر نام و قیمت را به صورت جداگانه (یعنی دو پارامتر مجزا) به کامپوننت MenuItem ارسال میکند. کامپوننت، به عنوان خروجی یک تگ <ul>، پر شده با آرایهای از کامپوننت MenuItem را بازگشت میدهد.
- متد render از شیء ReactDOM وظیفه ساخت تگهای JSX واقع در کامپوننتها را در HTML DOM به عهده دارد. پارامتر اول render، کامپوننت Menu است با ورودی دادههای گرفته شده از سرور. همانطور که شرح داده شد، کامپوننت Menu با فراخوانی و به کمک دادههای ورودی، کامپوننت MenuItem را پیادهسازی خواهد کرد. پارامتر دوم render، محلی است که تگها باید در آن ساخته شوند. مثلا یک تگ <div>
- در هر کدام از کامپوننتها و در قسمت ReactDOM.render میشود از کامپوننتهای دیگر به صورت تو در تو استفاده کرد.
React.createClass
React یک API درونی برای ایجاد کامپوننتها، به نام createClass دارد. این تابع باید یک شیء پیکربندی درون خود داشته باشد که در آن و بین دو آکولاد {} خواص و متدها تعریف میشوند. تابع createClass برای کار حداقل باید یک متد به نام render داشته باشد که در آن تگهای JSX را قرار میدهیم. کامپوننت MenuItem را که به صورت Stateless ساختیم، دوباره با createClass ایجاد میکنیم.
var MenuItem = React.createClass({ render: function () { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ) } });
برای خواندن مقادیر ورودی در این روش باید از this استفاده کنیم. بر اساس قواعد شیء گراییِ، MenuItem و Menu کلاس هستند و هر بار در ReactDOM.render کامپوننت Menu را به HTML DOM ارسال میکنیم. یک نمونه از این کلاس ساخته میشود و کلاس Menu، نمونههایی از کلاس MenuItem را میسازد. this به نمونهی ساخته شده از یک کلاس اشاره دارد.
React.Component
در روش آخر با استفاده از extend، از کلاس React.Component ارث بری میکنیم و کامپوننت را میسازیم. مفاهیم کلاس و ارث بری در جاوااسکریپ را میشود از اینجا یاد گرفت. مجددا MenuItem را با این روش ایجاد میکنیم.
class MenuItem extends React.Component { render() { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ); } }
همانطور که میبینید بین دو روش React.Component و React.createClass تفاوتی جز در syntax آنها نیست. در اینجا از سایر امکانات کلاس در جاوااسکریپت مثل سازنده کلاس میشود استفاده کرد. کامپوننتها در React میتوانند کاری بیشتر از ساخت تگها در HTML DOM را انجام دهند. در قسمت بعد به قابلیت مهم حفظ و دنبال کردن تغییرات در وضعیت کامپوننتها میپردازیم.
هر کامپوننتی در React یک چرخه زندگی دارد. زمانیکه یک کامپوننت را به روش React.createClass یا React.Component تعریف میکنیم و در ReactDOM.render نمونهای از کامپوننت را برای نمایش در مرورگر میسازیم، چرخه حیات آن شروع میشود.
ReactDOMServer
کتابخانه ReactDOMServer جهت ساخت یا render کردن کامپوننتها در سمت سرور استفاده میشود. توسط این کتابخانه میتوانیم کامپوننتها را در سمت سرور ایجاد کنیم و نتیجه آن را که تگهای HTML هستند به مرورگر ارسال کنیم. این روش جهت داشتن صفحههای وب سریعتر و اهداف SEO مفید است. جهت اطلاعات بیشتر و روشهای استفاده به مستندات آن رجوع کنید. در مثال زیر روش استفاده از این کتابخانه به اختصار آمده.
var persons = [ { id: 1, personName: "Parham", personContact: "parhamda@gmail.com" }, { id: 2, personName: "Roham", personContact: "roham@yahoo.com" }, { id: 3, personName: "Raha", personContact: "raha@live.com" } ]; class Person extends React.Component{ render(){ return ( <div> <p>{this.props.personName}</p> <p>{this.props.personContact}</p> </div> ) } } let person1 = persons[0]; let personElement = <Person personName={person1.personName} personContact={person1.personContact}/> console.log(ReactDOMServer.renderToStaticMarkup(personElement));
در کد بالا مواردی که جدید هستند، یکی ساخت یک نمونه از کامپوننت Person است و دیگری ساخت آن در سمت سرور، بدون آن که فعلا نمایشی در مرورگر داشته باشیم. در کنسول میتوانیم خروجی کتابخانه را که تگهای HTML هستند ببینیم. ReactDOMServer دو متد را فراهم کرده که کارکردی مشابه دارند؛ اما در جزئیات متفاوت هستند.
- renderStaticMarkup یک خروجی استاتیک و بدون attributeهای اضافه را تولید میکند که بیشتر برای بررسی یا استفاده در صفحههای وب ایستا مفید هستند.
- renderToString یک خروجی به صورت HTML String ایجاد میکند که برای HTML DOM در سمت کاربر سازگارتر است و مناسب برای صفحات پویا.
در نهایت خروجی از هر نوع که بود، برای اینکه در سمت کاربر قابل مشاهده باشد باید از همان متد ReactDOM.render استفاده کنیم. از آنجایی که این مجموعه جهت معرفی و بررسی ابزارهای اصلی React به صورت مختصر است، از آوردن مثالهای زیاد و پیچیده پرهیز میکنم. در اینجا میتوانید یک نمونه ساده برای استفاده از ReactDOMServer به صورت استاندارد و با جزئیات را بررسی کنید.
متدهای چرخه حیات در React
React چند متد را برای زمانهای قبل و بعد از ساخت شدن یک کامپوننت در DOM دارد که میشود رفتارهایی را برای کامپوننت، در این متدها در نظر گرفت تا در زمان مناسب اجرا شوند. در ادامه این متدها معرفی و کاربرد هر یک بیان میشود.
componentWillMount: این متد قبل از اینکه کامپوننت، تگهای متد render را بسازد اجرا میشود. این متد هم در سمت کلاینت کاربرد دارد و هم در سمت سرور. به همین جهت برای گرفتن log از دادههای کامپوننت و کار با پایگاه داده مکان مناسبی است. به عنوان مثال در قطعه کد زیر دادههای کامپوننت، توسط Ajax ارسال شدهاند.
componentWillMount() { Ajax.post("/componentLog", { name: this.constructor.name, props: this.props }); }
componentDidMount: این متد بعد از اینکه بخش render اجرا شد فراخوانی میشود. همچنین فقط در سمت کلاینت و زمانیکه از ReactDOM.render استفاده میکنیم کاربرد دارد. این متد مناسب برای تعامل کامپوننت با افزونهها و APIها است؛ مانند دریافت اطلاعات مورد نیاز کامپوننت از سایتی دیگر توسط یک API. از این متد در قسمت چهارم مثالی آورده شده.
(componentWillReciveProps(nextProps: این متد زمانی اجرا میشود که دادههای ورودی کامپوننت با مقادیری جدید تغییر کنند.
componentWillReceiveProps(nextProps) { // Do something with new received data and change the state. } ReactDOM.render( <TestComponent someData={newDataEveryFiveSecond()}/>, document.getElementById("divTest") );
در مثال بالا یک کامپوننت داریم که دادههای ورودی خود را از یک تابع میگیرد. این تابع هر پنج ثانیه یک بار یک داده تازه ایجاد میکند و به کامپوننت ارسال میکند. میتوانیم داخل کامپوننت، از متد componentWillReceiveProps جهت دستکاری دادههای رسیده و تغییر وضعیت کامپوننت توسط setState استفاده کنیم.
(shouldComponentUpdate(nextProps, nextState: این متد شبیه به متد componentWillReceiveProps است، البته با تفاوتهایی. این متد هم مقدار ورودی جدید برای پارامترهای کامپوننت میگیرد و هم مقداری برای وضعیتی که کامپوننت دارد. این متد باید یک مقدار بازگشتی false یا true داشته باشد. با این مقدار بازگشتی میتوان کنترل کرد که آیا کامپوننت بر اساس دادههای جدید بروز بشود یا نه.
class ComponentExample extends React.Component { shouldComponentUpdate(nextProps, nextState) { return notEqual(this.props, nextProps) || notEqual(this.state, nextState); } }
در مثال بالا پارامترها و وضعیت جاری کامپوننت، با مقدارهای تازه تغییر یافته و وضعیت جدید مقایسه میشوند. اگر مقادیر مقایسه شده برابر نباشند (یعنی داده تکراری وارد نشده) مقدار بازگشتی true خواهد بود و React کامپوننت را بر اساس وضعیت جدید و دادههای تازه دوباره میسازد.
(componentWillUpdate(nextProps, nextState: این متد زمانیکه کامپوننت ساخته شده، دادههای جدیدی را دریافت کند و یا وضعیت آن تغییر کند و دقیقا قبل از اجرای render فراخوانی میشود. اگر از متد shouldComponentUpdate مقدار false بازگشت داده شود، این متد دیگر اجرا نخواهد شد. باید توجه داشته باشیم که setState را نمیشود در این متد پیادهسازی کرده. به این علت که، زمانیکه وضعیت کامپوننت تغییر میکند، React متد componentWillUpdate و بلافاصله بعد از آن render را اجرا میکند و برای تغییر وضعیت دیگر دیر شده! تفاوت componentWillUpdate با componentWillMount این است که Will Mount در اولین وهله سازی از کامپوننت اجرا میشود، ولی Will Update بعد از هر دوباره سازی (rerender).
(componentDidUpdate(prevProps, prevStat: احتمالا میشود به راحتی حدس زد که این متد دقیقا بعد از دوباره سازی کامپوننتی که ساخته شده فراخوانی میشود.
componentWillUnmount: این متد زمانی اجرا میشود که یک کامپوننت از DOM پاک شود. برای پاک کردن نمونهای از یک کامپوننت که در DOM در حال نمایش است میتوانیم از دستور زیر استفاده کنیم.
ReactDOM.unmountComponentAtNode(document.getElementById("react"));