آموزش ریداکس قدم به قدم

منتشر شده در 1398/07/22 | بازدید : 675 بار | زمان مطالعه : 10 دقیقه

اگر با مدتی با ری اکت کار کرده باشید احتمالا با ایده ی پاس دادن prop ها از یک کامپوننت به بعدی و از آنجا به بعدی و همینطور به بعدی و بعدی آشنا هستید و با آن کار کرده اید. شاید یادگیری و استفاده از این مفهموم راحت باشد اما مطمئنا راه کارامدی نیست. روش های متنوعی برای مدیریت state در ری اکت وجود دارد اما ریداکس یکی از بهترین های آنهاست که متاسفانه خوب و به زبان ساده یاد داده نمی شود. در این مطلب به همراهی شما می خواهیم مفهوم ریداکس را بیاموزیم.

ریداکس چیست؟

به کمک ریداکس ما می توانیم state برنامه را در یکجا ذخیره و مدیریت کنیم. به جای اینکه چندین کامپوننت هر کدام درگیر مدیریت state شوند از فقط و فقط یکجا آن را تغییر می دهیم.

در ریداکس state را در چیزی با نام «Redux Store» ذخیره کنیم و با استفاده از «action» ها و «reducer» ها آن را تغییر می دهیم. اجازه دهید بیشتر در این رابطه توضیح دهیم.

فرض کنید به یک گوشی موبایل نیاز دارید. به فروشگاه محل خودتان که با شما آشنا است سر می زنید تا آن را تهیه کنید. او به شما پاسخ می دهد که موجودی ندارد و می تواند از همکار خود یعنی فروشگاه کناری گوشی شما را برای شما تهیه کند. آن فروشگاه هم گوشی مد نظر شما را ندارد و باید از بازار تهیه کند و طی یکی دو روز آینده به دست شما برساند.

حالا فرض کنید به جای این راهکار از دیجی کالا گوشی خود را مستقیما خریداری کنید؟!

اتفاقی که با ریداکس می افتد دقیقا همین است. به جای پاسکاری prop بین چندین کامپوننت همسایه شما مستقیما با مخزن state در ارتباط هستید. خیلی ساده چیزی را که میخواهید مستقیما از «Redux Store» می گیرید!

ریداکس استور 

ساختن یک ریداکس استور به آسانی نصب پکیج Redux و ایمپورت کردن تابع createStore در کامپوننت اصلی (معمولا index.js) است.

import { createStore } from 'redux'

const store = createStore(ourReducer)

به همین راحتی! الان مخزن شما در متغیر store است. تابع createStore یک پارامتر ورودی دارد و آن reducer شماست. reducer همان بخشی است که به کمک آن state را دستکاری می کنید. (فعلا زیاد به آن فکر نکنید. فقط به این فکر کنید که الان یک store دارید که state شما در آن است و از هر کامپوننتی به آن دسترسی دارید و در یکجا مدیریت می شود)

دادن دسترسیِ state به کامپوننت ها

حالا وقت آن شده که کامپوننت ها به state دسترسی داشته باشند. Provider اصلی ترین بازیگر این بخش است. کامپوننت App را در کامپوننت Provider قرار دهید:

import { Provider } from 'react-redux'

ReactDOM.render(
<Provider store={store}>
    <App />
</Provider>
, document.getElementById("root"));

به تکه کد بالا اینطور نگاه کنید که قرار دادن کامپوننت App داخل کامپوننت Provider باعث می شود که کل اپلیکیشن شما قابلیت استفاده از store را داشته باشد. بسیار خوب تا اینجا توانستیم به اپلیکیشن توانایی دسترسی به state را بدهیم. قبل از آنکه به طور واقعی به state دسترسی داشته باشیم اول باید آن را تعریف کنیم.

Reducer کجای ماجرا قرار می گیرد؟

reducer ها بخش قدرتمند ریداکس هستند. بازوی اجرایی ریداکس برای مدیریت مسئله state همین reducer است. فایل js مربوط به reducer از دو بخش اصلی تشکیل می شود: state اولیه و خودِ reducer

به عنوان مثال برای سادگی داستان فرض کنید که state اولیه ما از یک آرایه شامل دو نام تشکیل شده است:

const initialState = {
   names: ['Ali', 'Maryam']
}

بخش اول فایل reducer به همین راحتی با تعریف state اولیه ایجاد می شود. اما اصل ماجرا تعریف خود reducer است. reducer چیزی نیست به جز یک تابع پر از قواعد اگر ... آنگاه. حالا شما می توانید این قواعد را با if و else یا با switch case بنویسید. فرض کنید در مثال ما قرار است state اولیه که شامل آرایه ای از دو اسم بود را به سه اسم تغییر دهیم. بنابراین عملیاتی که برای این منظور در نظر گرفته می شود یعنی action را 'ADD_NAME' در نظر می گیریم. برای نام گذاری action ها سعی کنید از حروف بزرگ استفاده کنید. اجازه دهید شروع کنیم:

