مفهوم setState در React
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 8 دقیقه

مفهوم setState در React

کامپوننت (مولفه‌ یا جز) ‌های React اغلب دارای یک state یا همان حالت هستند که این state می‌تواند هر چیزی باشد؛ این هر چیز نیز معمولا شامل مواردی مثل لاگ‌این بودن یا نبودن کاربر، فعال بودن یا غیرفعال بودن او و یا حتی آرایه‌ای از پست‌ها می‌باشد.

کامپوننت‌های React که دارای state هستند، موارد رابط کاربری (UI) را بر اساس مقدار فعلی state، رندر می‌کنند؛ در نتیجه هر گاه state یک کامپوننت نیز تغییر کند، ظاهر آن کامپوننت نیز تغییر خواهد کرد.

پس ما باید بدانیم که دقیقا در چه هنگام و به چه شکلی به تغییر مقدار state یک کامپوننت بپردازیم. در پایان این مقاله شما باید نحوه‌ی کارکرد فانکشن setState را بیاموزید و علاوه‌ بر آن، با برخی اشکالات رایج که در هنگام کار با‌ state‌های ری‌اکتی بروز می‌کند، آشنا شوید.

کارکرد setState()

این فانکشن تنها راه درست برای تغییر دادن و بروز (update) کردن مقدار state در فریم‌ورک React پس از مقدار دهی اولیه به آن است. بیایید فرض کنیم که یک کامپوننت جست‌و‌جو داریم و می‌خواهیم عبارت جست‌وجو شده توسط کاربر را به نمایش در آوریم:

import React, { Component } from 'react'

class Search extends Component {
  constructor(props) {
    super(props)

    state = {
      searchTerm: ''
    }
  }
}

ما در حال پاس دادن مقدار یک رشته (string) خالی به state searchTerm این کامپوننت هستیم. حال می‌توان با فراخوانی فانکشن setState() به تغییر مقدار اولیه‌ی آن پرداخت:

setState({ searchTerm: event.target.value })

همان‌طور که در قطعه کد بالا می‌بینید، در حال پاس دادن یک آبجکت به فانکشن ست استیت هستیم. این آبجکت شامل قسمتی از state است که قصد بروز کردن آن را داریم که در این مثال، searchTerm است. ری‌‌اکت این مقدار را گرفته و آن را با آبجکت state ادغام (merge) می‌کند. در واقع این اتفاق به مانند این است که کامپوننت search از فانکشن state انتظار دارد که مقدار searchTerm را مشخص کند.

مفهوم setState در React

در واقع فانکشن ست استیت شروع کننده‌ی پروسه‌ای به نام reconciliation (اصلاح) است. این فرآیند باعث آپدیت شدن DOM توسط React به‌وسیله‌ی ایجاد تغییراتی بر اساس مقدار جدید state در کامپوننت‌هاست. وقتی درخواست setState اعلام می‌شود، React یک درخت تازه‌ که شامل المنت‌های واکنشی در کامپوننت می‌شود را می‌سازد. این درخت برای تعیین تغییرات جدید لازم در کامپوننت search به کار خواهد آمد. (با مقایسه‌ی درخت جدید با درخت قبلی)

نتیجتا React خواهد دانست که با توجه به state جدید، چه تغییراتی در کامپوننت موجود ایجاد کند تا به کامپوننت نهایی متناسب با state جدید برسد و آن را برای کاربر رندر کند؛ پس ما احتمالا در React با قابلیت setState، به هدف ایده‌آل‌مان که ایجاد حداقل تغییرات لازم برای رسیدن به کامپوننت نهایی است، خواهیم رسید و یک وب اپ سریع و بهینه خواهیم داشت.

حال بیایید برای درک بهتر، این جریان رخ داده شده تا به این‌جا را خلاصه کنیم:

  • یک کامپوننت جست و جو داریم که عبارت جست و جو شده را به نمایش در می‌آورد.
  • عبارت جست و جویی فعلا خالی است.
  • کاربر یک عبارت را برای جست و جو کردن وارد می‌کند.
  • این عبارت توسط فانکشن setState React گرفته (کپچر) و ذخیره می‌شود.
  • فرآیند Reconciliation (اصلاح) آغاز می‌شود و ری‌‌اکت متوجه تغییر رخ داده شده در مقدار state کامپوننت خود می‌شود.
  • React به کامپوننت جست‌وجو، دستور بروزرسانی مقدار جدید را می‌دهد و نهایتا عبارت جست و جو شده در کامپوننت ادغام می‌شود.

