فرمها
المنتهای فرم HTML کمی متفاوت از سایر المنتهای DOM در React عمل می کنند ، زیرا المنتهای فرم به طور طبیعی مقداری state درونی را حفظ می کنند. به عنوان مثال ، این فرم که بهصورت HTML ساده نوشته شده است یک نام را قبول می کند:
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
این فرم رفتار پیش فرض فرم HTML که کاربر پس از ثبت فرم به صفحهی جدید انتقال پیدا میکند را داراست . اگر این رفتار را در ریاکت میخواهید نیاز به انجام هیچ تغییری ندارید. اما در بیشتر موارد، مناسب است که یک تابع جاوااسکریپت داشتهباشیم که ثبت فرم را انجام میدهد و به دادههایی که کاربر در فرم وارد کرده است دسترسی دارد. روش استاندارد پیاده سازی این رفتار تکنیکی به نام “کامپوننتهای کنترلشده” میباشد.
کامپوننتهای کنترلشده
در HTML، المنتهای فرم مانند <input>
, <textarea>
و <select>
, معمولا وضعیت داده وارد شده را در خود نگه میدارند و بر اساس ورودی کاربر آن را بهروز رسانی میکنند. در ریاکت، معمولا وضعیت قابلتغییر در state کامپوننتها نگهداری میشود، و فقط از طریق متد setState()
بهروز رسانی میشود.
ما میتوانیم با قرار دادن قابلیت state کامپوننت در ریاکت به عنوان “تنها منبع حقیقت” [single source of truth] این دو را با هم ترکیب کنیم. آنگاه کامپوننت ریاکتی که فرم را رندر میکند، مشخص کننده نحوه عملکرد فرم در قبال ورودی کاربر نیز میباشد. یک المنت فرم ورودی داده که مقدارش توسط ریاکت به این صورت کنترل میشود را “کامپوننت کنترلشده” مینامند.
به عنوان مثال، اگر بخواهیم در مثال قبل در زمان ثبت فرم، نام نمایش داده شود، میتوانیم فرم را بهصورت یک کامپوننت کنترلشده بنویسیم:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) { this.setState({value: event.target.value}); }
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}> <label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} /> </label>
<input type="submit" value="Submit" />
</form>
);
}
}
از آنجایی که خصوصیت value روی المنت فرم ما قرار گرفتهاست، مقدار نمایش دادهشده همیشه this.state.value
که باعث میشود state ریاکت به عنوان منبع نگهداری داده و وضعیت باشد. از آنجایی که handleChange
با هر فشردن کلید برای بهروز رسانی state ریاکت اجرا میشود، مقدار نمایش دادهشده هم با تایپ کردن کاربر بهروزرسانی خواهد شد.
با داشتن یک کامپوننت کنترلشده، همیشه مقدار input از state ریاکت گرفته میشود. درحالی که کد بیشتری باید بنویسید، می توانید این مقدار را به المنتهای دیگر رابط کاربری بدهید یا با event handler های دیگر آن را تغییر دهید.
تگ textarea
در HTML، یک المنت <textarea>
مقدارش را بر اساس فرزندانش [متن بین تگ باز و بسته این المنت] تعریف میکند:
<textarea>
Hello there, this is some text in a text area
</textarea>
در عوض در ریاکت، تگ <textarea>
از خصوصیت value
برای این کار استفاده میکند. با این روش، فرمی که دارای المنت <textarea>
هست را میتوان خیلی شبیه به یک فرم با ورودیهای تکخطی نوشت:
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: 'Please write an essay about your favorite DOM element.' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) { this.setState({value: event.target.value}); }
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} /> </label>
<input type="submit" value="Submit" />
</form>
);
}
}
توجه کنید که this.state.value
در تابع سازنده کلاس (constructor) مقدار دهی اولیه میشود، بنابراین در ابتدا textarea با متنی درون آن نمایش داده میشود.
تگ select
در HTML، تگ <select>
یک لیست کشویی میسازد. برای مثال، این HTML یک لیست کشویی از طعمها را میسازد:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
توجه کنید که ابتدا گزینه Coconut به خاطر خصوصیت selected
انتخاب شده است. ریاکت به جای استفاده از خصوصیت selected
، از خصوصیت value
در تگ select
ریشه استفاده میکند. این روش در کامپوننتهای کنترلشده راحتتر است زیرا شما تنها نیاز به بهروزرسانی آن در یک مکان دارید. برای مثال:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) { this.setState({value: event.target.value}); }
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
در کل، این نوع ساختار باعث میشود که <input type="text">
, <textarea>
و <select>
، همگی بسیار مشابه کار کنند. همگی یک خصوصیت value
دریافت میکنند که میتوانید با استفاده از آن کامپوننت کنترلشده را پیاده سازی کنید.
نکته
شما میتوانید یک آرایه را به خصوصیت value پاس دهید که به شما اجازه انتخاب چندین گزینه در یک تگ select را میدهد:
<select multiple={true} value={['B', 'C']}>
تگ file input
در HTML، یک <input type="file">
به کاربر اجازه میدهد که یک یا چند فایل را از محل ذخیرهسازی دستگاه خود انتخاب کند تا روی سرور بارگذاری شود یا توسط File API جاوااسکریپت روی آن کار شود.
<input type="file" />
به دلیل اینکه مقدار این المنت فقط قابلخواندن میباشد، این المنت در ریاکت یک کامپوننت کنترلنشده میباشد. در مورد این کامپوننت همراه با دیگر کامپوننتهای کنترلنشده در بخش دیگری از این اسناد درباره آن بحث شدهاست.
کار با چندین ورودی
زمانی که نیاز دارید چند فیلد ورودی input
کنترلشده به کار ببرید، میتوانید یک خصوصیت name
برای هر یک از المنتها تعریف کنید و اجازه دهید که تابع handler بر اساس مقدار event.target.name تصمیم بگیرد که چه کاری انجام دهد.
برای مثال:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value });
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing" type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests" type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
توجه داشتهباشید که ما چگونه از قاعده نوشتن computed property name در ES6 برای بهروز رسانی state با کلید مرتبط با نام ورودی دادهشده استفاده کردهایم:
this.setState({
[name]: value});
معادل با این کد در ES5 است:
var partialState = {};
partialState[name] = value;this.setState(partialState);
همچنین از آن جایی که setState()
بهصورت خودکار میتواند تغییرات جزیی state را به درون state فعلی ادغام کند, ما فقط نیاز داریم آن را با بخشی که تغییر کردهاست فراخوانی کنیم.
مقدار Null ورودی کنترلشده
قراردادن prop به نام value روی یک کامپوننت کنترلشده از تغییر ورودی توسط کاربر جلوگیری میکند، مگر اینکه شما بخواهید. اگر یک value
روی ورودی قرار دادهاید، اما هنوز قابل ویرایش است، ممکن است value
را تصادفا با undefined
و یا null
مقداردهی کرده باشید.
نمونه کد زیر این [رفتار] را نشان میدهد. (در ابتدا فیلد ورودی قفل است اما پس از زمانی کوتاه قابلتغییر میشود.)
ReactDOM.createRoot(mountNode).render(<input value="hi" />);
setTimeout(function() {
ReactDOM.createRoot(mountNode).render(<input value={null} />);
}, 1000);
راهحلهای جایگزین کامپوننتهای کنترلشده
در بعضی مواقع استفاده از کامپوننتهای کنترلشده میتواند خستهکننده باشد، چون شما میبایست برای هر شکلی که دادهها تغییر میکنند یک event handler بنویسید و تمام وضعیت ورودی را به یک کامپوننت ریاکتی انتقال دهید. این میتواند مخصوصا زمانی که قصد دارید کد موجودتان را به ریاکت تبدیل کنید، یا برنامه ریاکتی را با یک کتابخانه غیرریاکتی ادغام کنید آزار دهنده شود. در چنین شرایطی شاید بهتر باشد به کامپوننتهای کنترلنشده نگاهی بیاندازید که یک تکنیک جایگزین برای پیاده سازی فرم های ورودی میباشد.
راهحلهای تمامعیار
اگر به دنبال یک راهحل کامل که شامل مواردی از قبیل اعتبار سنجی مقدار ورودی توسط کاربر، نظارت بر فیلدهای مشاهدهشده و کنترل نحوه ثبت فرم باشد، Formik یکی از محبوبترین گزینهها میباشد. با این حال، این راهحل بر اساس اصول کامپوننتهای کنترلشده و مدیریت state ساخته شده است، پس از یادگیری آن غفلت نکنید.