یکی از مهم ترین و کارآمد ترین state manager هایی که وجود داره و در عین حال همه مون ازش غفلت میکنیم، آدرس صفحه و یا همون url هست. این به این معنی نیست که کلا باید استیت منیجر های قدرتمندی مثل Redux رو بزاریم کنار. حضور ریداکس و ContextAPI در یک پروژه ی Large Scale با React اجتناب ناپذیره. پس بیاین بریم تو بحرش ببینیم منظور از url as a state manager چیه؟
سعی میکنم یه تجربه ی عملی ای که داشتم رو بگم تا با مثال واقعی بشه معنی و مفهوم رو برسونم.
ما تو اسنپ فود، صفحاتی داریم که در اون گهگاه، شما فیلترهای مختلفی اعمال میکنید. مثل این صفحه
خب این صفحه دسته های مختلفی از فیلتر رو داخل خودش داره، مثل
و
خب، اینجا یه اتفاقی می افته. اونم اینه که علاوه بر اینکه باید انواع مختلف فیلترها رو توی این صفحه هندل کنید، دیپ لینک هایی که به این صفحه میرسن و فیلترهای خاصی درشون وجود داره هم باید درست مدیریت بشن و داده های متناسب با خودشون نشون بدن. بیاین فرض کنیم بخوایم از لوکال استیت و ریداکس استفاده کنیم برای این کار ببینیم. چی میشه:
تو مرحله ی اول شما یه سری local variable و local state تعریف میکنید، با هر تغییر توی مقادیر این فیتلرها، این استیت ها رو آپدیت میکنید، بعد از اینکه این کارها تموم شد، و کاربر روی دکمه ی "اعمال" کلیک کرد شما به عنوان استیت کلی این صفحه، مقادیر فیلتر ها رو توی ریداکس ست میکنین و بعد ریکویست و ادامه ی ماجرا.
خب میدونین فاجعه کی رخ میده؟ آفرین وقتی صفحه رو رفرش کنید، یا رفت و برگشتی به این صفحه داشته باشین، همه ی فیلترهای قبلیتون که اعمال شده بود میپره. شاید بگین خبالا، persist ش میکنیم. آفرین منم همینجوری فک میکردم. از وسطای ورژن قبلی بود که ما persistence رو به اپ اضافه کردیم و خب جواب هم داد، اما تا حالا به این دقت کردین که دارین کجا این کار رو میکنین؟ به نظرم جواب همه تون local storage باشه اما هیچ وقت از خودتون پرسیدین که لوکال استوریج چقد استیت منیجر پر هزینه ایه؟
اگه به روال معمول، ریداکستون رو توی لوکال استوریج پرسیست کنید، تقریبا با هر آپدیت ریداکس، دارین یه transaction با لوکال استوریج انجام میدین، یعنی یه بار آپدیت لوکال استوریج! به نظرتون برای یه اپ large scale یکم سنگین به نظر نمیاد؟ باعث کند شدن Run Time نمیشه؟ به والله که میشه! اما یه مساله ای ، پرسیست شدن یه وب اپلیکیشن از اوجب واجباته، پس چیکار کنیم که این مساله حل بشه؟؟؟
اینجاست که باید بگیم باید تا جایی که میشه transaction ها رو کم کنیم. برای همین یه سری میزنیم به url
حالا فرض کنیم شما میخواین فیلترهایی که گفتم رو ببرین توی query parameter های صفحه و اونجا استیتشون رو نگه دارین. ترتیب کار چجوری میشه؟!
این دفعه باز هم مجبورین یه سری local state تعریف کنید، تغییرات کاربر رو توی اون ذخیره کنید و بعد از اینکه روی "اعمال" کلیک کرد، دیگه سراغ ریداکس نمیریم! حتی سراغ ContextAPI هم نمیریم، چون یه چیز منطقی تر به نام url وجود داره. بعد از کلیک روی اعمال ، استیت صفحه رو توی url آپدیت میکنیم!
این یعنی چی؟ این مساله یعنی اینکه شما key value های فیلترهاتون رو میریزین توی query parameter و آدرس جدید تولید شده رو با مقدار قبلی replace میکنید، تنها کاری که باید بکنید اینه که یه useEffect روی آدرس داشته باشین و هر وقت آدرستون آپدیت شد، درخواست جدید رو به سرور بدین. این کار باعث میشه هر رفرش و تغییر مسیری هم شما رو برمیگردونه به همین استیت، بدون نیاز به persist شدن.
آمّاااا
قضیه اینه که این کار به این سادگی ها هم نیست. چرا؟ چون که مجبورین کلی util یا custom hook بنویسین که این کار رو براتون مدیریت کنه. ولی یه راه فرار بهتون نشون میدم که همه چی رو آسون میکنه اونم این package زیباست:
این پکیج همه ی چیزی که شما برای این مدل استیت منیجینگ نیاز دارین رو بهتون میده به سادگی. تنها کاری که باید بکنید اینه که object ای که در query param قراره ساخته بشه رو بدین بهش. به این صورت:
const [query, setQuery] = useQueryParams({ x: NumberParam, q: StringParam, filters: withDefault(ArrayParam, []), });
این کار یعنی من قراره توی query parameter هام، یه متغیر x داشته باشم از نوع number، یه متغیر q از جنس string و یه filters که به صورت پیشفرض قراره arrayParams باشه. این هوک به شما دو تا مقدار بر میگردونه. یه Object به نام query و یه تابع به نام setQuery
هر وقت خواستین یکی از این مقادیر رو آپدیت کنید، کافیه فقط روی همون مقدار، setQuery بزنید، خودش براتون آدرس رو آپدیت میکنه. مثلا:
setQuery({ x: Math.random },'replaceIn')
این کار به q و filters دست نمیزنه و مقادیر قبلیشون رو نگه میداره و فقط مقدار x رو آپدیت میکنه. حالا تنها چیزی که باید رعایت کنید، useEffect روی query هست. هر وقت setQuery کال بشه، این useEffect هم کال شده و آپدیت جدید رو بهتون میده اونوقت با مقادیر جدید فیلترهاتون میتونید حال کنید!
یه نکته ی خیلی خیلی مهم هم اینه که میتونید Custom تایپ تعریف کنید. یعنی بگین متغیر y من از جنس مثلا CustomObject هست و برای CustomObject یه تابع بنویسید که آبجکت فیلتر شما رو میگیره و تبدیل میکنه به مقداری که باید توی y بشینه
مثلا:
const MyParam = { encode(value) return `${value * 10000}`; }, decode(strValue) { return parseFloat(strValue) / 10000; }} const [query, setQuery] = useQueryParams({ foo: MyParam }); setQuery({ foo: 99 })
این تایپ Custom عدد ورودی رو در 10000 ضرب کرده و در پارامتر foo جایگذاری میکنه
حالا برای اینکه دیپ لینک های خارجی درست مپ بشین به این صفحه، تنها کاری که باید بکنین اینه که translator بنویسید که این ساختار کاستوم خودتون رو بهتون تحویل بده. همین و تمام!