فرآیند Reconciliation، لزوما تمام درخت را تغییر نمی‌دهد؛ مگر در شرایطی که ریشه‌ی درخت به صورت زیر تغییر کند:

// old
<div>
  <Search />
</div>

// new
<span>
  <Search />
</span>

در مثال بالا می‌بینیم که تگ div به تگ span تغییر کرده است؛ در نتیجه کل درخت کامپوننت ما آپدیت خواهد شد.

هیچ‌گاه نباید مقدار state را به طور مستقیم تغییر داد. همیشه برای ایجاد تغییر در state از setState استفاده کنید چرا که تغییر مستقیم state، باعث رندر مجدد (re-render) کامپوننت‌ها در ری اکت نمی‌شود:

// do not do this
this.state = {
  searchTerm: event.target.value
}

پاس دادن یک فانکشن به setState()

برای بیان این مورد نیز اجازه دهید یک مثال بیاوریم؛ در این مثال در حال ساخت یک شمارنده‌ی ساده هستیم که با کلیک، می‌توان مقدار آن را کم یا زیاد کرد:

class App extends React.Component {

state = { count: 0 }

handleIncrement = () => {
  this.setState({ count: this.state.count + 1 })
}

handleDecrement = () => {
  this.setState({ count: this.state.count - 1 })
}
  render() {
    return (
      <div>
        <div>
          {this.state.count}
        </div>
        <button onClick={this.handleIncrement}>Increment by 1</button>
        <button onClick={this.handleDecrement}>Decrement by 1</button>
      </div>
    )
  }
}

حال می‌توانیم با کلیک بر روی دکمه‌های ساخته شده، مقدار شمارنده را، یک واحد، کم یا زیاد کنیم.

اما اگر بخواهیم با هر بار کلیک، مقدار ۳ واحد را کم یا زیاد کنیم چه؟ برای انجام این عمل می‌توانیم با هربار کلیک، فانکشن‌ setState را سه بار فراخوانی کنیم:

handleIncrement = () => {
  this.setState({ count: this.state.count + 1 })
  this.setState({ count: this.state.count + 1 })
  this.setState({ count: this.state.count + 1 })
}

handleDecrement = () => {
  this.setState({ count: this.state.count - 1 })
  this.setState({ count: this.state.count - 1 })
  this.setState({ count: this.state.count - 1 })
}

اما به شکلی غافلگیرانه متوجه می‌شویم که کد بالا مطابق انتظار ما کار نخواهد کرد. این کد معادل قطعه کد زیر است:

Object.assign(  
  {},
  { count: this.state.count + 1 },
  { count: this.state.count + 1 },
  { count: this.state.count + 1 },
)

Object.assign() برای کپی کردن داده‌ها از یک آبجکت منبع به یک آبجکت مقصد و هدف است. اگر تمام داده‌ها با استفاده از یک کلید مشترک، قصد کپی شدن از آبجکت مبدا به آبجکت مقصد را داشته باشند، دچار ایراد خواهیم شد. (همانند اتفاقی که در کد ما افتاد)

در زیر می‌توانید یک نسخه‌ی ساده‌تر از کد را ببینید تا نحوه‌ی عملکرد object.assign() را بهتر متوجه شوید:

let count = 3

const object = Object.assign({}, 
  {count: count + 1}, 
  {count: count + 2}, 
  {count: count + 3}
);

console.log(object);
// output: Object { count: 6 }

همان‌‌طور که در کد بالا می‌بینید، تنها آبجکت آخر که count را به علاوه‌ی ۳ کرده اعمال شده و دو آبجک قبلی آن ندیده گرفته شده‌اند.

پس در مثال اولیه‌ی ما نیز به جای فراخوانی سه‌باره، تنها یک بار setState فراخوانی می‌شود. این مورد را می‌توان با پاس دادن یک فانکشن به setState() تصحیح کرد. این‌ کار را می‌توان به همان ترتیب پاس دادن آبجکت به setState انجام داد تا در نهایت از مشکل یاد شده در بالا راحت شویم.