export const reducer = (state = initialState, action) {
    if (action.type === 'ADD_NAME') {
        return {
            ...state,
            names: [...names, action.payload]
        }
    } else {
        return state
    }
}

همانطور که در تابع بالا یعنی همان reducer، دو ورودی گرفته می شود: state با مقدار اولیه initialState  و action. در واقع اینطور بخوانید که ما به reducer می گوییم state و کاری که قرار است روی آن انجام بگیرد یعنی action را بگیر و بسته با اینکه این action چیست یک خروجی بساز. این خروجی همان state جدید خواهد بود. مثلا در مثال بالا اگر action ما ADD_NAME بود به آرایه اولیه یک اسم دیگر اضافه کن و آن را به عنوان state جدید برگردان. دقت کنید که action خود عموما به شکل زیر است:

{ type: 'ADD_NAME', payload: 'Reza' }

در واقع action ها از دو بخش تشکیل می شوند. بخش اول که الزامی است و نوع یا type آن را مشخص می کند. و بخش دوم که اختیاری است و داده ی لازم یا payload را برای تغییر state در اختیار reducer قرار می دهد. بنابراین type دستورالعمل تغییر state را مشخص می کند و اگر این تغییر state نیاز به داده ی جدیدی داشت از payload می گیرد.

تا اینجا چه اتفاقی افتاد؟ اجازه دهید خیلی سریع مرور کنیم که تا به اینجا چه اتفاقی افتاد. ما به کمک createStore یک store ایجاد کردیم (این store در واقع مخزن state ماست). سپس به کمک Provider و همین store ی که ایجاد کرده بودیم به کل اپلیکیشن اجازه دادیم به store دسترسی داشته باشد. برای ایجاد تغییر در state ها سراغ reducer رفتیم. reducer تابعی است که state به علاوه action ی که قرار است برای روی آن اتفاق بیفتد را گرفته و خروجی آن، state جدید را مشخص می کند. اجازه دهید action را بیشتر بررسی کنیم.

بررسی بیشتر Action و استفاده از Action Creator 

همچنان که گفته شد به action ها به دید یک دستورالعمل نگاه کنید. دستورالعملی که به reducer می گوید state جدید به چه شکلی باشد. در واقع action یک object است نه یک تابع! برخلاف اسم آن که غلط انداز است و این تصور را در ما به وجود می آورد که یک تابع است فقط یک object است با دو خصیصه: type که ماهیت تغییر را مشخص می کند و payload که داده ی لازم برای این تغییر را فراهم میکند.

معمولا برای کار با action ها در کامپوننت از action creator استفاده می شود. action creator یک تابع است که action را به عنوان خروجی بر می گرداند.

export const addName = (name) => {
   return { type: 'ADD_NAME', payload: name }
}

در مثال بالا تابع addName را تعریف کرده ایم که خروجی آن action است. از این به بعد در کامپوننت به جای انتقال مستقیم action به reducer از actionCreator استفاده می کنیم. بنابراین فرض کنید مانند فرم زیر یک فیلد ورودی دارید که در آن یک نام وارد می کنید و با زدن دکمه مقابل آن می خواهید نام جدید به لیست نام های موجود در state شما اضافه شود: 


کاری که باید کنید این است که در onClick مربوط به button بالا تابع addName را صدا بزنید و پارامتر name را به آن پاس دهید!

 با پیچیده شدن اپلیکیشن شما خیلی چیزها پیچیده تر می شود اما واقعا ریداکس همین مفهومی هست که تا اینجا با آن همراه شده اید. اما فقط یک سوال باقیمانده و آن اینکه:

کامپوننت چطور به state دسترسی دارد و تغییرات state چطور در آن منعکس می شود؟

اتصال state موجود در  store به کامپوننت از طریق connect اتفاق می افتد. در واقع تا اینجا امکان دسترسی به store را از طریق Provider فراهم کرده ایم اما اینکه کدام کامپوننت به store وصل شود و کدام وصل نشود از طریق متد connect اتفاق می افتد. این متد در هنگام اکسپورت کردن کامپوننت فراخوانی می شود.

import { connect } from 'react-redux';

حال فرض کنید کامپوننتی با نام List دارید و می خواهید آن را به store و state آن وصل کنید:

export default connect(null, {})(List);

