کتابخانه react-redux با این هدف آمده است که کار با redux را در پروژه react آسان کند.
برای شروع ابتدا میتوانید پستهای قبلی من در مورد ریداکس را مطالعه کنید :
ریداکس یک state manager سراسری برای کامپوننتهای react فراهم میکند. کامپوننتها با store ریداکس که محل عرضه stateهاست، سر و کار دارند. store را subscribe میکنند و با آپدیت state آن مقدار جدید را از store دریافت میکنند و state خود را آپدیت میکنند. تا اینجا علاوه بر state ریداکس، خود کامپوننتها نیز باید دارای یک state محلی باشند. تا به واسطه آپدیت آن state ، آن کامپوننت rerender شود.
کتابخانه react-redux به عنوان یک واسط جدید بین کامپوننتها و ریداکس عمل میکند. به قولی نمایندگی جدیدتر و برنامهنویس پسندتر برای عرضه state ریداکس به کامپوننتهاست. بطوری که برای آپدیت یک کامپوننت نیازی به تعریف state محلی درون کامپوننت نباشد. به هرحال بهتر از استفاده از subscribe هست.
به سراغ پروژه ریکتی خود میرویم و redux و react-redux را نصب میکنیم :
npm i redux react-redux
برای استفاده در reducer اکشنهای خود را میسازیم.
const COUNTER_UP = 'COUNTER_UP'; const COUNTER_DOWN = 'COUNTER_DOWN'; function counterUp(value){ return { type: COUNTER_UP, payload:value, } } function counterUp(value){ return { type: COUNTER_DOWN, payload:value, } }
اکشن COUNTER_UP مقدار value را به reducer میفرستد و به اندازه value به state شمارنده اضافه خواهد شد. اکشن COUNTER_DOWN مقدار value را به reducer میفرستد و به اندازه value از state شمارنده کم خواهد شد.
به سراغ ایجاد reducer میرویم :
function reducer(state={initialState} , action){ switch(action.type){ case COUNTER_UP: const currentCounter = state.counter; return {...state,counter:currentCounter + action.payload} case COUNTER_UP: const currentCounter = state.counter; return {...state,counter:currentCounter - action.payload} default: return state; } }
اکنون store خود را ایجاد میکنیم :
import {lagacy_createStore as createStore} from 'redux'; const initialState = { counter:0, } const store = createStore( reducer, initialState, ); export default store;
خب اکنون store ما آماده است و میتوانیم در پروژه خود از آن استفاده کنیم.
برای شروع استفاده از react-redux، کامپوننت Provider را از react-redux به پروژه خود import میکنیم:
import { Provider } from 'react-redux';
کامپوننتهایی که قصد داریم درون آنها از store استفاده کنیم را با کامپوننت Provider باید بپوشانیم. همچین store را هم باید بعنوان props برای کامپوننت Provider مشخص کنیم. اکنون در تمام کامپوننتهای فرزند Provider میتوانیم از store ریداکس استفاده کنیم. البته منظور از تمام فرزندان شامل فرزندان فرزندان فرزندان ... هم میشود.
برای اینکه از state ها و dispatch درون کامپوننت خودمان بتوانیم استفاده کنیم باید redux را به react component متصل کنیم یا اصطلاحا connect کنیم. چیز عجیب و وحشتناکی نیست. بسیار ساده این کار انجام میشود.
اول connect را از react-redux ایمپورت میکنیم :
import {connect} from 'react-redux';
آماده کردن dispatch برای کامپوننت: به کد زیر دقت کنید.
const mapDispatchToProps = (dispatch) => { return { doCounterUp:(value)=>{dispatch(counterUp(value)); } , doCounterDown:(value)=>{dispatch(counterDown(value))} , } }
این بخش دو تابع را برای ارسال به کامپوننت به عنوان props آماده میکند. تابع doCounterUp وقتی درون کامپوننت ما صدا زده شود، اکشن counterUp را به reducer میفرستد یا dispatch میکند. تابع doCounterDown هر وقت درون کامپوننت ما صدا زده شود، اکشن counterDown را برای reducer میفرستد.
چیز پیچیده ای نیست یک تابع که در ورودی dispatch را میگیرد و از آن میتوانیم در توابعی که میخواهیم برای کامپوننت بعنوان props ارسال کنیم، استفاده کنیم.
آماده کردن state برای کامپوننت : به کد زیر دقت کنید.
const mapStateToProps = (state) => { return { counter: state.counter }; };
برخی ویژگی هایی که برای استفاده در کامپوننت لازم داریم را با استفاده از mapStateToProps آماده ارسال به کامپوننت بعنوان props میکنیم. کل state را از ورودی میگیرد و آن بخشهایی از state که در کامپوننت لازم داریم، تا نمایش بدهیم را بعنوان props مشخص میکند.
اکنون باید store خود را به کامپوننت B کانکت کنیم :
export default connect( mapStateToProps, mapDispatchToProps )(CompnentB);
اکنون با استفاده از connect ، استور را به ComponentB متصل کردیم. همچنین از طریق mapStateToProps مقدار شمارنده درون redux state را بعنوان counter به این کامپوننت فرستادیم.
دو تابع هم بعنوان dispatch کننده به این کامپوننت بعنوان props ارسال کردیم. اکنون برای استفاده میتوانیم بعنوان props از counter و doCounterUp و doCounterDown استفاده کنیم. برای نمایش شمارنده درون تابع render :
render() { return (<> <h3>Counter in Component B : {this.props.counter}</h3> <button ={()=>{}}>Add</button> <button ={()=>{}}>Minus</button> </>); }
برای تغییر شمارنده redux state هم باید از توابع موجود در props این کامپوننت استفاده کنیم.
render() { return (<> <h3>Counter in Component B : {this.props.counter}</h3> <button ={()=>{this.props.doCounterUp(1)}}>Add</button> <button ={()=>{this.props.doCounterDown(1)}}>Minus</button> </>); }
به همین راحتی با mapStateToProps ، حالت یا state های مورد نیاز رو بعنوان props به کامپوننتها میفرستیم و dispatch ها را با mapDispatchToProp.
دقت کنید خروجی connect را بجای کامپوننت اصلی export کنیم:
export default connect( mapStateToProps, mapDispatchToProps )(CompnentB);
خبر خوب اینجاست که استفاده از react-redux درون functional component هنوز هم سادهتر هست. میتوانیم از هوکهای react-redux به سادگی استفاده کنیم.
برای نمایش مقدار state موجود در store فقط کافیست مانند نمونه از هوک useSelector استفاده کرد :
import { useSelector } from "react-redux" export default function CompnentC(){ const counter = useSelector((state)=>state.counter); return (<h3>Counter State in C : {counter}</h3>); }
هوک useSelector یک تابع در ورودی میگیرد که مقدار شمارنده را از state میگیرد و به ما پاس میدهد. با استفاده از این هوک هرگاه counter آپدیت شد، مقدار شمارنده درون کامپوننت C هم تغییر میکند.
هوک useDispatch هم برای dispatch یا ازسال اکشن به reducer کاربرد دارد. برای نمونه
import { useSelector , useDispatch } from 'react-redux'; import { counterUp , counterDown } from '../state/actions'; ComponentB() { const counter = useSelector((state)=>state.counter); const dispatch = useDispatch(); return (<> <h3>Counter in Component B : {counter}</h3> <button ={ ()=>dispatch(counterUp(1)) }>Add</button> <button ={()=>dispatch(counterDown(1)) }>Minus</button> </>); }
به همین راحتی درون functional component میتوان اکشن را dispatch کرد.
توصیه react-rdux به استفاده از هوک هاست. اما روش connect هم به خوبی کار میکند.
کامپوننت A :
import { useEffect, useState } from "react" import store from "../store" export default function CompnentA(){ useEffect(()=>{ store.subscribe(()=>{ const state = store.getState(); setCount(state.counter) }); },[]); const [count,setCount] = useState(0); return (<h3>Counter State in A : {count}</h3>); }
در کامپوننت A موقعی که اولین بار کامپوننت ایجاد شود ما باید store را subscribe کنیم. پس از آن، هربار که state موجود در store ریداکس تغییر کرد، setCounter درون ComponentA صدا زده میشود و پس از آن این کامپوننت rerender میشود. یعنی در حقیقت rerender شدن مجدد این کامپوننت وابسته به state درون خودش است نه state ریداکس!
کامپوننت C:
import { useSelector } from "react-redux" import {State} from '../store'; export default function CompnentC(){ const counter = useSelector((state:State)=>state.counter); return (<h3>Counter State in C : {counter}</h3>); }
درون کامپوننت C دیگر ما هیچ state یا حالتی تعریف نکردیم. به واسطه هوک useSelector با هر تغییر state موجود در store ، کامپوننت c هم آپدیت میشود.