فانکشن handleIncrement را به شکل زیر تغییر دهید:

handleIncrement = () => {
  this.setState((prevState) => ({ count: prevState.count + 1 }))
  this.setState((prevState) => ({ count: prevState.count + 1 }))
  this.setState((prevState) => ({ count: prevState.count + 1 }))
}

حال با این کد جدید می‌توانیم با هر بار کلیک، سه‌ واحد شمارنده را اضافه کنیم.

در این حالت جدید، React به جای ادغام، فراخوانی‌های فانکشن را در صف قرار داده تا به ترتیب، یکی پس از دیگری اعمال شوند و همچنین state نیز تنها یک بار (در آخر) تغییر کند.

دسترسی به state قبلی با استفاده از یک Updater

گاهی اوقات در حین ساخت اپلیکیشن ری‌اکتی ممکن است نیاز داشته باشید که به state قبلی کامپوننت خود دسترسی داشته باشید و state جدید آن‌ را بر اساس state قبلی محاسبه کنید. همیشه نمی‌توان به this.state اعتماد داشت و توقع داشت بلافاصله پس از فراخوانی setState() مقدار آن نیز تصحیح شود؛ چرا که همیشه مقدار state کنونی همواره تنها مطابق با مقداری است که باعث آخرین رندر کامپوننت شده است.

بیایید مجدد به سراغ مثال شمارنده‌ خود برویم و بررسی دقیق‌تری از کارکرد آن بکنیم. حال فرض کنیم که یک فانکشن داریم که مقدار count را یک واحد کم می‌کند. این فانکشن را در زیر مشاهده می‌کنید:

changeCount = () => {
  this.setState({ count: this.state.count - 1})
}

حال می‌خواهیم قابلیت تغییر سه واحدی شمارنده‌ی خود را داشته باشیم. فانکشن changeCount را می‌توان سه بار در فانکشنی که رویداد کلیک را مدیریت و هندل می‌کند فراخوانی کرد:

handleDecrement = () => {
  this.changeCount()
  this.changeCount()
  this.changeCount()
}

پس از انجام این تغییرات نیز باز هم با هر بار کلیک، تنها یک واحد تغییر را در شمارنده مشاهده می‌کنیم. این اتفاق به دلیل این است که مقدار this.state.count تا زمانی که کامپوننت رندر مجدد نشود، بروز نخواهد شد. راه‌حل این مشکل نیز استفاده از یک updater یا بروز کننده است. یک بروزکننده به شما اجازه می‌دهد که به state کنونی دسترسی داشته باشید و از آن بلافاصله پس از تغییر state استفاده کنید تا موارد دیگر دلخواهتان را نیز آپدیت کنید. پس فانکشن changeCount() باید به شکل زیر در آید:

changeCount = () => {
  this.setState((prevState) => {
    return { count: prevState.count - 1}
  })
}

حال ما دیگر وابسته به نتیجه‌ی مقدار this.state نیستیم و مقادیر count به دنبال یک‌دیگر در حال تغییر‌اند. از این پس قادر به دسترسی به مقدار صحیح state که توسط setState تغییر کرده است، خواهیم بود.

به خاطر داشته باشید که باید با setState به شکل async یا ناهمزمان رفتار کنید و انتظار نداشته باشید که مقدار state شما بلافاصله پس از ست کردن آن، تغییر کند.

جمع‌بندی

در هنگام کار با state‌ها در React نیاز است که به چند نکته توجه داشته باشید:

  • بروز یا آپدیت کردن state یک کامپوننت باید تنها با استفاده از setState() صورت گیرد.
  • شما می‌توانید به setState() یک آبجکت یا فانکشن پاس دهید.
  • برای تغییر چندباره‌ی state به setState، فانکشن پاس دهید.
  • به مقدار this.state بلافاصله پس از فراخوانی setState() اطمینان نکنید و حتما در این موارد از یک فانکشن بروز کننده استفاده کنید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
4 از 1 رای

/@BAbolfazl

Front-End

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

ابوالفضل باغشاهی

Front-End

مقالات برگزیده

مقالات برگزیده را از این قسمت میتوانید ببینید

مشاهده همه مقالات