ایجاد ایالات در سطح جهانی می تواند عملکرد برنامه شما را کاهش دهد. بیاموزید که چگونه می توانید به طور موثر حالت ها را در برنامه React خود ایجاد کرده و از آنها استفاده کنید.
اگر کدهای React زیادی نوشته اید، به احتمال زیاد از State به اشتباه استفاده کرده اید. یکی از اشتباهات رایج بسیاری از توسعه دهندگان React این است که حالت ها را به صورت جهانی در برنامه ذخیره می کنند، به جای ذخیره آنها در اجزای مورد استفاده.
بیاموزید که چگونه می توانید کد خود را برای استفاده از حالت محلی بازسازی کنید و چرا انجام این کار همیشه ایده خوبی است.
مثال اصلی State در React
در اینجا یک برنامه شمارنده بسیار ساده وجود دارد که نمونه ای از نحوه مدیریت حالت به طور معمول در React است:
import {useState} from 'react'
import {Counter} from 'counter'
function App(){
const [count, setCount] = useState(0)
return <Counter count={count} setCount={setCount} />
}
export default App
در خطوط 1 و 2، قلاب useState() را برای ایجاد حالت و مولفه Counter وارد میکنید. شما حالت count و روش setCount را برای به روز رسانی حالت تعریف می کنید. سپس هر دو را به مولفه Counter منتقل می کنید.
سپس مولفه Counter تعداد را رندر می کند و setCount را برای افزایش و کاهش تعداد فراخوانی می کند.
function Counter({count, setCount}) {
return (
<div>
<button onClick={() => setCount(prev => prev - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount(prev => prev + 1)}>+</button>
</div>
)
}
شما متغیر count و تابع setCount را به صورت محلی در مولفه Counter تعریف نکردید. در عوض، شما آن را از مؤلفه والد (برنامه) ارسال کردید. به عبارت دیگر، شما از یک دولت جهانی استفاده می کنید.
مشکل با کشورهای جهانی
مشکل استفاده از حالت جهانی این است که شما حالت را در یک جزء والد (یا والد یک والد) ذخیره میکنید و سپس آن را بهعنوان پایه به مؤلفهای که آن حالت واقعاً مورد نیاز است، منتقل میکنید.
گاهی اوقات وقتی حالتی دارید که در بسیاری از مؤلفهها به اشتراک گذاشته شده است، خوب است. اما در این حالت، جز مولفه Counter، هیچ جزء دیگری به وضعیت count اهمیت نمی دهد. بنابراین، بهتر است حالت را به مولفه Counter جایی که در واقع از آن استفاده می شود، منتقل کنید.
انتقال دولت به مؤلفه کودک
هنگامی که حالت را به مولفه Counter منتقل می کنید، به شکل زیر است:
import {useState} from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount(prev => prev - 1)}>-</button>
<span>{count}</span>
<button onClick={() => setCount(prev => prev + 1)}>+</button>
</div>
)
}
سپس در داخل مؤلفه App خود، لازم نیست چیزی را به مؤلفه Counter منتقل کنید:
// imports
function App(){
return <Counter />
}
شمارنده دقیقاً مانند قبل کار می کند، اما تفاوت بزرگ این است که همه ایالت های شما به صورت محلی در داخل این مولفه Counter قرار دارند. بنابراین اگر نیاز دارید که شمارنده دیگری در صفحه اصلی داشته باشید، دو شمارنده مستقل خواهید داشت. هر پیشخوان مستقل است و از تمام وضعیت خود مراقبت می کند.
مدیریت وضعیت در برنامه های پیچیده تر
موقعیت دیگری که در آن از یک حالت جهانی استفاده می کنید، فرم ها است. مؤلفه App زیر، دادههای فرم (ایمیل و رمز عبور) و روش تنظیمکننده را به مؤلفه LoginForm منتقل میکند.
import { useState } from "react";
import { LoginForm } from "./LoginForm";
function App() {
const [formData, setFormData] = useState({
email: "",
password: "",
});
function updateFormData(newData) {
setFormData((prev) => {
return { ...prev, ...newData };
});
}
function onSubmit() {
console.log(formData);
}
return (
<LoginForm
data={formData}
updateData={updateFormData}
onSubmit={onSubmit}
/>
);
}
مؤلفه LoginForm اطلاعات ورود را دریافت کرده و آن را ارائه می کند. هنگامی که فرم را ارسال می کنید، تابع updateData را فرا می خواند که از مؤلفه والد نیز منتقل می شود.
function LoginForm({ onSubmit, data, updateData }) {
function handleSubmit(e) {
e.preventDefault();
onSubmit();
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
value={data.email}
onChange={(e) => updateData({ email: e.target.value })}
/>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={data.password}
onChange={(e) => updateData({ password: e.target.value })}
/>
<button type="submit">Submit</button>
</form>
);
}
به جای مدیریت حالت در مؤلفه والد، بهتر است وضعیت را به LoginForm.js منتقل کنید، جایی که از کد استفاده می کنید. انجام این کار باعث می شود هر جزء مستقل باشد و برای داده ها به مؤلفه دیگری (یعنی والد) وابسته نباشد. در اینجا نسخه اصلاح شده LoginForm آمده است:
import { useRef } from "react";
function LoginForm({ onSubmit }) {
const emailRef = useRef();
const passwordRef = useRef();
function handleSubmit(e) {
e.preventDefault();
onSubmit({
email: emailRef.current.value,
password: passwordRef.current.value,
});
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input type="email" id="email" ref={emailRef} />
<label htmlFor="password">Password</label>
<input type="password" id="password" ref={passwordRef} />
<button type="submit">Submit</button>
</form>
);
}
در اینجا شما با استفاده از ویژگیهای ref و قلاب useRef React، به جای ارسال مستقیم روشهای بهروزرسانی، ورودی را به یک متغیر متصل میکنید. این به شما کمک می کند تا کد پرمخاطب را حذف کنید و عملکرد فرم را با استفاده از قلاب useRef بهینه کنید.
در مولفه والد (App.js)، میتوانید هم حالت global و هم متد updateFormData() را حذف کنید زیرا دیگر به آن نیاز ندارید. تنها تابع باقیمانده onSubmit() است که شما آن را از داخل مؤلفه LoginForm فراخوانی میکنید تا جزئیات ورود به سیستم را در کنسول ثبت کنید.
function App() {
function onSubmit(formData) {
console.log(formData);
}
return (
<LoginForm
data={formData}
updateData={updateFormData}
onSubmit={onSubmit}
/>
);
}
نه تنها ایالت خود را تا حد امکان محلی کردید، بلکه در واقع نیاز به هر حالتی را حذف کردید (و به جای آن از ref استفاده کردید). بنابراین مؤلفه برنامه شما به طور قابل توجهی ساده تر شده است (فقط یک عملکرد).
مؤلفه LoginForm شما نیز سادهتر شده است زیرا نیازی به نگرانی در مورد بهروزرسانی وضعیت ندارید. در عوض، شما فقط دو داور را دنبال می کنید، و تمام.
رسیدگی به وضعیت مشترک
یک مسئله در رویکرد تلاش برای محلی کردن ایالت تا حد امکان وجود دارد. شما اغلب با سناریوهایی مواجه می شوید که در آن مؤلفه والد از حالت استفاده نمی کند، اما آن را به چندین مؤلفه منتقل می کند.
یک مثال داشتن یک مؤلفه والد TodoContainer با دو مؤلفه فرزند است: TodoList و TodoCount.
function TodoContainer() {
const [todos, setTodos] = useState([])
return (
<>
<TodoList todos={todos}>
<TodoCount todos={todos}>
</>
)
}
هر دوی این مؤلفههای فرزند به حالت todos نیاز دارند، بنابراین TodoContainer آن را به هر دوی آنها ارسال میکند. در سناریوهایی مانند این، شما باید ایالت را تا حد امکان محلی کنید. در مثال بالا، قرار دادن آن در داخل TodosContainer تا جایی که می توانید محلی است.
اگر بخواهید این حالت را در مؤلفه App خود قرار دهید، تا آنجا که ممکن است محلی نخواهد بود زیرا نزدیکترین والد به دو مؤلفه نیست که به داده نیاز دارند.
برای برنامه های بزرگ، مدیریت حالت فقط با قلاب useState() می تواند دشوار باشد. در چنین مواردی، ممکن است لازم باشد React Context API یا React Redux را برای مدیریت موثر حالت انتخاب کنید.
درباره React Hooks بیشتر بیاموزید
هوک ها پایه و اساس React را تشکیل می دهند. با استفاده از هوک ها در React، می توانید از نوشتن کدهای طولانی که در غیر این صورت از کلاس ها استفاده می کنند، جلوگیری کنید. قلاب useState() بدون استدلال رایج ترین قلاب React است، اما موارد دیگر مانند useEffect()، useRef() و useContext() وجود دارد.
اگر میخواهید در توسعه برنامهها با React مهارت داشته باشید، باید بدانید که چگونه از این هوکها در برنامه خود استفاده کنید.