تغییر state به شکل immutable

Changing State

23 بهمن 1399
تغییر state به شکل immutable

در این جلسه می خواهیم کدهای جلسه قبل را تکمیل کنید. برای شروع باید کدهای اضافی را حذف کنیم؛ متد switchNameHandler باید حذف شود چراکه دیگر در هیچ جایی از برنامه ما صدا زده نمی شود. به جای آن یک متد دیگر به نام deletePersonHandler را تعریف می کنیم که کارش حذف یکی از افراد است:

  deletePersonHandler = () => {
    
  }

اگر به کامپوننت Person.js نگاهی بیندازید قسمتی از کدها را می بینید که به این شکل است:

<p onClick={props.click}>I'm {props.name} and I am {props.age} years old!</p>

همانطور که می بینید پاراگراف اول دارای یک property به نام click است که هنگام کلیک شدن اجرا می شود، بنابراین می توانیم از آن استفاده کنیم و متد جدید خودمان را به آن متصل کنیم:

{this.state.persons.map(person => {
            return <Person
              click={this.deletePersonHandler}
              name={person.name}
              age={person.age} />
          })}

ما می خواهیم این اتفاق (حذف شدن) برای فردی بیفتد که روی آن کلیک می شود اما از آنجایی که در حال نمایش یک لیست هستیم معلوم نیست چه کسی حذف خواهد شد! خوشبختانه تابع map یک آرگومان دیگر نیز می گیرد (به نام index) که index عضو آرایه را مشخص می کند و به صورت خودکار پاس داده می شود.

اگر یادتان باشد در جلسات قبل دو نکته را توضیح داده بودیم:

  • برای پاس دادن آرگومان به متدی که به عنوان prop پاس داده شده است دو راه وجود دارد:
    • استفاده از bind با دو آرگومان this و آرگومان مورد نظر خودمان
    • استفاده از arrow function
  • اگر در arrow function ها بیشتر از یک آرگومان داشته باشیم باید آرگومان ها را درون پرانتز قرار دهیم.

ما در این قسمت به جای bind از arrow function استفاده می کنیم و می گوییم:

    {this.state.persons.map((person, index) => {
            return <Person
              click={ () => this.deletePersonHandler(index)}
              name={person.name}
              age={person.age} />
    })}

حالا می توانیم آن را در deletePersonHandler دریافت کنیم بنابراین:

  deletePersonHandler = (personIndex) => {
    const persons = this.state.persons;
    persons.splice(personIndex, 1);
    this.setState({persons: persons});
  }

توضیح کد:

ابتدا index آرایه را دریافت کرده ایم. شما می توانید هر نامی برایش انتخاب کنید، ما personIndex را انتخاب کرده ایم. این مقدار همان مقداری است که در تابع map به متد deletePersonIndex پاس داده شده است (آرگومان index).

سپس تمام افراد را (آرایه persons) دریافت کرده ایم و آن ها را درون یک ثابت به نام persons قرار داده ایم. در مرحله بعد با استفاده از تابع splice یک عنصر را حذف کرده ایم. این تابع دو پارامتر می گیرد؛ یکی index آرایه مورد نظر شما و دیگری تعداد اعضایی که قرار است حذف شوند. به طور مثال:

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2, 1);

در این قسمت splice ایندکس شماره 2 را گرفته است یعنی عضو سوم آرایه (Apple). پارامتر بعدی عدد 1 است یعنی 1 عضو را از این آرایه حذف کن که می شود همان Apple. اگر برای پارامتر دوم عدد 2 بگذاریم Apple و Mongo با هم حذف می شوند.

در آخر هم با استفاده از دستور this.setState آرایه persons (درون state) را برابر با ثابت persons (که دو خط بالاتر تعریف کردیم) قرار داده ایم:

    this.setState({persons: persons});

از آنجا که ثابت persons آپدیت شده است (تابع splice یکی از اعضایش را حذف کرده است) React نیز متوجه می شود که ثابت persons با آرایه persons یکی نیست بنابراین DOM باید دوباره render شود.

ممکن است برایتان سوال پیش بیاید که چطور یک ثابت (const) را ویرایش کرده و تغییر داده ایم؟ توجه داشته باشید که آرایه ها و اشیاء در جاوا اسکریپت از نوع ارجاعی یا reference type هستند (ر.ک به مروری بر ES6) بنابراین واقعا آرایه یا شیء جدیدی نمی سازند و ما اصلا مقدار جدیدی به این ثابت نداده ایم بلکه یک pointer را درون خود دارد که به شیء یا آرایه اصلی اشاره می کند. بنابراین ما ثابت را تغییر ندادیم بلکه شیء ای را تغییر دادیم که ثابت به آن اشاره می کرد. در این مورد قبلا صحبت کرده ایم.

اگر به مرورگر مراجعه کنید متوجه می شوید که هیچ مشکلی وجود ندارد و کدها کار می کنند (البته هنوز خطاهای قسمت console را تصحیح نکرده ایم) اما استفاده از این روش یک نقص اساسی دارد. نقص مهم کد ما این است که آرایه ها و اشیاء در جاوا اسکریپت reference type هستند بنابراین زمانی که آرایه persons را درون ثابت persons میریزیم، فقط یک pointer ایجاد می کنیم که به آرایه اصلی (درون state) اشاره می کند. بنابراین زمانی که آن را splice می کنیم، داده های اصلی را در State تغییر داده و آن ها را Splice کرده ایم!

استفاده از چنین روشی باعث می شود برنامه شما رفتارهای غیرعادی از خود نشان دهد و اصلا توصیه نمی شود.

برای حل این مشکل می توانیم از تابع Slice استفاده کنیم:

deletePersonHandler = (personIndex) => {
    const persons = this.state.persons.slice();
    persons.splice(personIndex, 1);
    this.setState({ persons: persons });
  }

استفاده از تابع slice بدون پارامتر مشکل را حل می کند چرا که slice از آرایه/شیء اصلی یک کپی گرفته و آن را درون ثابت persons قرار می دهد. به این صورت دیگر داده های اصلی در state ویرایش نمی شوند و ثابت persons نیز یک ثابت مستقل و جدید است و به داده دیگری اشاره ندارد.

اما همانطور که در ابتدای این دوره گفتیم، ما می خواهیم از ویژگی های ES6 استفاده کنیم بنابراین از spread operator (علامت سه نقطه) استفاده می کنیم که در قسمت های «مروری بر ES6» در موردشان صحبت کرده بودیم:

const persons = [...this.state.persons];

این اپراتور اعضای آرایه persons در state را گرفته و آن ها را به صورت یک لیست در یک آرایه جدید قرار می دهد. بنابراین اشیاء درون persons (در state) را می گیرد اما خود آرایه را دور می اندازد. در واقع کد ما به این صورت بوده است:

const persons =[];

یعنی ابتدا یک آرایه خالی ساخته بودیم، سپس اپراتور spread اعضای آرایه دیگری (persons) را گرفته و آن ها را درون این آرایه جدید قرار می دهد. به این روش، تغییر state به صورت immutable می گویند؛ یعنی داده های اصلی دست نمی خورد. شما می توانید از هر کدام از این روش ها استفاده کنید. در جلسات بعدی به تکمیل کردن این کد می پردازیم.

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

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