مشکلات مشترک مبتدی ها در React

در ابتدای شروع یادگیری ری اکت که مدت زمانی زیادی هم از آن نمی گذرد، با مشکلات و مسائلی مواجه می شدم که باعث وقفه هایی در روند آموزش می شد. تصمیم گرفتم بعضی از مهم ترین ها را در این مقاله با شما به اشتراک بگذارم.1. نامگذاری کامپوننت هامی دانید که برای فراخوانی المان Html در ری اکت باید بگونه زیر عمل کنید.<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>اما نام کامپوننت ها باید با حرف بزرگ شروع شود، در غیراین صورت به عنوان المان های built-in درنظر گرفته می شوند.مثال: می خواهیم فرمی برای لاگین کاربران در سیستم داشته باشیم.function loginForm() { return ( <> <h1>Login</h1> <form>....</form> </> ); }اگر بخواهیم کامپوننت را به صورت <loginForm /> فراخوانی کنیم. با خطای زیر مواجه می شویم.Warning: The tag <loginForm> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.همانطور که از پیغام مشخص شود، مرورگر سعی می کند تگ loginForm را از میان تگ های از پیش تعریف شده پیدا کند، درصورتی که این تگ به کامپوننت اختصاصی ما اشاره دارد. برای این کار تنها کافی است نام کامپوننت را به LoginForm تغییر دهیم.2. از PropTypes استفاده کنیدبرای تعیین نوع Props کامپوننت می توانید از پکیج prop-types استفاده کنید.فرض کنید می خواهید اطلاعات مربوط به یک رویداد را نمایش دهید. این اطلاعات شامل عنوان رویداد، محل برگزاری، تاریخ برگزاری، تعداد کل ظرفیت و تعداد ثبت نام ها است. (این اطلاعات از طریق Props به کامپوننت فرستاده می شوند.)به صورت ساده کامپوننت را این گونه طراحی می کنیم.function EventDetails(props) { const { title, address, date, capacity, registeredCount } = props; const remainingCapacity = capacity - registeredCount; return ( <div className=&quotrow&quot> <h1>{title}</h1> <p>Location: {address}</p> <p>Date: {date}</p> <p>Capacity: {capacity}</p> <p>Registered Count: {registeredCount}</p> <p>Remaining Capacity: {remainingCapacity}</p> </div> ); }براساس دو متغیر ظرفیت کل (capacity) و تعداد ثبت نام شده ها (registeredCount)، می توان ظرفیت باقی مانده را حساب کرد و در کامپوننت نشان داد. پیش فرض ما این است که این دو متغیر همواره عدد هستند، اما اگر به جای عدد، رشته داده شد چه؟ اگر اصلا مقداری برای این دو متغیر فرستاده نشد چه؟ شما خطایی در کنسول مرورگر نمی بینید، اما خروجی نهایی چیزی نیست که شما انتظارش را دارید!برای جلوگیری از این گونه اتفاقات باید نوع داده های ورودی را تعریف کنید.الف) ابتدا پکیج prop-types را در پروژه ایمپورت کنید.import PropTypes from &quotprop-types&quotب) سپس پس از کدهای کامپوننت از آن استفاده کنید.EventDetails.propTypes = { title: PropTypes.string.isRequired, address: PropTypes.string, capacity: PropTypes.number.isRequired, registedCount: PropTypes.number.isRequired, };3. به نوع داده ورودی ها توجه کنیداگر می خواهید پراپرتی از نوع عدد به یک کامپوننت ارسال کنید، به جای رشته، آن را واقعا عددی بفرستید.<TotolPrice amount={1500} />4. حالت یا State را با Props اشتباه نگیرید.اگر بخواهید مقادیری را از کامپوننت پدر به فرزند منتقل کنید، باید آن را با استفاده از props ارسال کنید.مثال: کامپوننتی داریم به نام Box که پراپرتی هایی با نام های title ،body و footer دریافت می کند و آن ها را در یک باکس در قالب بوت استرپ نمایش می دهد. پیاده سازی این کامپوننت به صورت زیر خواهد بود.function Box({ title, body, footer }) { return ( <div class=&quotcard&quot> <div class=&quotcard-header&quot>{title}</div> <div class=&quotcard-body&quot>{body}</div> <div class=&quotcard-footer&quot>{footer}</div> </div> ); }و با استفاده از کد زیر می توانیم آن را فراخوانی کنیم.<Box title=&quotCard title&quot body=&quotSome example text.&quot footer=&quotCard footer&quot />همانطور که از صورت مسئله هم مشخص است، قرار است این سه متغیر توسط پدر یا همان (Container) تعریف شوند، پس راه حل درست استفاده از props است.اما state متغیرهای محلی برای کامپوننت هستند که توسط هیچ کامپوننت دیگری قابل دسترس نیستند، مگر آن که آن ها را به عنوان property به یک کامپوننت بفرستیم.تفاوت اصلی میان props و state در این است که تغییر state باعث رندر شدن مجدد کامپوننت می شود. بنابراین اگر مقداری دارید که با تغییر آن انتظار دارید، بخشی از کامپوننت شما تغییر کند، باید از state استفاده کنید.همیشه اولین مثال برای توضیح state کامپوننت Counter است.Counter viewدر این کامپوننت یک مقدار اولیه تعریف می شود، می خواهیم پس از آنکه کاربر روی دکمه + کلیک کرد، یک عدد به مقدار اولیه اضافه و با کلیک روی دکمه – یک عدد از آن کم شود. کلیک روی دکمه Reset هم باعث بازگشت مقدار فعلی به مقدار اولیه شود.همانطور که مشخص است، می خواهیم درطول اجرای برنامه براساس دستورات کاربر مقداری تغییر کند و این تغییر در صفحه نمایش داده شود. تنها راه حل استفاده از state است.نکته: امکان تغییر دادن متغیرهای محلی و حتی props نیز وجود دارد، اما توجه داشته باشید که هیچ یک از آن ها نمی توانند منجر به رندر شدن مجدد کامپوننت شوند.function Counter() { const initialCount = 1; const [count, setCount] = useState(initialCount); const increaseHandler = () => setCount(prevCount => prevCount + 1); const decreaseHandler = () => setCount(prevCount => prevCount - 1); const resetHandler = () => setCount(initialCount); return ( <> <p>{count}</p> <button ={increaseHandler}>+</button> <button ={decreaseHandler}>-</button> <button ={resetHandler}>Reset</button> </> ); }با توجه به اینکه در تعریف کامپوننت از Functional Component استفاده کردیم، برای تعریف یک state از State Hooks استفاده می کنیم. در Class Component ها سینتکس تا حدودی متفاوت است، اما منطق آن تفاوتی ندارد.5. حالت یا State را به صورت مستقیم تغییر ندهیدهمانطور که در تکه کد بالا دیدید، هر زمان که نیاز به تغییر count داشتیم، از تابع setCount استفاده کردیم. ما به هیچ وجه نمی توانیم مقدارهای state را به صورت مستقیم دستکاری کنیم.count = count + 1;نکته: در Class Component ها با استفاده از دستور setState می توان محتوای حالت را تغییر داد. مثال Counter را در این روش ببینید:class Counter extends Component { initialCount = 1; constructor(props) { super(props); this.state = { count: this.initialCount }; } increaseHandler = () => { this.setState(prevState => ({ count: prevState.count + 1 })); } decreaseHandler = () => { this.setState(prevState => ({ count: prevState.count - 1 })); } resetHandler = () => this.setState({ count: this.initialCount }); render() { return ( <> <p>{this.state.count}</p> <button ={this.increaseHandler}>+</button> <button ={this.decreaseHandler}>-</button> <button ={this.resetHandler}>Reset</button> </> ); } }نکته: تنها جایی که می توان state را به صورت مستقیم مقداردهی کرد، تابع constructor است.6. تغییر state غیرهمزمان یا asynchronous استغیرهمزمان بودن یک تابع به این معنی است که اجراکننده برای رفتن به خط بعدی، منتظر اتمام اجرای آن تابع نمی ماند. مثال زیر را درنظر بگیرید.setTimeout(() => { console.log(&quotFirst log&quot); }, 1000); console.log(&quotSecond log&quot);در حالت عادی انتظار دارید که بعد از یک ثانیه First log نمایش داده شود و بلافاصله بعد از آن Second log.درصورتی که خروجی به صورت زیر خواهد بود.Second log First logدلیل این اتفاق این است که تابع setTimeOut به صورت async اجرا می شود، یعنی مرورگر منتظر اجرا شدن آن نمی ماند. بلکه کدهای بعد از آن اجرا می شود و هر زمان که یک ثانیه تعیین شده برای تابع setTimeOut تمام شد، درخواست ما که در اینجا یک لاگ است انجام می شود.توابع مربوط به تغییر دادن state نیز به صورت async اجرا می شوند.this.setState(prevState => ({ count: prevState.count + 1 }) // We could not be sure that the value of 'count' is the new value or previous oneبنابراین نمی توانیم تضمین کنیم که بلافاصله در خط بعدی ما به مقدار جدید count دسترسی داریم.پس چکار کنیم؟اگر می خواهید بعد از تغییر یک وضعیت، کاری انجام دهید، باید از پارامتر دوم تابع setState استفاده کنید.this.setState( prevState => ({ count: prevState.count + 1 }), () => { // Do sth after setting count to new value } );توجه داشته باشید پارامتر دوم زمانی اجرا می شود که مقدار جدید state دخیره شده باشد، بنابراین در این تابع می توانید با خیال راحت براساس مقدار جدید state تصمیم گیری کنید و کاری را انجام دهید.گفتیم که در Functional Component از State Hooks برای پیاده سازی state استفاده می کنیم. توابع تغییر state در hooks پارامتر دومی نمی پذیرند. بنابراین شما نمی توانید کد زیر را بنویسید.const [count, setCount] = useState(1); setCount( prevCount => prevCount + 1, () => { // Do sth after setting count to new value } );برای این کار می توانید از Effect Hooks استفاده کنید.useEffect(() => { //Do sth after setting count to new value }, [count]);در کد نوشته شده تعیین کرده ایم که هرگاه مقدار count تغییر کرد، این تابع اجرا شود.7. متصل کردن چند رشتهفرض کنید متغیری با نام username داریم که قصد داریم، آن را در انتهای یک پیام خوشامدگویی گذاشته و به کاربر نشان دهیم. در گذشته از تکه کد زیر استفاده می کردیم.const username = &quotSara Rezaei&quot const greetingMessage = &quotWelcome, &quot + username;در ES6 ویژگی جدیدی به نام Template literals معرفی شد، که با استفاده از Back-ticks می توان درون یک رشته از متغیر استفاده کرد. در این حالت مسئله به صورت زیر حل خواهد شد.const greetingMessage = `Welcome, ${username}`;1. باید Event Handler ها را Bind کنیمدر Class Component ها می توان متد تعریف کرد و آن را در هرجایی از کامپوننت فراخوانی کرد.class TestBinding extends Component { whatIsThis() { console.log(this); } render() { this.whatIsThis(); return <></>; } }در کامپوننت تعریف شده، متد whatIsThis محتوای this را لاگ می گیرد. با توجه به اینکه در یک کلاس جاوااسکریپتی هستیم، انتظار داریم this همیشه برابر با کامپوننت فعلی باشد.در مرحله بعد ما در تابع render تابع whatIsThis را فراخوانی کرده ایم. This در تابع رندر به کامپوننت فعلی اشاره می کند، درنهایت خروجی مدنظر ما به دست می آید.TestBinding {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}اما فرض کنید قرار است این تابع به عنوان Event Handler به المانی پاس داده شود.<button ={this.whatIsThis}>What is this</button>در این حالت، با توجه به اینکه رفرنسی به متد را به ایونت click فرستاده ایم، آبجکت صدازننده (this) فراموش می شود و مقدار this را برابر با undefined درنظر می گیرد، درنتیجه undefined لاگ گرفته می شود.برای جلوگیری از این اتفاق 4 رویکرد متفاوت وجود دارد.1. استفاده از Anonymous Function<button ={() => this.whatIsThis()}>What is this</button>2. استفاده از تابع Bind در هنگام رفرنس دادن به متد<button ={this.whatIsThis.bind(this)}>What is this</button>با استفاده از تابع Bind می توان صدازننده (this) را برای یک تابع مشخص کرد.3. استفاده از تابع Bind در تابع سازندهconstructor(props) { super(props); this.whatIsThis = this.whatIsThis.bind(this); }این روش کاملا شبیه روش دوم است، با این تفاوت که دیگر نیازی نیست در بخش های متفاوت کامپوننت تابع Bind را فراخوانی کنید.<button ={this.whatIsThis}>What is this</button>4. استفاده از Arrow Function در هنگام تعریف متدwhatIsThis = () => { console.log(this); };وقتی تابعی را با Arrow Function تعریف می کنیم، مقدار this برابر با مقدار this پدر می شود. پس هرجایی که تابع whatIsThis فراخوانی شود، this کلاس کامپوننت به عنوان صدازننده درنظر گرفته می شود.از میان 4 روش ذکر شده، ساده ترین روش، گزینه چهارم است.موارد عنوان شده در این پست کامل نیست و در آینده ممکن است آپدیت شود. اگر شما هم موضوعی به نظرتان رسید در بخش نظرات ذکر کنید تا به پست اضافه شوند. ممنونم :)