بین کامپوننتها state به اشتراک گذاشتن
گاهی اوقات، شما میخواهید که state دو کامپوننت همیشه با هم تغییر کند. برای انجام این کار، state را از هر دو کامپوننت حذف کنید، آن را به نزدیکترین والد مشترک آنها منتقل کنید، و سپس آن را از طریق props به آنها منتقل کنید. این به عنوان بالا بردن state شناخته میشود، و این یکی از متداول ترین کارهایی است که شما در حین نوشتن کد ریاکت انجام خواهید داد.
You will learn
- چگونه state را با بالا بردن آن بین کامپوننتها به اشتراک بگذارید
- کامپوننت های کنترل شده و کنترل نشده چیست
بالا بردن state با مثال
در این مثال یک کامپوننت والد Accordion
دو کامپوننت جداگانه Panel
را رندر میکند:
Accordion
Panel
Panel
هر کامپوننت Panel
یک isActive
state دارد که مشخص میکند که آیا محتوای آن قابل مشاهده است یا خیر.
برای هر دو کامپوننت Panel
دکمه Show را فشار دهید:
import { useState } from 'react'; function Panel({ title, children }) { const [isActive, setIsActive] = useState(false); return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); } export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About"> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology"> The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); }
توجه کنید که فشار دادن دکمه یکی از پنلها بر روی پنل دیگر تاثیری ندارد—آنها مستقل هستند.
اما حالا فرض کنید که میخواهید فقط یک پنل در هر زمان باز شود. در این طراحی، باز کردن پنل دوم باید پنل اول را ببندد. چگونه این کار را انجام میدهید؟
برای هماهنگ کردن این دو پنل، شما باید state آنها را به یک کامپوننت والد در سه مرحله “بالا ببرید”:
- حذف state از کامپوننتهای فرزند.
- انتقال دادههای ثابت از والد مشترک.
- اضافه کردن state به والد مشترک و انتقال آن همراه با event handlers.
این به کامپوننت Accordion
اجازه میدهد که هر دو Panel
را هماهنگ کند و فقط یکی را در هر زمان باز کند.
قدم ۱: حذف state از کامپوننتهای فرزند
شما کنترل isActive
را به کامپوننت والد Panel
میدهید. این به این معنی است که کامپوننت والد isActive
را به عنوان یک prop به Panel
منتقل میکند. با حذف این خط از کامپوننت Panel
شروع کنید:
const [isActive, setIsActive] = useState(false);
و به جای آن، isActive
را به لیست prop های Panel
اضافه کنید:
function Panel({ title, children, isActive }) {
حالا کامپوننت والد Panel
میتواند isActive
را با انتقال آن به عنوان prop کنترل کند. متقابلا، کامپوننت Panel
دیگر کنترلی بر روی مقدار isActive
ندارد—حالا این کار به عهده کامپوننت والد است!
قدم ۲: انتقال دادههای ثابت از والد مشترک
برای بالا بردن state، شما باید نزدیکترین کامپوننت والد مشترک میان دو کامپوننت فرزند که میخواهید آن ها را هماهنگ کنید، پیدا کنید:
Accordion
(نزدیک ترین والد مشترک)Panel
Panel
در این مثال، این کامپوننت Accordion
است. از آنجایی که این کامپوننت بالاتر از هر دو پنل قرار دارد و میتواند prop های آنها را کنترل کند، این کامپوننت منبع اصلی برای اینکه کدام پنل در حال حاضر فعال است میباشد. کامپوننت Accordion
را طوری تغییر دهید که مقدار ثابت isActive
را (به عنوان مثال، true
) به هر دو پنل منتقل کند:
import { useState } from 'react'; export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About" isActive={true}> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology" isActive={true}> The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); }
سعی کنید مقادیر isActive
را در کامپوننت Accordion
ویرایش کنید و نتیجه را در صفحه مشاهده کنید.
قدم ۳: اضافه کردن state به والد مشترک
بالا بردن state معمولا طبیعت آنچه را که به عنوان state ذخیره میکنید تغییر میدهد.
در این مثال، فقط یک پنل در هر زمان باید فعال باشد. این به این معنی است که کامپوننت والد مشترک Accordion
باید اینکه کدام پنل فعال است را پیگیری کند. به جای یک مقدار boolean
، میتوانید از یک عدد به عنوان ایندکس پنل فعال برای متغیر state استفاده کنید:
const [activeIndex, setActiveIndex] = useState(0);
هنگامی که activeIndex
برابر 0
است، پنل اول فعال است، و هنگامی که برابر 1
است، پنل دوم فعال است.
کلیک کردن روی دکمه “Show” در هر Panel
باید ایندکس فعال در Accordion
را تغییر دهد. یک Panel
نمیتواند state activeIndex
را مستقیما تغییر دهد زیرا در داخل Accordion
تعریف شده است. کامپوننت Accordion
باید به صورت صریح به کامپوننت Panel
اجازه دهد که state آن را تغییر دهد با انتقال یک event handler به عنوان prop:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
<button>
داخل Panel
اکنون از onShow
prop به عنوان event handler کلیک استفاده میکند:
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Show </button> )} </section> ); }
این بالا بردن state را کامل میکند! انتقال state به کامپوننت والد مشترک به شما اجازه داد که دو پنل را هماهنگ کنید. استفاده از ایندکس فعال به جای دو پرچم “نمایش داده شده” اطمینان حاصل میکند که تنها یک پنل در یک زمان فعال است. و انتقال event handler به کامپوننت فرزند به آن اجازه میدهد که state والد را تغییر دهد.
Deep Dive
به طور معمول، یک کامپوننت با چند state محلی “کنترل نشده” نامیده میشود. به عنوان مثال، کامپوننت اصلی Panel
با یک متغیر state isActive
کنترل نشده است زیرا والد آن نمیتواند تاثیری بر روی فعال بودن یا نبودن پنل داشته باشد.
متقابلا ، میتوانید بگویید که یک کامپوننت “کنترل شده” است زمانی که اطلاعات مهم در آن توسط prop ها و نه state محلی آن هدایت میشوند. این به کامپوننت والد اجازه میدهد که رفتار آن را به طور کامل مشخص کند. کامپوننت نهایی Panel
با isActive
prop توسط کامپوننت Accordion
کنترل میشود.
استفاده از کامپوننت های کنترل نشده درون کامپوننت های والدشان آسان تر است زیرا نیاز به کانفیگ کمتری دارند. اما زمانی که میخواهید آنها را با هم هماهنگ کنید، کمتر از کامپوننت های کنترل شده انعطاف پذیر هستند. کامپوننت های کنترل شده حداکثر انعطاف پذیری را دارند، اما نیاز به کانفیگ کامل تری از طرف کامپوننت های والد با prop ها را دارند.
در عمل، “کنترل شده” و “کنترل نشده” اصطلاحات فنی دقیقی نیستند—هر کامپوننت معمولاً مجموعه ای از state محلی و prop ها را دارد. با این حال، این یک روش مفید برای صحبت در مورد نحوه طراحی کامپوننت ها و قابلیت هایی است که ارائه می دهند.
هنگام نوشتن یک کامپوننت، در نظر داشته باشید که اطلاعاتی که در آن باید کنترل شده باشد (از طریق prop ها) و اطلاعاتی که باید کنترل نشده باشد (از طریق state) چیست. اما همیشه میتوانید نظر خود را تغییر دهید و بعداً بازطراحی کنید.
یک منبع اصلی برای هر state
در یک برنامه ریاکت، بسیاری از کامپوننتها state مخصوص به خود را دارند. برخی از state ممکن است “زندگی” خود را نزدیک به کامپوننتهای برگ (کامپوننتهای در پایین درخت) مانند ورودیها داشته باشند. دیگر state ها ممکن است “زندگی” خود را نزدیک به بالای برنامه داشته باشد. به عنوان مثال، حتی کتابخانههای مسیریابی سمت-کلاینت معمولا با ذخیره مسیر فعلی در state ریاکت پیادهسازی میشوند و از طریق prop ها به پایین منتقل میشوند!
برای هر قطعه state منحصر به فرد، شما کامپوننتی که “مالک” آن است را انتخاب خواهید کرد. این اصل همچنین به عنوان داشتن یک “منبع اصلی برای هر state”. شناخته میشود. این به این معنی نیست که همه state در یک مکان زندگی میکنند—اما برای هر قطعه state، یک کامپوننت خاص وجود دارد که آن قطعه اطلاعات را نگه میدارد. به جای تکرار state های مشترک بین کامپوننت ها، آنها را به والد مشترک خود بالا ببرید، و آنها را به کامپوننت های فرزندی که نیاز به آنها دارند، منتقل کنید.
برنامه شما هنگام کار بر روی آن تغییر خواهد کرد. معمول است که در حین کار بر روی آن، state را به پایین یا به بالا ببرید در حالی که هنوز در حال یافتن مکان زندگی هر قطعه state هستید. این ها همه بخشی از فرایند است!
برای دیدن اینکه این در عمل با چند کامپوننت دیگر چگونه است، Thinking in React را بخوانید.
Recap
- وقتی میخواهید دو کامپوننت را هماهنگ کنید، state آنها را به والد مشترک آنها منتقل کنید.
- سپس اطلاعات را از طریق prop ها از والد مشترک به پایین منتقل کنید.
- در نهایت، event handler ها را به پایین منتقل کنید تا کامپوننت های فرزند بتوانند state والد را تغییر دهند.
- مفید است که کامپوننت ها را به عنوان “کنترل شده” (توسط prop ها) یا “کنترل نشده” (توسط state) در نظر بگیرید.
Challenge 1 of 2: ورودی های هماهنگ
این دو ورودی مستقل هستند. آنها را هماهنگ کنید: ویرایش یک ورودی باید ورودی دیگر را با همان متن به روز کند، و بالعکس.
import { useState } from 'react'; export default function SyncedInputs() { return ( <> <Input label="First input" /> <Input label="Second input" /> </> ); } function Input({ label }) { const [text, setText] = useState(''); function handleChange(e) { setText(e.target.value); } return ( <label> {label} {' '} <input value={text} onChange={handleChange} /> </label> ); }