یاد میگیرم، تجربه میکنم، اشتباه میکنم و این چرخه من است تا موفق بشم یا ازش درس بگیرم.
خداحافظ دشواریهای Redux
اگر بخواهیم با ریداکس در یک پروژه اینترپرایز کار کنیم اگر در حین توسعه جان ندیم قطعا دیونه خواهیم شد!
کل داستان این هست که میخوای دادهها رو در محلی قرار بدی تا بتونی اونها رو در هر کامپوننتی، مستقل از این که کجاست و زیر مجموعه چندتا کامپوننت دیگه هست، بخونی. اگر هم لازم به تغییر دادهای بود،بعد از تغییرش تمام کامپوننتهایی که از اون داده استفاده میکنن مقدار جدید رو بگیرن یا به اصلاح به روز بشن! همین و تمام!
حالا همین رو بخوای پیاده سازی کنی، جا داره بگیم خدا نصیب گرگ بیابون نکنه! اما نگران نباشید من راهی رو بتون یاد میدم تا کار با ریداکس به سادگی داستان بالا باشه.
چطور Redux را ساده کنیم؟
من هم مثل شما از سختی ریداکس به تنگ آمده بودم و این شد که یک پکیج مینیمال (کمتر از 3 کیلوبایت) به نام trim-redux رو نوشتم.
این پکیج یک واسط هست که شما با دستورات ساده درخواستهاتون رو به اون میدید و اون با ریداکس ارتباط برقرار میکنه و نتیجهای که ریداکس میده رو بتون تحویل میده. چیزی شبیه UI بین کاربر و Back-End قرار داره و کار رو ساده میکنه.
در تریم-ریداکس reducer , dispatch , combineReducer و action حذف شده و فقط یک متد setStore دارید که مثل setState در کامپوننتهای ریاکت باید باش کار کنید. جذاب نیست؟! :)
۱) نصب trim-redux
قدم اول نصب ابزار به کمک دستور زیر هست.
npm i trim-redux
۲) ساخت Store
اولین بخش شگفت انگیز ماجرا اینجاست. گفتیم reducer و combineReducer نداریم پس چطور stateها رو تعریف کنیم و مقدار اولیه هم بشون بدیم؟
فایل store.js رو میسازیم، از trim-redux متد createStore رو import میکنیم. حالا وقت تعریف stateهای ریداکس با مقادیر اولیه رسید. برای همین شی state رو میسازیم. حال به متد createStore به عنوان پارامتر اول پاس میدیم.
import {createStore} from 'trim-redux'
const state = { age : 0 }
export const store = createStore(state);
اما اتفاقاتی که در پشت قضیه میافته:
به ازای هر پارامتر در ریشه شی state شما یک reducer با همان نام در ریداکس ساخته می شه و مقدار اولیهای که براش قرار دادید هم به عنوان مقدار اولیه state قرار داده میشه. پس بعد از اجرای کدهای بالا یک استور ریداکس دارید که یک state با نام age با مقدار صفر در اون وجود داره.
میشه گفت قطعه کد بالای شما جایگزین تعریف reducer , combineReducer شده. اما سوال این هست تکلیف action و dispatch چی میشه و چطور این دادهها رو بخونیم و آپدیت کنیم؟
۳) اتصال Store به Redux
در این بخش کار جدیدی قرار نیست بکنیم و خودتون استاد ما هستین و میدونید باید store رو به عنوان پارامتر و App (کامپوننت ریشه) رو هم به عنوان Child به Provider ارسال کنید و در نهایت به render بدید.
با نصب trim-redux دیگر به نصب پکیج react-redux نیاز نیست و اگر قبلا نصب کردهاید آن را uninstall نمایید و import متدها را با trim-redux انجام دهید. در واقع trim-redux یک نسخه از react-redux را در درون خود export کرده تا برای کار با ریداکس تنها با یک پیکج کار کنید.
خب طبق توضیحات این بخش، فایل index.js چیزی شبیه به این خواهیم بود:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'trim-redux'
import store from './store'
import App from './App'
const rootElement = document.getElementById('root');
ReactDOM.render( <Provider store={store}> <App /> </Provider>, rootElement );
۴) تغییر دادها
قبلا این بخش رو لود دادم :) این کار به کمک متد setStore انجام میشه. با این متد هم میشه مثل setState کامپوننتهای ریاکت کار کنید یعنی یک شی که حاوی نام و مقدار جدید stateهای مد نظرتون رو پاس بدید بش و هم میتونید نام state مورد نظرتون رو به صورت رشته به عنوان اولین پارامتر اول و مقدار جدیدش رو به عنوان دومین پارامتر ارسال کنید.
فرق خاصی ندارن فقط دومی یه کوچولو پرفورمنسش بهتره،واسه ادمای حساس گذاشتم غر نزنن :)
import {setStore} from "trim-redux"
// حالت ارسال شی
setStore( { age : 20 } );
// حالت ارسال مستقیم
setStore( "age " , 20 );
کلش همینه! این متد رو شما از هر جایی که دلتون خواست صدا بزنید باعث میشه که ریداکس با مقدار جدید به روز بشه دیگه کانکت و دیسپچ و اکشن و اینا نمیخواد.
۵) خواندن دادهها
برای خواندن دادهها هم یک متد داریم به نام getStore که اگر اسم state مورد نظرتون رو به صورت رشته به اون ارسال کنید مقدارش در اون لحظه رو بتون میده. اگر هیچی ارسال نکنید کل دادههای store به شکل یک شی در خروجی در دسترس قرار میگیره.
import {getStore} from 'trim-redux'
// حالت شی
const age = getStore().age;
// حالت مستقیم
const age = getStore('age')
متد getStore رو هر وقت شما صدا بزنی دادهای store رو در اون لحظه میده این یعنی این متد به روزرسانی نداره و صرفا یک متد خواندن مستقیم هست. کاربردش در مواردی هست که در بین کدها در اون لحظه یه دادهای در ریداکس نیاز داریم و برامون مهم نیست بعدا چی میشه یا قبلا چی بوده.
مثلا کاربر روی یک دکمه ارسال نظر کلیک میکنه در اون لحظه شما نیاز دارید وضعیت لاگین بودن یا نبودن کاربرتون که در ریداکس با مقدار isValidUser نگهداری میشه رو بخونید تا ببینید ایا پنجره لاگین رو نشون بدید یا نه به سرور نظر رو ارسال کنید.
اما اگر لازم باشه تا با تغییر مقدار ریداکس اتوماتیک کامپوننت به روز بشه چه باید کرد؟
۶) به روز شدن کامپوننتها بعد از تغییر دادها در ریداکس
در اینجا چیز جدیدی وجود نداره و از همون ساختار ساده connect که از متدهای پکیج react-redux هست استفاده میکنیم.
به نقلقول مرحله (۲) دوباره نگاه کنید، برای اینجا هم کاربرد دارد.
برای این کار کافیستاز متد connect استفاده کرده و شی map state to props را به آن پاس دهیم. حتی اگر در بدنه کامپونتت هم از آن استفاده نکنیم با setStore کردن مقداری جدید برای age کامپوننت MyApp به روز خواهد شد. (lifecycle متدهای مربوط به آپدیت کامپوننت اجرا می شوند).
// map state to props
const mstp = state => ({ age : state.age });
export default connect(mstp)(MyApp);
مثال کامل (کامپوننت MyApp متصل به استیت age ریداکس):
import React from 'react';
import {connect} from "trim-redux"
// MyApp component
const MyApp = (props) => <div>{props.age}</div>;
// map state to props
const mstp = state => ({ age : state.age });
export default connect(mstp)(MyApp);
جمع بندی
دیدم که با واسط trim-redux تعریف کردن یک State جدید در ریداکس فقط به نوشتن نامش در شی state و دادن مقدار اولیه خلاصه شد و در نهایت تغییر دادهها فقط به صدا زدن متد setStore ختم شد.
همین چند تغییر ساده بخش قابل توجهی از کدها را حذف کرد و کم شدن حجم کدها هم باعث افزایش خوانایی کدها شد.
از طرفی این واسط تا حدی هم باعث افزایش پرفرمنس نیز شده است. در روش قبل شما برای هر عملیات یک action و به ازای هر action یک case در reducer تعریف میکردید. ریداکس به ازای هر dispatch کل reducerها را اجرا میکند و کلیه caseهای آن چک می شود.
پس اگر mتا reducer داشته باشیم و که هر کدام به طور متوسط nتا case داشته باشند به طوری که n>=1 و m>=1 است در نهایت m*nتا شرط در هر dispatch چک می شود. اگر m=5 و n=3 باشد آنگاه 15 شرط با هر بار dispatch چک میشود. این پکیج به ازای هر state تنهای یک case دارد پس در مثال بالا تنها 5 شرط با هر بار dispatch چک میشود.
در نهایت میتوان گفت بکارگیری این پکیچ در بدبینانه ترین حالت اگر سود شگفت انگیزی هم عایدمان نشود قطعا بهتر از روش معمولی خواهد بود.
این مطالب هم میتونه واست سودمند باشه:
مطلبی دیگر از این انتشارات
React و عملگرهای Bitwise
مطلبی دیگر از این انتشارات
از تشخیص کلیک خارج از کامپوننت تا Type Assertions در تایپاسکریپت
مطلبی دیگر از این انتشارات
تیم ریاکت دارن چیکار میکنن؟ (قسمت دوم)