خیلی از برنامهنویسهای ریاکت، با ریداکس به عنوان یک state manager کار کردند یا حداقل احساس نیاز به چنین چیزی رو داشتند. قبل از این، ریاکت به صورت پیشفرض قابلیت مدیریت stateها رو نداشت و همین باعث شد که دولوپرهای ریاکت به ریداکس رو بیارن. اما از وقتی که React Context API خودشو نشون داد، توجه خیلیها رو تونست جلب کنه.
توی این نوشته قراره بفهمیم که اصلا چه موقع به همچین چیزی نیاز پیدا میکنیم و در نهایت متوجه بشیم که چطور میتونیم ازش استفاده کنیم. پس سیستمهاتون رو روشن کنید :)
میدونیم که در ریاکت برای فرستادن stateهای کامپوننت پدر به فرزندهاش، نمیتونیم ساختار سلسله مراتبی (hierarchy) رو نقض کنیم و باید از طریق propها تکتک کامپوننتهای واسط رو رد کنیم تا به کامپوننت مقصد برسیم. بنابراین وقتی تعداد کامپوننتهای تو در تو زیاد باشن، فرستادن stateهای بالاترین کامپوننت به پایینترین کامپوننت، کار پیچیده و تقریبا غیرمعقولیه. (دیاگرام سمت چپ عکس زیر)
اما برای راحتی کار، بهتره که stateهایی به صورت سراسری (Global) داشته باشیم تا داخل هر کامپوننت، فارق از اینکه اون کامپوننت کجا قرار داره بتونیم ازش استفاده کنیم. (دیاگرام سمت راست عکس زیر)
این دقیقاً همون کاریه که استیت منیجرهایی مثل React Context برامون انجام میدن.
همونطور که گفتیم، برای ساختن stateهای سراسری از React Context استفاده میکنیم. پس اولین کاری که قراره انجام بدیم اینه که داخل فایل App.js یک Context جدید میسازیم و اون رو نامگذاری میکنیم:
export const TestContext = React.createContext( );
نکتهای که وجود داره اینه که برای ساخت Context جدید نیازی به نصب هیچ ماژولی نداریم و همونطور که میبینیم، از خود پکیج React برای ساختن Context استفاده میکنیم.
یادتون نره که این متغیر رو از داخل App.js اکسپورت کنید که بتونید توی کامپوننتهای دیگه مجدد import کنید.
بعد از ساختن Context، نوبت به ساختن قسمتی برای ذخیره کردن دادهها میرسه. پس به یک کامپوننتی احتیاج داریم که stateهای سراسری رو داخل اون قرار بدیم که اصطلاحاً Provider Component (کامپوننت ارائهدهنده) نامیده میشه. پس Provider Component رو به شکل زیر ایجاد میکنیم:
import React, {Component} from 'react'; //Don't forget to import your Context from App.js import {TestContext} from "PATH_TO_APP.JS";
export default class DataProvider extends Component{ this.state = { data: "This is a sample data." } render( ){ return( <TestContext.Provider value={{this.state.data}}> {this.props.children} </TestContext.Provider> ); } }
خب، شروع کنیم به بررسی این تکه کد:
حتما یادتون نره که Context ساخته شده رو داخل این کامپوننت import کنید تا بتونید ازش استفاده کنید.
در این جا یک Provider Component ساختیم به اسم DataProvider که داخلش یک state هم وجود داره و در متد render، به جای اینکه تگهای Html برگردونیم، ارائهدهندهٔ Context رو برمیگردونیم. در واقع از Context ساخته شده در App.js استفاده میکنیم و Provider اش رو برمیگردونیم:
<TestContext.Provider value={{this.state}}> {this.props.children} </TestContext.Provider>
اما این قسمت از کد که return شده چه معنایی میده؟
اگر از ریداکس استفاده کرده باشید، همچین چیزی براتون خیلی آشنا هست. ما قراره از این Provider به عنوان یک wrapper برای کل کامپوننتهای پروژه استفاده کنیم تا همه کامپوننتهای درون این Provider بتونن از stateهای سراسری استفاده کنند.
سوال دوم اینه که این stateهای سراسری چطور به کامپوننتهای درونی انتقال داده شدند؟ جواب واضحه. پارامتر value که به عنوان prop در اینجا نوشته شده، stateهای سراسری رو به تمام فرزندان
(یعنی همون this.props.childern که داخل تگ هست) میفرسته.
حالا ما یک Provider Component داریم که میتونیم داخلش تمامی کامپوننتهای دیگه رو قرار بدیم. پس برمیگردیم به App.js و این کامپوننت رو import میکنیم و همه کامپوننتهای دیگه رو داخلش قرار میدیم:
import React, {Component} from 'react'; //Don't forget to import your Provider Component import DataProvider from "PATH_TO_PROVIDER_COMPONENT"; export const TestContext = React.createContext( ); export default class App extends Component { render( ){ return( <DataProvider> //All your other components </DataProvider> ); } }
همونطور که اینجا میبینید، از DataProvider به عنوان یک wrapper استفاده کردیم و همه کامپوننتهای دیگه رو داخل اون قرار دادیم تا بتونن به state سراسریای که درون DataProvider تعریف کردیم دسترسی داشته باشن.
اما فقط یک مرحله دیگه وجود داره برای اینکه به اون stateهای سراسری دسترسی داشته باشیم و اون اینه که، هر جای پروژه که نیاز داریم اون stateها رو مصرف کنیم یا به اصطلاح Consume کنیم.
حالا نوبت رسیده به استفاده از دیتاهایی که به صورت سراسری ذخیره شدند. برای اینکار باید از Consumer (مصرفکننده) استفاده کنیم. وارد یکی از کامپوننتهای مورد نظر خودمون میشیم و هرجایی که نیاز بود از Consumer استفاده میکنیم.
اما همچنان فراموش نکنید که Context ساخته شده (TestContext) رو داخل کامپوننت مورد نظرتون import کنید تا بتونید از Consumerاش استفاده کنید:
<div> <TestContext.Consumer> {(contextValue) => ( <p>Here's the global data: {contextValue}</p> )} </TestContext.Consumer> </div>
نکته مهم: توجه کنید که فرزند Consumer همواره یک تابع است.
درون Consumer یک تابع قرار داده شده که پارامتر ورودی این تابع در واقع همون مقداری هست که به عنوان value در Context Provider وجود داشت. و خروجی این قسمت طبیعتاً به این شکل هست:
Here's the global data: This is a sample data.
به همین راحتی شما تونستید از React Context Api استفاده کنید و یک دیتا رو به صورت سراسری در اختیار تمام کامپوننتها قرار بدید.
باید بگم که کاربرد React Context Api به همین موضوع ختم میشه بر خلاف ریداکس که قابلیتهای دیگهای هم در اختیارتون قرار میده از جمله افزونههای debuging که توی ریداکس زیاد هم طرفدار داره.
به هر حال ریداکس سن بیشتری نسبت به React Context Api داره و طرفدارای زیادی داره و این موضوع باعث شده یکم پختهتر به نظر بیاد.
اما اگه نیازی به ویژگیهای دیگه ریداکس ندارید و صرفا برای مدیریت stateهای سراسری میخواید ازش استفاده کنید، خب همین کار رو با React Context Api انجام بدید. چون همونطور که دیدیم نیازی به اضافه کردن هیچ کتابخونه دیگهای نداره و استفاده ازش به مراتب راحتتر از ریداکس هست.
یادتون نره در مستندات سایت ریاکت خیلی کاملتر از هر مطلب دیگه به این موضوع پرداخته شده. پس حتما اونو هم بررسی کنید.
دست به کد باشید :)