حدود ۴ ماه پیش یه گفتوگو [discussion] جدید توی ریپازیتوریِ ریاکت ۱۸ متعلق به reactwg (wg مخفف working group) باز شد و sylwiavargas که آغاز کنندهٔ این گفتوگو بود، عنوان گفتوگو رو گذاشت "واژه نامه + یه جوری توضیح بده انگار ۵ سالمه" و توی پیامِ اولش نوشت "...برای پیدا کردن (یا اضافه کردن) یه اصطلاح یا توضیح جدید به قسمت نظرات برید... بیاید به توسعه دهنده های جدید ریاکت کمک کنیم به اندازهٔ ما از این release هیجانزده بشن!" قرار بر این بود که هر کسی اصطلاحی از ویژگی های ریاکت ۱۸ شنیده که معنی و یا کارآییش رو درست متوجه نشده، اون رو توی قسمت نظرات بپرسه تا تیم ریاکت به کمک ساده ترین مثال های ممکن اون رو برای همه توضیح بده. هرچند سوال ها بعد از مدت کوتاهی دیگه به ریاکت ۱۸ محدود نمیشدن و سوالاتی دربارهٔ ویژگیهایی فراتر از ریاکت ۱۸ و حتی ویژگی های کاملاً بنیادین ریاکت (render کردن چیه؟ توضیح دقیق state ها چیه؟ ...) پرسیده و پاسخ داده شدن.
این صرفاً یک ترجمه است. میتونید متن اصلی رو با کلیک کردن روی این لینک پیدا کنید.
نویسنده: gaearon
همزمانی به این معناست که تسک ها میتونن با هم تداخل کنن (روی هم بیوفتن).
مثلاً میتونیم از تماس های تلفنی به عنوان قیاس استفاده کنیم.
غیرهمزمانی یعنی من در آنِ واحد فقط میتونم یک مکالمهٔ تلفنی داشته باشم. اگه دارم با آلیس حرف میزنم، و یهو باب بهم زنگ میزنه، اول باید تماسم رو با آلیس تموم کنم و بعد میتونم با باب حرف بزنم.
همزمانی یعنی میتونم در آنِ واحد بیشتر از یک مکالمه داشته باشم. مثلاً میتونم آلیس رو بذارم رو hold، یکم با باب صحبت کنم، و بعد دوباره برگردم به تماسم با آلیس.
توجه کنید که همزمانی لزوماً به این معنا نیست که با دو نفر در آنِ واحد حرف بزنم. فقط به این معناس که در هر لحظه ای، ممکنه وسط چند تا تماس باشم، و میتونم مثلاً بر اساس اینکه کدوم مکالمه ضروری تره، انتخاب کنم که با کی حرف بزنم.
حالا برای اینکه این قیاسمون رو ترجمه کنیم، توی ریاکت "تماس های تلفنی" میشن همون کال های "setState". قبلاً ریاکت در آنِ واحد فقط میتونست روی یک آپدیتِ state کار کنه. پس همهٔ آپدیت ها "ضروری" بودن: همین که شروع میکردید به re-render کردن، دیگه نمیتونستید متوقف بشید. اما با startTransition میتونید یه آپدیت غیرضروری رو به عنوان یه transition علامت بزنید.
میتونید آپدیت های "ضروری" setState رو معادل همون تماس های تلفنیِ ضروری ببینید (مثلاً دوستتون به کمکتون نیاز داره) در حالی که transition ها مثل مکالمه های ریلکسی هستن که میتونن برن رو hold یا حتی قطع بشن اگه دیگه مطابقت نداشتن.
نویسنده: sylwiavargas
#۲۸ یه thread بسیار کارآمده که توش دو تا قیاسِ اساسی واسه تعلیق [suspense] داره:
یه کامپوننت رو تصور کنین که قبل از اینکه render بشه باید یه سری تسک های ناهمزمان [asynchronous] رو انجام بده، مثلاً یه سری داده fetch کنه.
قبل از تعلیق، یه همچین کامپوننتی state یه isLoading رو نگه میداشت و بر اساس اون یه جور fallback رو return میکرد (مثلاً یه state خالی، اسکلتبندی، اسپینر، ...).
با تشکر از تعلیق، حالا یه کامپوننت میتونه در حین render شدن داد بزنه "حاجی وایسا! من هنوز آماده نیستم. من رو render نکن فعلاُ بی زحمت - هر وقت آماده بودم صدات میکنم!"
اینجا ریاکت میره نزدیکترین تعلیقی که توسط شما set شده و شامل fallback هست رو پیدا میکنه و تا زمانی که اون کامپوننت آمادگی خودش رو جهت render شدن اعلام کنه، اون رو render میکنه.
یه راه دیگه واسه توضیح دادنش، اینه که داستان مثل - پرتاب کردن / گرفتن - میمونه ولی در جهت لود کردن state ها. وقتی یه کامپوننت میگه "من آماده نیستم" (یه موقعیت غیرمعمول، مثل پرتاب کردن)، نزدیک ترین بلوکِ تعلیقِ بالا (مثل گرفتن) میاد بجاش اون fallback رو نشون میده. بعد، وقتی که وعده [promise] حل شد [resolve]، ما یه "تلاش مجدد" میکنم واسه render کردن.
یه تفاوت کلیدی که توی برنامه نویسی بر پایه ی وعده [Promise-based programming] ی مرسوم وجود داره اینه که وعده ها [Promises] در معرض کد کاربر نبودن. شما مستقیماً باهاشون سر و کله نمیزدید. شما Promise.all یا then. یا یه همچین کارایی باهاشون نمیکردید. بجای "صبر کن تا وعده [promise] حل [resolve] بشه و یه سری از کد های کاربر رو اجرا کن"، الگو اینطوری میشه که "ریاکت وقتی resolve شد خودش دوباره فرایند render کردن رو اجرا میکنه". از بعضی جهات این ساده تره ولی یه مقدار "یاد نگرفتن" میخواد چون کاربر یه مقدار از کنترلی که از سر برنامه نویسی بر پایه وعدهٔ مرسوم بهش عادت کرده بوده رو از دست میده.
نویسنده: gaearon
تصور کنید دارید صبحانه آماده میکنید.
میرید سوپرمارکت تا یکم شیر بخرید. برمیگردید. بعد متوجه میشید یه مقدار کورن فلکس لازم دارید. میرید سوپرمارکت تا کورن فلکس بخرید. بعد برمیگردید. بعد متوجه میشید که بیسکویت لازم دارید. میرید سوپرمارکت و بیسکویت میخرید. بالاخره، میتونید صبحانه تون رو میل کنید.
ولی مطمئناً یه راه مؤثرتر واسه انجامش هست نه؟ بجای اینکه هر مورد رو به محض اینکه به ذهنتون رسید برید بخرید و بیاید، ایدهٔ بهتر اینکه اول یه لیست خرید تهیه کنید. بعد برید سوپرمارکت و همه چیزهایی که لازم دارید رو بخرید و صبحانه تون رو حاضر کنید.
این میشه دستهبندی [batching].
توی ریاکت، هر موردِ مجزا، یه کالِ setState هست.
setIsFetching(false); setError(null); setFormStatus('success')
ریاکت میتونست بعد از هر کدوم از این call ها، ("برو به مغازه") رو دوباره render کنه (re-render کنه). ولی چنین حرکتی کارآمد نیست. بهتر اینه که اول همه ی آپدیت های state رو ذخیره کنه و بعد، وقتی همهٔ اون قسمتایی از state رو که میخواستید آپدیت کردید، یک بار re-render کنه.
چالشش اینجاست که ریاکت چه زمانی باید اینکارو انجام بده. از کجا میتونه بفهمه کارتون با آپدیت کردن state تموم شده؟
برای event handler ها که آسونه:
function handleClick() { setIsFetching(false); setError(false); setFormStatus('success'); }
ریاکت اون چیزه که click handler شما رو call میکنه.
// l :توی ریاکت handleClick();
پس ریاکت بلافاصله بعدش فرایند re-render رو اجرا میکنه:
// l :توی ریاکت handleClick(); updateScreen();
ریاکت اینطوری کار میکرد همیشه.
برگردیم به این مثال:
setIsFetching(false); setError(null); setFormStatus('success');
اگه در جریان یه event اتفاق نیوفته چی؟ مثلاً، شاید این کد بعد از یه run ،fetch بشه. ریاکت هیچ ایده ای نداره که کِی state set کردن شما "تموم" میشه. پس باید یکم زمان بذاره تا صفحه رو آپدیت کنه.
این مشابه اینه که شما صبحونه نخورید و بجاش تمام روز هوس های اَلا بختکی داشته باشید. آیا همون لحظه که احساس گرسنگی میکنید میرید سوپرمارکت؟ یا یه مدت صبر میکنید (مثلاً ۳۰ دقیقه) و بعد با لیست چیزهایی که میخواستید بخرید میرید سوپرمارکت؟
قبلاً، ریاکت اینطوری بود که در چنین موقعیتی بلافاصله re-render میکرد. یعنی بابت ۳ بار setState رو صدا زدن، ۳ تا آپدیتِ صفحه گیرتون میومد. این زیاد کارآمد نیست.
با دستهبندی اوتومات شده [automated batching] توی ریاکت ۱۸، اینا همیشه دستهبندی [batch] میشن. عملاً به این معناست که ریاکت "یکم صبر میکنه" (اصطلاح فنیاش میشه "تا آخره تسک یا ریزتسک [microtask]). بعد ریاکت صفحه رو آپدیت میکنه.
نویسنده: sylwiavargas
توضیح دَن از آبرسانی توی ۳۷# (با زیرعنوان "SSR چیست؟") مفهوم رو میرسونه. اینم یه گزیده اش:
پروسهٔ render کردن کامپوننت هاتون و پیوستن event handler ها به اونا به عنوان "آبرسانی" شناخته میشه. مثل این میمونه که یه HTML "خشک"، از "آب" عه فعل و انفعال داشتن و event handler ها سیراب بشه.
قیاسی که دوست دارم برای "آبرسانی" بکار ببرم اینه که به طور خشک load میشه، و بعد به چرخدنده ها روغن زده میشه تا حرکتش بدن.
نویسنده: meganesu
چیزی که من فهمیدم:
آبرسانی فقط با render شدن سمت سرور [server-side rendering] استفاده میشه.وقتی از SSR استفاده میکنید، پروسهٔ render کردن کامپوننت ها توی مرورگرتون شامل ۲ تا مرحله میشه:
نویسنده: laurieontech
میشه SSR رو به خوبی با غذا توضیح داد.
فرض کنید به یه میز سالاد رفتید و میتونید همه ی اجزائی که میخواید رو از روش بردارید تا سالاد خودتون رو درست کنید. شما اونارو با هم مخلوط میکنید، اونطوری که دلتون میخواد طبقهبندیش میکنید، و بعد اون رو میل میکنید. این میشه render سمت کلاینت [client-side rendering]. شما اجزاء رو در کنار هم قرار میدید (JS ،CSS ،HTML، خودتون).
از اون طرف میتونید به یه میز سالاد برید و یه سالادِ از قبل آماده شده رو بردارید. اون آماده است، تمام کاری که لازمه بکنید اینه که اون رو میل کنید. این میشه render سمت سرور [server-side rendering]. شما یه HTML آماده ی render رو مستقیماً از سرور دریافت میکنید.
نویسنده: sophiebits
غالب مردم فکر میکنند ریاکت سمت کلاینت run میشه (مثلاً توی مرورگر کاربر). نتیجه render شدن کامپوننت ها میشه تغییر دادن HTML ای که روی صفحه هست. وقتی یه کاربر وارد وبسایتی میشه که از ریاکت استفاده میکنه، اول باید ریاکت و کد جاوا اسکریپتی که برای هر کامپوننت ریاکت استفاده شده رو دانلود کنیم، و بعد روی مرورگرشون اون کد رو run میکنیم تا ببینیم چه HTML ای رو میخوایم روی صفحه نشون بدیم. وقتی این اتفاق افتاد، کاربر میتونه app رو ببینه و باهاش تعامل کنه.
رِندِر سمت سرور [server-side rendering] وسیله ایه که به طراحی شده تا به اون پروسه سرعت ببخشه تا که صفحه بتونه از load ریاکت سریعتر استفاده کنه. وقتی از SSR استفاده میکنیم، ما اول اون کامپوننت هایی که دارن فایل های مورد نیاز صفحه رو برای کاربر میفرستن run میکنیم. HTML خروجی رو توسط اون کامپوننت ها میگیریم و اون HTML رو میفرستیم برای صفحه کاربر. همین که اون رسید، کاربر میتونه همه محتوای صفحه رو ببینه. حواستون باشه که HTML خودش میتونه تصویری از چیزی که قراره صفحه در ابتدا
نشون بده رو بهمون بده و به خودیه خود تعامل پذیر نیست. پس همچنان لازمه که کد ریاکت و جاوا اسکریپتِ هر کامپوننت ریاکت رو دانلود کنیم، و بعد باید به ریاکت بگیم که اون HTML ای که قبلاً روی صفحه بوده رو "کنترل کن"، که بهش میگن "آبرسانی". وقتی اون تموم شد، کاربر میتونه همه محتویات صفحه رو ببینه و میتونه باهاشون تعامل کنه.
اینطوری SSR به نمایان شدن سریعتر صفحهٔ اولیه کمک میکنه ولی باعث نمیشه که سریعتر تعاملی [interactive] بشه چون بازم باید منتظر دانلود و run شدنِ همهٔ کد های جاوا اسکریپت بمونید - با SSR، دیگه داریم همه کامپوننت هارو روی سرور run میکنیم، ولی در عین حال همچنان داریم اونارو سمت کلاینت run میکنیم. (اتفاقی که در آینده برای سرور کامپوننت ها خواهد افتاد - تجربه ای جدید توسط تیم ریاکت - اینه که به گونه ای طراحی میشن که شما بتونید بعضی کامپوننت هارو فقط روی سرور run کنید، ولی حالا حالاها بعیده آماده بشه.)
برای اینکه از SSR استفاده کنید، باید ۲ تا کار انجام بدید: (الف) مطمئن بشید هر کامپوننت ریاکت ای که روی کد بیستون دارید فقط از ویژگی هایی استفاده میکنه که هم سمت سرور و هم سمت کلاینت جواب میدن. (یه سری مقررات داره، مثلاً استفاده نکردن از آبجکت window از اونجایی که فقط سمت کلاینت کار میکنه)، و (ب) کد سمت سرورتون رو به گونه ای تغییر بدید که ریاکت رو call کنه و با HTML ای که در نتیجه برمیگرده یه کاری انجام بده. بعضی از فریمورک ها مثل Next.js به طور پیشفرض SSR رو ساپورت میکنن، که یعنی اونا قسمت (ب) رو واستون انجام دادن.
نویسنده: gaearon
دو نوع اثر [effect] داریم:
گاهاً وقتی شما میگید "effects" معلوم نیست منظورتون هر دو نوع هست یا فقط نوع اول. واسه اینه که نوع اول (useEffect) بعضی وقتا "منفعل" [passive] صدا زده میشه.
ما توی داکیومنت ها [docs] همیشه از این لفظ پرهیز میکنیم، اما بین خودمون توی مکالمه های اصلی تیم و حتی توی کامنت های GitHub ازش استفاده میکنیم. فکر میکنم نگه دارندگان کتابخانه [library maintainers] یحتمل از همینجا یادش گرفتن. من انتظار ندارم نویسنده های اپلیکیشن [application authors] راجع به این لفظ بدونن و یا بهش اهمیت بدن - ولی اگه شما دیدینش، معنیش همون useEffect خودمونه، نه هیچ چیز دیگه ای.
نویسنده: gaearon
رویدادهای "گسسته" مفهومی نیست که ما هیچ کجای داکیومنت ها [docs] ازش استفاده کنیم. مفهومی نیست که انتظار داشته باشیم دولوپر های اپلیکیشن های ریاکتی [React application developers] راجع بهش بدونن یا بهش اهمیت بدن.
با این حال، وقتی توضیح میدیم که دستهبندی اوتوماتیک چطوری کار میکنه بحثش پیش میاد. چیزی که لازمه روشن کنیم اینه که برای بعضی رویدادها، مثل کلیک کردن ها، تضمین میکنیم که صفحه، قبل از اینکه رویداد بعدی بتونه اجرا بشه، آپدیت میشه. این واسه روابط کاربری ای مثل شمارنده ها [counters] (تصور کنید کاربر دو بار خیلی سریع رو یه دکمه کلیک کنه) یا فرم ها (جایی که فرستادن یک فرم ممکنه باعث غیرفعال شدنش بشه، و اگه این اتفاق قبل از فشار دادن دکمه دوم نیوفته، ریسک ۲ بار فرستادن یک فرم رو متحمل میشید) اهمیت داره.
چنین رویداد هایی رو "گسسته" مینامیم. این به این معناس که هر رویداد به صورت جداگانه ای توسط کاربر در قصد شده. اگه ۲ بار کلیک میکنم، بخاطر اینه که میخوام ۲ بار کلیک کنم. اگه تایپ میکنم "س"، "ل"، "ا"، "م"، بخاطر اینه که میخوام تایپ کنم "سلام".
این با رویدادی مثل mousemove (حرک موس) تفاوت داره. من شاید موس رو تو ده جهت مختصاتی مختلف حرکت بدم در حالی که از قصد نمیخواستم موس رو توی هیچکدوم از این جهت های مختصاتی مختلف تکون بدم. به عنوان یه کاربر هیچ ایده ای ندارم الان چند بار رویداد mousemove رو اجرا کردم. فقط موس رو تکون دادم. به چنین رویدادهایی میگیم "مداوم" [continuous]. — که توی اینا، آخرین مورد اهمیت داره، نه که چندتا اتفاق افتاد.
این مفاهیم فقط برای ساختار درونی ریاکت اهمیت داره، چون ممکنه آپدیت هارو طبق چند تا رویداد مداوم [continuous] دستهبندی [batch] کنه، ولی بازم به ازای هر رویداد "گسسته" ی پی در پی، صفحه رو آپدیت میکنه. به هر ترتیب، به عنوان یه کاربر ریاکت، شما یحتمل لازم نباشه بهش فکر کنین.
نویسنده: gaearon
یک وعده آبجکتیه که نمایندهٔ چیزیه که در آینده موجود خواهد شد. میتونید به وعده [promise] مثل همون تایمر هایی که تو بعضی از رستوران ها بهتون میدن فکر کنید. وقتی تایمرر زنگ میزنه، میرید غذاتون بر برمیدارید.
let food = await cook(); eat(food);
وقتی میبیند که توی کد از await استفاده شده، مهمه که متوجه باشید که تابع شما همش با هم اجرا نمیشه. بجاش، هر await، تابع شما رو "جدا" [split] میکنه (شکاف میده / به دو نیم میکنه). یه بخشی هست که قبلش run میشه (اینجا ()cook به شما یه وعده [promise] میده). بعد بهش میگید وقتی که غذا آماده شد، ادامه بده. در نهایت، وقتی غذا آماده شد، برمیگردید به این تابع، به متغیر food مقدار اولیه میدید، و میلش میکنید.
یک "تیک وعده" [Promise tick] به زمانی اشاره میکنه که جاوا اسکریپت تابع شما رو که منتظر یه وعده بود، ادامه میده. بهش میگن "تیک" چون یه تایمره. وقتی نتیجه آماده است، وعده بلافصله تابع شما رو ادامه نمیده. بجاش به محظ اینکه ممکن بود بعد از اینکه اون کُدی که در اون لحظه در حال اجرا شدنه کارش تموم شد اینکارو انجام میده. میتونید به این مثل سرآشپز رستورانی فکر کنید که منتظره دستش آزاد بشه تا "تایمر" شما رو call کنه بجای اینکه به محظ اینکه غذاتون رو آماده کرد اینکارو بکنه.
فلاش / فلاش کردن میتونه به معنای سیفون کشیدن باشه. به طور کلی به معنای منزه کردن هر چیزی میتونه به کار بره. - جلال
همگام سازیِ فلاش اسم روشیه که بهتون اجازه میده کنترل کنید ریاکت کِی صفحه رو آپدیت میکنه. (آپدیت های state رو "فلاش" میکنه). مخصوصاً بهتون این قابلیت رو میده که به ریاکت بگید همین الان اینکارو بکنه. ("به طور همزمان" [synchronously])
flushSync(() => { setState(something); }); // به این خط که برسیم، صفحه یه دور آپدیت شده
"Flush synchronously" = "همین الان صفحه رو آپدیت کن"
معمولاً نباید به flushSync احتیاج پیدا کنید. فقط در صورتی ازش استفاده میکنید که بخواید که مشکلی که توش ریاکت دیرتر از اون زمانی که شما میخواید صفحه رو آپدیت میکنه رو برطرف کنید. خیلی به ندرت پیش میاد همچین چیزی.
نویسنده: laurieontech
مطمئن نیستم این کار مجاز باشه {چون داره روی ریپازیتوریه یکی دیگه تو گیت هاب منتشرش میکنه اینو میگ}، ولی یه پست روی وبلاگم نوشتم که توش اینو توضیح دادم.
خلاصه اش میشه محدودیتِ کیف و چمدونِ خطوط هوایی. هر چمدون فقط میتونه ۵۰ پوند (حدود ۲۲.۶ کیلوگرم) یا کمتر وزن داشته باشه. ولی شما لازمه که ۱۵۰ پوند وسیله با خودتون بیارید مثلاً. پس اونارو توی ۳ تا چمدون پخش میکنید که همشون بتونن باهاتون پرواز کنن در حالی که محدودیت رو هم نقض نکردن.
نویسنده: shrutikapoor08
منم اتفاقاً یه پست در راستای توضیح دادن جداسازی کد نوشتم که اینجاس. اگه خوب بنظر برسه منتشرش میکنم. {منظورش یحتمل اینه که بعنوان یه پست روی وبلاگش..}
نویسنده: gaearon
ابطال و کنترل کردن برای سر و کله زدن با چیز هایی که "زیادی اتفاق میوفتن" روش های بسیار متداولی هستن. تصور کنید یکی از دوستانتون رو دیدید، و داره براتون یه قصه تعریف میکنه، ولی در حین صحبت به سختی مکث میکنه. بیایید فرض کنیم قراره یه اقامتگاه هم برای ایشون ردیف کنید در حالی که همچنین پاسخ صحبت هاش رو هم میدید، وقتی که ممکن بود. (میدونم یکم تخیلیه، ولی تحملش کنید!)
فرض کنیم هیچوقت نمیتونید همزمان با هم حرف بزنید / تو حرف هم بپرید. حالا شما چند تا استراتژی دارید:
میتونید جواب همه جمله هارو همون لحظه که بیان میشن، بدید:
این اگه پاسخ های کوتاه بدید میتونه جواب بده. ولی اگه جواب هاتون بلندتر باشن، میتونه باعث بشه دوستتون به سختی داستانش رو تموم کنه. پس این استراتژی عالی نیست.
میتونید منتظر بمونید تا وقتی دیگه حرف نزنه. مثلاً، اگه یه مکث طولانی داشته باشه، میتونید جوابتون رو شروع کنید:
اگه دوستتون گاهاً بین حرفاش مکث بکنه این استراتژی میتونه جواب بده. گرچه اگه یه چند دقیقه بدون وقفه حرف بزنه، کلاً نمیتونید جواب بدید:
میتونید با خودتون قرار بذارید که حداکثر هر یک دقیقه، یک جواب بدید. اینطوری، آماره اینکه الان چند ثانیه است صحبت نکردید رو دارید. همینکه شد یک دقیقه و هنوز صحبت نکرده بودید، جوابتون رو درست بعد از جمله ی بعدیه دوستتون میچپونید:
"جملات" دوستتون event هایی مثل کلیکِ یه دکمه یا تایپ کردن با کیبورد هستن. "جواب ها" ی شما آپدیت های صفحه ان.
از ابطال کردن [debouncing] یا کنترل کردن [throttling] وقتی استفاده میکنید که کاربر داره یه کاری رو خیلی تند تند انجام میده (مثل تایپ کردن)، و آپدیت کردن صفحه در جواب هر رویداد جداگانه زیادی کُنده. پس یا منتظر میشید که کاربر تایپ کردنش رو تموم کنه (ابطال کردن) یا هر از چندگاهی صفحه رو آپدیت میکنید، مثلاً هر یک ثانیه یه بار (کنترل کردن).
قسمت جالب قضیه اینجاست که startTransition در موارد زیادی باعث بیهوده شدن هر دو استراتژی میشه. بیایید یه استراتژی دیگه هم که قبل از ریاکت ۱۸ نداشتید اضافه کنیم:
شما به محض اینکه دوستتون یه جمله میگه جوابش رو میدید، بی هیچ محلتی. اما با دوستتون توافق کردید که میتونه هر وقت خواست بپره وسطه حرفتون اگه میخواست حرفشو ادامه بده. پس در این حالت شما جوابتون رو نصفه رها میکنید. بعد بلافصله بعد از جمله بعدی، دوباره تلاش میکنید. و به همین شکل:
(البته اینم توافق میکنید که اگه مقدار معینی زمان گذشت و شما جوابی نداده بودید شما هم توی حرف اون میپرید. این باعث میشه از اون حالتی که شما برای یه مدت طولانی هیچ حرفی نمیزدید جلوگیری بشه.)
توجه کنید که توی این استراتژی شما هیچ زمانی رو هدر نمیدید و همچنین از ادامه دادن دوستتون هم جلوگیری نمیکنید.
این بین سه تا راه کار قبلی بهترینه. (کی فکرشو میکرد که تعاونی بودن [being cooperative] کمک میکنه!)
اگه بخوایم با کُد مثال بزنیم، این بدین معناست که ریاکت بلافاصله بعد از اینکه کاربر شروع میکنه به تایپ کردن، شروع میکنه به render کردن. هیچ نیازی نیست اول یکم صبر کنه بعد (مثل کاری که تو ابطال کردن [debouncing] میکنه). اگه کاربر دوباره تایپ کرد، ریاکت صرفا اون کارشو رها میکنه و دوباره از اول شروع میکنه. اگه render کردن به اندازه ای که توقف لای event ها جا بده سریع بود، اون موقع هیچ زمانی رو هدر ندادید (با بیهوده صبر کردن). همچنین هیچ زمانی رو واسه تموم کردن render ای که دیگه بهش نیازی نیست هدر ندادید.
میتونید یه دمو از تفاوت های اینا اینجا ببینید. یه چیزی توی input بنویسید. هر چی بیشتر بنویسید، نمودار پیچیدهتری هم کشیده میشه (که مجازاً کُند شده تا نکته رو نشون بده). رفتار های متفاوتِ بین ۳ تا استراتژی رو مشاهده کنید. وقتی یه متن طولانی رو پشت هم توی input تایپ میکنید چه رفتاری نشون میده؟
کدوم یکی حس هموارتری داره؟
نویسنده: gaearon
بنظرم این به یک جمله ی توی ۴# ارجاع داره.
این ویژگی ها به صورت مشارکت اختیاری توی یک اساس زیرْ درختی [sub-tree basis] بدون فعال کردن حالت strict برای کل اپلیکیشنتون هستن.
جمله بندی گیج کننده ای داره {خدایی..}. اینجا "توی یک اساس زیرْ درختی" به معنای "برای تک به تک اعضای درخت بجای کل app" هست. من اگه بودم اینو اینطوری میگفتم:
میتونید شروع کنید به استفاده از ویژگی ها روی قسمت کوچکی از درختتون بدون اینکه لازم باشه حالت strict رو برای کل اپلیکیشنتون فعال کنید.
نویسنده: gaearon
به زبان محاوره، یه "تحولِ state" معادله با "تغییر state". مثلاً، شاید شما state عه activeTab رو از "خانه" به "پروفایل" تغییر بدید. میتونید بگید این یه "تحولِ state" عه از خانه به پروفایل.
معمولاً ملت منظورشون از "تحول" [transition] آپدیت های بزرگتریه. مثل وقتایی که اتفاق های بیشتری میوفته. شاید یه سری داده باید fetch بشن، یه قسمت بزرگی از صفحه دچار تغییر و تحول بشه، شاید یه مقدار انیمیشن باشه.
ریاکت ۱۸ بر اساس این بینش ساخته شده یه مفهومی صریح از "تحولات" [transitions] رو معرفی میکنه (۴۱#). با علامت گذاری یه آپدیت state بعنوان یک تحول [transition]، به ریاکت این اشاره میکنید که این ممکنه یه مقدار کار ببره. ریاکت اجازه میده که تحولات [transitions] توسط آپدیت های ضروری تر قطع بشن، مثل تایپ کردن توی یه input. در آینده، ما داریم ادغام کردن این مفهوم رو با انیمیشن ها هم در نظر میگیریم.
نویسنده: gaearon
کامپوننت های سرور یه ویژگیِ آزمایشیِ ریاکتیِ جدید هستند. احتمال داره که جزئی از ریاکت ۱۸ نباشن، ولی بالاخره یه زمانی به ریاکت اضافه خواهند شد بعداً. به شدت پیشنهاد میکنیم صحبت معرفیش رو مشاهده کنید که ایده اش رو توضیح میده چون هیچ چیزی تاحالا واقعاْ شبیهش نبوده، پس مقایسه کردنش به چیزهایی که وجود دارن یکم سخته.
میتونید اینطوری بهش فکر کنید. یه سرور (جایی که ممکنه مرکز داده [databse] تون باشه) و یه client (مثلاً یه گوشی یا یه کامپیوتر) دارید. مرورگر روی client اجرا میشه. اپلیکیشن ریاکتی تون هم روی client اجرا میشه. پس کامپوننت های شما:
<Panel> <SearchField /> <FriendList /> </Panel>
هم روی client اجرا میشن. (روی گوشی و یا کامپیوترِ یه کاربر).
این به این معناست که:
کامپوننت های سرور بهتون این اجازه رو میدن که کامپوننت هاتون رو روی سرور قرار بدید. مثلاً، اگه کامپوننت های <Panel> و <FriendList> به خودیِ خود هیچ event handler (مثل کلیک) یا state ای ندارند، دیگه دلیلی نداره که حتماً روی کامپیوتر کاربر اجرا بشن. میتونن روی سرور اجرا بشن، درست همونطوری که API شما روی سرور اجرا میشه!
چرا اینطوری بهتره؟
پس سرور کامپوننت ها راهی هستن برای:
نویسنده: shaundai
راه های مختلفی برای نوشتن کامپوننت وجود داره، ولی کامپوننت های تابعی [functional components] عالین چون بهتون اجازه میدن به راحتی محاسبات پیچیده انجام بدید و منطق رو به آسونی به اینور اونور منتقل کنید. مثلاً اگه میخواید یه فایل json. طولانی به همراه داده رو بگیرید و بعد اون داده رو به عنوان یه جدول روی وبسایت یه نفر render کنید، میتونید همهٔ این کارا رو اساساً با یک تابع انجام بدید. کامپوننت های تابعی بینقصن، فقط یه "مچتو گرفتم" دارن: نمیتونن state رو نگه دارن.
قبلنا (قبل از هوک ها) اگه میخواستید از state استفاده کنید، باید از چند نوع کامپوننت مختلف استفاده میکردید (از کامپوننت های کلاسی [class components] برای هر چیزی که state داره و از کامپوننت های تابعی [functional components] برای اینکه با اطلاعات یه حرکتی بزنید) و این خیلی کمتر تمیز بود.
هوک ها وارد شدن. هوک ها بهتون اجازه میدن ویژگی های ریاکتی ای مثل state و متد های lifecycle رو "قلاب کنید" {معنی لغوی هوک میشه قلاب یا قلاب کردن} تا که کد تمیز و شفافی داشته باشید. بعضی هوک ها "به طور پیش فرض" وجود دارن (برای دریافت و آپدیت کردن state، شما useState رو دارید، یا برای ممکنه برای تغییر دادن چیزها با استفاده از متد های lifecycle از useEffect استفاده کنید).
نویسنده: gaearon
بنظرم هم "کامپوننت های تابع" [function components] هم "کامپوننت های تابعی" [functional components] در زبان محاوره مشکلی ندارن. ما وقتی که hooks رو معرفی کردیم بجای "functional" که قبلاً استفاده میشد گفتیم "function" چون نگران این بودیم که ممکنه نابگرایانِ [purists] برنامه نویسیِ تابعی [functional programming] به اینکه هوک ها در اون معنای برنامه نویسی بر اساس تابع [functional programming]، "تابعی نیستن" [not being functional] خًرده بگیرند. (هرچند یه جورایی هستند — ولی خب اون یه بحث جداست.) من شخصاً ایرادِ بنی اسرائیلی ازشون نمیگیرم مگر توی داکیومنت ها [docs]. جایی که قراره فقط "کامپوننت" صداشون کنیم از اونجایی که کامپوننت های کلاسی [class components] کم کم دارن به عنوان یه مورد تخصصی شده، بیشتر میراثی، محو میشن.
نویسنده: meganesu
توی ریپازیتوریه TIL ام یه نگاهی به این انداختم:
رفرش سریع [fast refresh] یه ویژگیِ ریاکته که وقتی توی ادیتور (مثلاً VS Code) روی کُدتون تغییراتی اعمال میکنید بلافاصله کامپوننت هارو render میکنه. با رفرش سریع [fast refresh]، نمای مرورگرتون بدون از دست دادن state عه کامپوننت ها آپدیت میشه. که منجر به بهبود یافتن تجربه ی توسعه دهندگی میشه.
رفرش سریع [fast refresh] تکرارِ جدیدیست بر hot reloading. قرار بود hot reloading هم مزایای مشابهی داشته باشه، ولی نتونست به خوبی با کاموننت های تابعی [functional components] و هوک ها کار کنه. همچنین error ها رو هم به خوبی handle نمیکرد، اینطوری که اگه یه اشتباه تایپی میکردید مجبور میشدید app تون رو کلاً restart کنید. رفرش سریع [fast refresh] اون کاستی هارو برطرف کرده.
نویسنده: gaearon
کامپوننت های ریاکتیِ شما اطلاعات رو به رابط کاربری [UI] منتقل میکنند. دو نوع اطلاعات داریم.
بعضی از این اطلاعات مادامِ حضور یک کاربر [user session] هیچ وقت تغییر نمیکنند. مثلاً چیدمان وبسایت شما، آیکون های روی دکمه هاتون، و ترتیب بخش ها روی صفحه.
بقیه اطلاعات ممکنه در پاسخ به تعامل کاربر تغییر کنند. مثلاً، آیا یه دکمه hover شده یا نه. کاربر چند بار روی "اضافه کردن" کلیک کرده. محتوای سبد خریدشون یا پیامی که در حال تایپ کردنش بودن.
اطلاعاتی که در نتیجهٔ تعامل کاربر تغییر میکنه رو بهش میگن "state".
هر اطلاعاتی یه طول عمری داره. به یه چیزی وصله. مثلاً، متغیر های جاوا اسکریپتیِ عادی به function call ها وصلن. فقط مادامی زنده ان که شما درون تابع هستید. وقتی از یک تابع خارج میشید، "موجودیت" متغیر های اون call از بین میره. (بجز بحث closure ها، که وقتی توابع رو nest میکنین بوجود میان، و باعث میشن که یه تابع nest شده بتونه متغیر هایی از تابع خارجی ای که call شده رو "همچنان مشاهده کنه".)
توی ریاکت، هر دفعه که یه کامپوننت render میشه، یه function call جداگانه به حساب میاد. پس اگه سعی کنید مقداری اطلاعات مثل محتوای سبد خرید رو توی یه متغیر عادی ذخیره کنید، بعد از render بعدی به سادگی محو میشه. واسه اینه که ریاکت مفهومی مِن باب "متغیر های اِستیت" [state variables] داره که با useState تعریفش میکنید. اونا با قسمتی از درخت مرتبطن — مثلاً، یه text input عه مشخص یا یه سبد خریدی که روی صفحه میبینید — بجای اینکه با یه function call مرتبط باشن.
اینکه کجا متغیر اِستِیتون رو میسازید حائز اهمیته. میشه اینطوری بهش فکر کرد. اگه چیزی رو توی دو قسمت از صفحه render کنید، دلتون میخواد هماهنگ [synchronized] باشن یا نه؟
مثلاً، render کردن دو تا فیلدِ نظر [comment field] معنیش این نیست که میخواین وقتی توی یکی تایپ میکنید اون یکی هم آپدیت بشه. پس اینکه state رو بذاریم توی فیلدِ نظر منطقیه. اینطوری هر کپی مستقل میشه.
ولی شاید دارید نظری که قبلاً ارسال شده رو توی دو جا render میکنید. اگه یکیش رو edit کنید، و ذخیره اش کنید، منطقیه که انتظار داشته باشیم هر دوشون edit شما رو منعکس کنن. پس اطلاعات لیستی از نظرات باید جایی خارج از کامپوننتون باشه. — شاید، توی parent مشترکشون یا توی یه جور cache.
این چیزیه که ملت منظورشونِ وقتی راجع به انواع مختلف state حرف میزنن. اینا اصطلاحات تکنیکال نیستن، بلکه "اِستیتِ رابط کاربری" [UI state] اغلب به معنای یه جور state ایه که به طور خاص مربوطه به یه جور رابط کاربریِ بهم چسبیده [concrete UI widget]. مثل متنِ text input. درحالیکه "اِستیتِ عه اپ" [app state] معمولاً به معنای مقداری اطلاعاتِ به اشتراک گذشته شده در بین تعداد زیادی کامپوننته و لازمه که هماهنگ باشه [in sync]. پس معمولاً خارج از اونا گذاشته میشه.
نویسنده: gaearon
وقتی که کاربر کاری انجام میده (مثلا کلیک کردن روی یه دکمه)، شاید بخواین چیزی که روی صفحه هست رو تغییر بدین. که با set کردن state انجامش میدید. در پاسخ، ریاکت یه "render" انجام میده.
در حین یک render، ریاکت اون کامپوننتی که شما state اش رو set کردین رو صدا میکنه. کامپوننت شما اون چیزی که باید روی صفحه باشه رو return میکنه. بعد، ریاکت همینکارو واسه کامپوننت های زیرینِ اون کامپوننته انجام میده (چون این احتمال که شاید اونا هم چیز متفاوتی نشون بدن وجود داره). این پروسه اسمش render کردنه.
این شاید شمارو یادِ برنامه ریزیِ از بالا به پایین بندازه. یه شرکت طراحی ممکنه یه سفارش دریافت کنه. کارگردان طرح، چشم اندازی رو تنظیم میکنه و تصویرسازی [illustration]، تایپوگرافی، یا طراحی لوگو رو میده دست کسانی که تخصص اون کار رو دارن. اونا هم، به نوبت، یحتمل قسمت هایی از اون کار رو میدن دست بقیه همکار ها. و به همین ترتیب. در نهایت نتیجه ای مرکب از کار آدم های مختلف حاصل میشه.
این معادل چگونگیه مرکب بودنِ اتفاقیه که نهایتاً روی صفحه میوفته از نتایج render کامپوننت های مختلف.
فرض کنیم اون مشتریِ شرکت طراحی حالا میخواد یه چیزی رو تغییر بده. "بیاید رنگ هارو بهتر کنیم." پس اون پروسهٔ از بالا به پایین مجدداً شروع میشه. کارگردان طرح با طراحی حرف میزنه که با کسی که طرح های اولیه رو میسازه حرف میزنه، که تغییرات رو اعمال میکنه. بعد یه طرح جدید گیرمون میاد.
این میشه همون "re-render". یعنی وقتی که state رو دوباره set میکنید، ریاکت کامپوننت هاتون رو صدا میکنه تا بفهمه چی باید روی صفحه باشه، و بعد صفحه رو آپدیت میکنه. "re-render" یعنی "دوباره render کردن"، نه هیچ چیزی فراتر از اون.
نهایتاً، یه "render عه تلف شده / بی فایده" [wasted render] یعنی قسمتی از کاری که انجام شده بیهوده بود. مثلاً، اگه مشتریمون بگه "یکم با فونت هاش بازی کنید" و کل شرکت شروع کنه به تکرار کردن دوباره همه چیز. ولی واقعیتش اینه که فقط کارگردان طرح و طراح فونت لازم بود درگیر بشن. نیازی به درگیر کردن کسی که روی پالت رنگ ها کار میکرد نبود.
توی ریاکت، این ممکنه وقتی که دارید state عه کامپوننتی که شامل کامپوننت های خیلی زیادی میشه رو آپدیت میکنید اتفاق بیوفته. ممکنه که آپدیت روشون تأثیری نذاره، اما بازم ریاکت باید از تک تکشون بپرسه که آیا میخوان چیز متفاوتی رو نشون بدن یا نه. پس اگه در پاسخ چیزی رو عوض نکن این یه "کار بیهوده" است.
چندین راه متداول برای بهینه سازیِ render های "تلف شده / بیهوده" وجود داره:
پینوشت: میدونم که یحتمل شرکت های طرحی اینریختی از بالا به پایین کار نمیکنن. فقط یه استعاره بود.