حل مشکل زنجیره‌های prop با استفاده از context API

React Context API

23 بهمن 1399
حل مشکل زنجیره های prop با استفاده از context API

آشنایی عملی با Context API در ری اکت

در پوشه src پروژه یک پوشه دیگر به نام context ایجاد کنید (البته هر نام دیگری نیز می توانید برایش انتخاب کنید) سپس یک فایل به نام auth-context.js نیز داخل آن بسازید. می خواهیم در این فایل یک شیء context بسازیم و برای این کار نیاز به React داریم. بنابراین:

import React from 'react';

سپس ثابت خود را نیز ایجاد کرده و آن را export می کنیم:

const authContext = React.createContext();

export default authContext;

این دستور react شیء context را برای ما می سازد. این شیء به زبان ساده تر یک شیء سراسری (global) است که از همه جا قابل دسترس می باشد (البته به شرطی که شما آن را global کرده باشید چرا که شما تعیین می کنید در کدام قسمت ها در دسترس باشد) و ما می توانیم با استفاده از آن و بدون نیاز به props مقادیر مختلف را به کامپوننت های مختلف پاس بدهیم.

من مقدار اولیه آن را بدین شکل تعیین می کنم:

const authContext = React.createContext({
    authenticated: false,
    login: () => { }
});

البته دلیل اینکه تمام موارد مورد استفاده خود را در همین ابتدا به createContext می دهیم، دریافت auto-completion بهتر از ویرایشگر کدها است. بنابراین شما می توانید مقدار اولیه را اینجا به createContext ندهید.

حالا به فایل App.js برویم تا از آن استفاده کنیم. برای استفاده ابتدا باید آن را وارد App.js کنیم:

import AuthContext from '../context/auth-context';

حالا AuthContext ما باید تمام قسمت هایی که نیاز به آن دارند را درون خود قرار دهد. در پروژه ما کامپوننت های Cockpit و Persons به AuthContext نیاز دارند بنابراین باید درون آن باشند، بنابراین:

  <AuthContext.Provider>
          {this.state.showCockpit ? (
        <Cockpit
          title={this.props.appTitle}
          showPersons={this.state.showPersons}
          personsLength={this.state.persons.length}
          clicked={this.togglePersonsHandler}
          login={this.loginHandler}
        />
        ) : null}
    {persons}
  </AuthContext.Provider>

در اینجا باید از provider استفاده کنیم و نمی توانیم آن را به صورت یک عنصر عادی JSX وارد پروژه کنیم.

کامپوننت provider یک prop پیش فرض به نام value می گیرد. دلیل اهمیت نداشتن مقدار دهی اولیه در createContext هم همین بود چرا که می توانیم به جای آن قسمت، در این قسمت کارهایمان را انجام دهیم. اگر هیچ مقداری تعیین نکنید آن مقدار دهی اولیه روی عنصر اعمال می شود اما ما نمی خواهیم چنین کاری کنیم. چرا؟ به دلیل اینکه state برای پروژه ما تغییر می کند و ما می خواهیم به صورت پویا (dynamic) آن را مدیریت کنیم. بنابراین باید state را به صورت یک شیء جاوا اسکریپتی به آن بدهیم:

<AuthContext.Provider value={{}}>

توجه داشته باشید که مقدار value دو عدد {} است؛ یعنی یک شیء جاوا اسکریپتی است و نباید اطلاعات را بدون این علامت به Value بدهید. حالا کد را تکمیل می کنم:

<AuthContext.Provider value={{authenticated: this.state.authenticated}}>

react زمانی عنصر را re-render می کند که state یا prop تغییر کند اما تغییر دادن چیزی درون یک شیء context باعث re-render شدن نمی شود بنابراین چیزی هم به کاربر نمایش داده نمی شود. برای حل این مشکل آن را به Value داده ایم که به نوعی prop محسوب می شود تا re-render اجرا شود.

حالا login را هم باید اضافه کنیم تا به متد loginHandler ما اشاره کند:

<AuthContext.Provider value={{
            authenticated: this.state.authenticated,
            login: this.loginHandler
          }}>

حالا بگذارید نحوه استفاده از آن را به شما نشان دهم. وارد فایل Persons.js شده و AuthContext را import کنید:

import AuthContext from '../../context/auth-context';

اکنون به قسمت JSX خود رفته و این کار را انجام می دهید:

    render() {
        console.log('[Persons.js] rendering...');

        return <AuthContext.Consumer>

            {this.props.persons.map((person, index) => {
                return (
                    <Person
                        click={() => this.props.clicked(index)}
                        name={person.name}
                        age={person.age}
                        key={person.id}
                        changed={(event) => this.props.changed(event, person.id)}
                        isAuth={this.props.isAuthenticated}
                    />
                );
            })}
        </AuthContext.Consumer>
    }