connect یک کامپوننت HOC است. یعنی باید دو بار فراخوانی شود. به همین دلیل در فراخوانی اول دو پارامتر و در فراخوانی دوم یک پارامتر که همان کامپوننت ما است را می گیرد. نگران syntax این تابع نباشید. همین قدر لازم است بدانید که connect باعث می شود کامپوننت شما به store وصل شود.

بسیار خوب خط اتصال بین کامپوننت List و store فراهم شد. حالا باید ببینیم که پارامترهای null و { } که در مثال قبل قرار داده شدند چه بخشی از این ماجرا هستند. در ابتدا گفتیم که به کمک ریداکس مدیریت state در یکجا یعنی store انجام می شود. همچنین گفتیم که به جای اینکه state را در قالب prop از کامپوننت های پدر به فرزند پاس دهیم میخواهیم از یک پنجره واحد state ها را در قالب prop به کامپوننت ها ارسال کنیم. پارامتر اول که در بالا null داده شده است وظیفه اش همین است: «نگاشت بین prop در این کامپوننت با آن بخشی از state موجود در redux store که در این کامپوننت به آن نیاز است» به تکه کد زیر دقت کنید:

const mapStateToProps = state => {
   return {
      userNames: state.names
   }
}

تابع بالا یک state به عنوان ورودی می گیرد. این همان کل آبجکت state موجود در store است. در نمونه بالا state ما فقط شامل آرایه ای از اسم ها یعنی names است. در شرایط واقعی ممکن است state ما شامل چندین شی و آرایه تو در تو باشد. بنابراین در کامپوننتی قرار است به state دسترسی داشته باشد فقط بخشی از آن را که در همین کامپوننت مورد نیاز است مشخص می کنیم. تابع بالا یک آبجکت برمی گرداند که شاما آرایه اسامی است. دقت کنید که اسم userNames در بالا می تواند هر چیزی باشد و بهتر است که آن را همان names بگذاریم چون با state منطبق تر و زیباتر است:

const mapStateToProps = state => {
   return {
      names: state.names
   }
}

و تابع بالا را به عنوان ورودی به متد connect پاس می دهیم:

export default connect(mapStateToProps, {})(List)

 حالا کامپوننت ما که قبلا state را در قالب prop از کامپوننت والد خود دریافت می کرد، مستقیما state را در قالب prop از store دریافت می کند. 

 در مثال زیر می بینید که پس از map کردن state به prop میتوانم در قالب prop به state دسترسی داشته باشیم:

const List = props => {
    return (
        <div>
            {
                props.names.map(name => {
                    return <div>{name}</div>
                })
            }
        </div>
    )
}

تا اینجا تقریبا تمام مسائل حل شدند. فقط باید به این پرسش پاسخ دهیم که action ها کجا صدا زده می شوند؟

قبلا اشاره کوچکی به صدا کردن actionCreator در onClick مربوط به دکمه بالا کردیم. یک فولدر با نام action ایجاد کرده و در آن معمولا یک فایل با نام index.js می سازیم. این فایل حاوی actionCreator های ما خواهد بود. برای استفاده از آن actionCreator مورد نظر را در کامپونتت ایمپورت می کنیم:

import { addName } from "../actions"

حالا addName را به عنوان پارامتر دوم به متد connect پاس می دهیم:

export default connect(mapStateToProps, {addName})(List);

به این ترتیب دقیقا مشابه state که به صورت prop در کامپوننت قابل استفاده بود، توابع actionCreator هم در props موجود بوده و می توان از آن استفاده کرد:

const List = props => {
    return (
        <div>
            <button onClick={() => props.addName('Mohsen')}>Add Mohsen!</button>
            {
                props.names.map(name => {
                    return <div>{name}</div>
                })
            }
        </div>
    )
}
 

با کلیک بر روی دکمه بالا actionCreator ی با عنوان addName صدا زده می شود، پارامتر ورودی این تابع به عنوان payload در action قرار گرفته شده و به reducer پاس داده می شود. reducer با اضافه کردن اسم جدید به لیست اسم های موجود در state قبلی یک لیست جدید از اسامی را در قالب state جدید بر میگرداند. کامپوننت List به store از طریق connect وصل شده است. بنابراین تغییرات state به طور خودکار در قالب prop به کامپوننت List اطلاع رسانی می شود و لیست جدید اسامی دوباره render می شود!

 

جریان-داده-ریداکس

تمام! شما ریداکس را یاد گرفته اید. فارغ از تنوع شکلی که در تعریف و استفاده از بخش های مختلف مثل connect ، action و ... وجود دارد تمام ماجرا همین چیزی بود که با هم مرور کردیم.

شما هم سعی کنید با تجسم و تکرار اتفاقاتی که با آمدن ریداکس می افتد و هم چنین ترسیم جریان داده و state بین کامپوننت و store تسلط خود را در ریداکس بالا ببرید.

موفق باشید.