توضیح:

  • کدهای JSX خود را درون AuthContext قرار داده ایم.
  • از consumer استفاده کرده ایم.
  • provider یعنی فراهم کننده یا تهیه کننده. consumer یعنی مصرف کننده. بنابراین این دو بر خلاف هم هستند. کامپوننت js موارد لازم برای AuthContext را provide کرد، یعنی فراهم کرد تا کامپوننت Persons.js آن را consume یا مصرف کند.
  • کدهای جاوا اسکریپت را درون {} قرار داده ایم تا به خطا برخورد نکنیم.

اما هنوز مشکلی وجود دارد؛ AuthContext.Consumer کدهای JSX را به عنوان فرزند قبول نمی کند و همیشه یک تابع می گیرد بنابراین باید به آن یک تابع بدهیم:

        return <AuthContext.Consumer>

            {()=> this.props.persons.map((person, index) => {
                return (
                    <Person
                        click={() => this.props.clicked(index)}
                        name={person.name}
                        age={person.age}
                        key={person.id}
                        changed={(event) => this.props.changed(event, person.id)}
                        isAuth={this.props.isAuthenticated}
                    />
                );
            })}
        </AuthContext.Consumer>

قبل از this.props.persons.map علامت تابع را قرار داده ام (<=()) که کدهای JSX ما را return می کند. حالا باید به عنوان آرگومان، شیء context را به این تابع پاس بدهیم:

{(context)=> this.props.persons.map((person, index) => {

حالا درون کدهای JSX به شیء context و تمام اطلاعات آن دسترسی خواهیم داشت!

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

    render() {
        console.log('[Persons.js] rendering...');

        return this.props.persons.map((person, index) => {
            return (
                <Person
                    click={() => this.props.clicked(index)}
                    name={person.name}
                    age={person.age}
                    key={person.id}
                    changed={(event) => this.props.changed(event, person.id)}
                />
            );
        });
    }

به isAuth نیازی نداریم چرا که قرار است دیگر برای انتقال اطلاعات prop های مختلف را بین کامپوننت ها پاس ندهیم (مشکل prop chain - ر.ک به جلسه قبل). دستور import را نیز برای AuthContext حذف کنید.

به فایل Person.js رفته و AuthContext را وارد آن کنید:

import AuthContext from '../../../context/auth-context';

در فایل Person فقط یک خط وجود دارد که به اطلاعات context نیاز دارد. بنابراین فقط همان خط را درون AuthContext قرار می دهیم:

        return (
            <Aux>
                <AuthContext.Consumer>
                    {(context) => this.props.isAuth ? <p>Authenticated!</p> : <p>Please log in</p>}
                </AuthContext.Consumer>
                <p key="i1" onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
                <p key="i2">{this.props.children}</p>
                <input
                    key="i3"
                    // ref={(inputEl) => { this.inputElement = inputEl }}
                    ref={this.inputElementRef}
                    type="text"
                    onChange={this.props.changed} value={this.props.name}
                />
            </Aux>
        );

حالا باید به جای مقدار قبلی (this.props.isAuth) از context استفاده کنیم:

  <AuthContext.Consumer>
       {(context) => context.authenticated ? <p>Authenticated!</p> : <p>Please log in</p>}
  </AuthContext.Consumer>

اگر به مرورگر بروید متوجه می شوید که نوشته please log in به صورت صحیح کار کرده و مشکلی از این بابت نداریم اما هنوز دکمه Log in را تکمیل نکرده ایم. بنابراین به فایل Cockpit.js رفته و AuthContext را import کنید:

import AuthContext from '../../context/auth-context';

حالا در قسمت JSX دکمه Log in را درون آن قرار می دهیم:

   <AuthContext.Consumer>
        {(context) => <button onClick={props.login}>Log in</button>}
   </AuthContext.Consumer>

همانطور که می دانید در حالت خلاصه شده می توانید پرانتز های context را نیز بردارید. حالا باید به جای props.login از context استفاده کنیم:

   <AuthContext.Consumer>
        {(context) => <button onClick={context.login}>Log in</button>}
   </AuthContext.Consumer>

حالا دیگر نیازی به ارسال prop مربوط به login در App.js نداریم:

            <Cockpit
              title={this.props.appTitle}
              showPersons={this.state.showPersons}
              personsLength={this.state.persons.length}
              clicked={this.togglePersonsHandler}
              login={this.loginHandler}
            />

بنابراین login را حذف می کنیم:

            <Cockpit
              title={this.props.appTitle}
              showPersons={this.state.showPersons}
              personsLength={this.state.persons.length}
              clicked={this.togglePersonsHandler}
            />

فایل ها را ذخیره کرده و به مرورگر بروید. به سادگی مشاهده می کنید که دکمه login نیز کار می کند. امیدوارم از این قسمت لذت برده باشید، در قسمت بعد دو روش دیگر برای استفاده از Context API را به شما معرفی می کنم که بهتر از روش فعلی هستند.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.