در قسمت اول آموزش React، این کتابخانه را معرفی، و بررسی کردیم که تاثیر آن بر روند توسعه UI چیست و به چه صورت می توانیم React را به صفحه اضافه، و از امکانات آن استفاده کنیم. همچنین ایجاد کامپوننت ها و مباحث مربوط به آن عنوان شد و مشاهده کردیم که چطور با تعریف کامپوننت ها می توان صفحات پیچیده را ساده تر پیاده سازی کرد.
به طور دقیق تر با مباحث:
- معرفی React و JSX
- Render کردن عناصر React
- کامپوننت و Prop
- State و توابع Lifecycle
آشنا شدیم و در این قسمت با امکانات بیشتری از کتابخانه React آشنا خواهیم شد.
در ادامه مطلب همراه من باشید.
اداره کننده رویداد ها
اداره رویداد ها در React همانند اداره رویداد ها در DOM با کمی تغییرات نحوی می باشد همچنین رویداد ها در React به جای lowercase به صورت camelCase نامگذاری می شوند.
با استفاده از JSX، به جای در نظر گرفتن یک رشته (نام تابع)، یک تابع را به عنوان اداره کننده رویداد در نظر خواهیم گرفت.
برای مثال در HTML:
<button onclick="activateLasers()">
Activate Lasers
</button>
در React کمی متفاوت است:
<button onClick={activateLasers}>
Activate Lasers
</button>
تفاوت دیگری که در React وجود دارد این است که نمی توانیم مقدار false را برای جلوگیری از رفتار پیش فرض یک رویداد استفاده کنیم و به جای آن، باید صریحا preventDefault را فراخوانی کنیم. به طور مثال، در HTML برای جلوگیری از رفتار پیش فرض تگ a که باز کردن یک لینک می باشد می توانیم به صورت زیر عمل کنیم:
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
در صورتیکه این کار در React به صورت زیر انجام می شود:
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
در اینجا، e یک Synthetic Event
است. React این رویداد ها را طبق W3C spec تعریف می کند، بنابراین نگرانی در ارتباط با سازگاری مرورگر های مختلف نخواهیم داشت.
در React، بعد از ایجاد شدن یک عنصر DOM نیازی به فراخوانی addEventListener برای اضافه کردن listener نخواهیم داشت و فقط زمان اولین رندرعنصر، listener ارائه می شود.
زمانی که یک کامپوننت را با استفاده از ES6 class تعریف می کنیم، الگوی رایج برای اداره کننده رویداد، تعریف متد در کلاس است. برای مثال، در کد زیر یک کامپوننت به نام Toggle تعریف کردیم که یک button را render می کند و به کاربر این امکان را می دهد بین حالت های ON و OFF تغییر وضعیت دهد.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
باید به این نکته توجه داشته باشیم که در جاوااسکریپت متدهای کلاس به طور پیش فرض bound نمی شوند. اگر فراموش کنیم که this.handleClick را bind کنیم و آن را به onClick ارسال کنیم، مقدار this در تابع مربوط به رویداد برابر با undefined خواهد بود.
اگر فراخوانی bind برای شما آزار دهنده است، دو راه دیگر نیز وجود دارد.
می توانید از فیلد های class به صورت زیر استفاده کنید:
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
که این روش، پیش فرض Create React App می باشد.
اگر هم قصد استفاده از فیلد های class را ندشته باشید می توانید از arrow function در callback استفاده کنید:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
مشکل این روش این است که، هر زمان که LogginButton رندر می شود یک callback هم ایجاد می شود که البته در برخی از مواقع گزینه خوبی خواهد بود هر چند که اگر این callback به صورت prop به کامپوننت های سطح پایین تر ارسال شود، آن کامپوننت ها باید رندر مجدد و اضافی را انجام دهند. برای مسائل مربوط به حفظ Performance بهتر است توابع را یا در constructor، bind کنیم یا با استفاده از فیلد های کلاس.
ارسال آرگومان به اداره کننده رویداد
گاهی اوقات لازم است در یک حلقه یک پارامتر اضافی را به اداره کننده رویداد ارسال کنیم. در کد پایین، id شناسه هر سطر می باشد و هر دو خط کد زیر به درستی عمل خواهند کرد:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
دو خط بالا معادل یکدیگر هستند، و به ترتیب از arrow function و Function.prototype.bind استفاده کرده اند.
در هر دو مورد، آرگومان e رویداد React است که در مورد اول پس از ID ارسال شده است. در این حالت ما باید e را به عنوان آرگومان ارسال کنیم اما در حالت دوم و با استفاده از bind، این آرگومان به صورت خودکار ارسال می شود.
رندر شرطی
در React و با توجه به آموخته های قبلی، می توان کامپوننت های مجزا از هم، که دارای رفتار های متفاوت از یکدیگر هستند تعریف کرد. در این قسمت بررسی می کنیم چطور با استفاده از state، فقط برخی از انها را رندر کنیم.
رندر شرطی در React دقیقا به همان صورتی که شرط ها در جاوااسکریپت کار می کنند عمل می کنند. از اپراتورهای جاوااسکریپت مانند if یا اپراتور های شرطی برای ساخت عنصرهایی که طبق state جاری می باشند استفاده می کنیم و به React این امکان را می دهیم که UI را طبق آنها مطابقت دهد.
برای مثال دو کامپوننت زیر را در نظر بگیرید:
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
حالا یک کامپوننت تابعی دیگر به نام Greeting که هر یک از این دو کامپوننت را بر اساس وضعیت لاگین کاربر بر می گرداند ایجاد می کنیم:
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
این مثال Greeting های مختلف را طبق مقدار isLoggedIn رندر می کند.
متغیرها از نوع Element
می توانیم از متغیر ها برای ذخیره element ها هم استفاده کنیم. این مساله به ما کمک می کند تا به صورت شرطی قسمتی از کامپوننت را رندر کنیم و باقی خروجی تغییری نکند.
دو کامپوننت زیر را در نظر بگیرید که برای button های Login و Logout می باشند:
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
در مثال پایین ما یک stateful component به نام LoginController ایجاد می کنیم.
این کامپوننت یکی از دو کامپوننت های <LoginButton/> یا <LogoutButton/> را طبق state جاری نمایش می دهد. همچنین این کامپوننت از کامپوننت <Greeting/> در مثال قبلی استفاده می کند.
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
می توان با تعریف یک متغیر و با استفاده از if به صورت شرطی یک کامپوننت را رندر کرد، علاوه بر این می توان از syntax های کوتاه تر هم استفاده کرد. در ادامه چندین روش دیگر را برای پیاده سازی رندر شرطی بررسی خواهیم کرد.
If درون خطی با شرط و اپراتور
همانطور که قبلا هم مشاهده کردیم می توان در ،JSX از هر عبارتی با استفاده از آکولاد استفاده کرد. این موضوع شامل اپراتور && جاوااسکریپت هم می شود که در رندر شرطی به ما کمک می کند.
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
در جاوااسکریپت نتیجه true && expression همواره expression ارزیابی می شود و false && expression همواره false ارزیابی می شود از اینرو اگر شرط true باشد عنصر بعد از && در خروجی قرار می گیرد و اگر flase باشد، React آن را نادیده می گیرد و از آن رد می شود.
If و Else درون خطی با اپراتور شرطی
یکی دیگر از روش های رندر شرطی عناصر، استفاده از اپراتور شرطی جاوااسکریپت است که فرم آن به صورت condition ? true : false می باشد.
در مثال پایین ما برای رندر یک بلاک کوچک از متن، از این نوع if استفاده می کنیم.
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
همچنین می توان از این نوع if برای عبارات بزرگتر هم استفاده کرد که البته از خوانایی کمتری برخوردار است:
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
درست مانند جاوااسکریپت این موضوع به ما بر می گردد که از کدام حالت شرطی استفاده کنیم. به خاطر داشته باشید که شرط ها ممکن است پیچیده شوند و در چنین مواقعی راه حل بهتر تقسیم کامپوننت به کامپوننت های کوچکتر است.
جلوگیری کامپوننت از رندر
در برخی موارد ممکن است بخواهیم کامپوننتی را که رندر شده است hide کنیم. برای انجام این کار کافی است مقدار null را به جای خروجی متد render بازگردانید.
در مثال پایین، <WarningBanner/> بر اساس مقدار prop که warn می باشد رندر می شود. اگر مقدار prop برابر با false باشد کامپوننت رندر نخواهد شد.
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(state => ({
showWarning: !state.showWarning
}));
}
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
);
بازگرداندن null از یک کامپوننت باعث عدم اجرای متد های lifecycle نخواهد شد. برای مثال componentDidUpdate همچنان فراخوانی می شود.
لیست ها و کلید ها
در ابتدا تبدیل لیست ها در جاوااسکریپت را بررسی می کنیم.
کد زیر را در نظر بگیرید، ما با استفاده از تابع map یک آرایه از اعداد را دریافت کردیم و مقادیر موجود در آن را دو برابر کردیم و خروجی تابع را در قالب یک آرایه در متغیری به نام doubled قرار دادیم و در کنسول به صورت لاگ نمایش دادیم.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
این کد آرایه [2,4,6,8,10] را در کنسول نمایش می دهد.
در React تبدیل آرایه ها به لیست، مشابه مثالی است که بررسی کردیم.
رندر کردن چندین کامپوننت
می توانیم مجموعه ای از element ها را ایجاد، و در JSX از آن استفاده کنیم.
در نمونه کد پایین با استفاده از تابع map به ازای هر آیتم آرایه numers یک عنصر <li> ایجاد کردیم و آرایه تشکیل شده (نتیجه تابع map) را به متغیر listItems تخصیص دادیم:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
پس از تخصیص، آرایه listItems را در داخل یک عنصر ul قرار دادیم و آن را در DOM رندر کردیم.
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
این کد یک لیست از اعداد 1 تا 5 را نمایش می دهد.
کامپوننت مبتنی بر لیست
می توانیم از مثال قبلی در قالب کامپوننت استفاده کنیم، به صورتیکه یک آرایه به نام numbers را دریافت می کند و یک لیست از عناصر را به عنوان خروجی بر می گرداند.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
زمانی که این کد را اجرا می کنیم یک اخطار مبنی بر اینکه باید برای هر آیتم لیست یک کلید تعریف کنیم دریافت خواهیم کرد. یک “key”یک attribute رشته ای است و زمانی که لیستی از element ها را ایجاد می کنیم باید به هر عنصر اختصاص دهیم. در قسمت بعدی اهمیت کلید را بررسی می کنیم.
به هر یک از آیتم های لیست در numbers.map()، یک کلیداختصاص می دهیم و اخطار کلید را رفع می کنیم.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
کلید ها
کلیدها به React کمک می کنند تا تشخیص دهد چه آیتم هایی تغییر، اضافه یا حذف شده است. کلید ها باید در داخل آرایه به عناصر تخصیص یابند تا دارای هویتی پایدار باشند.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
بهترین راه برای انتخاب کلید، استفاده از یک رشته است که به صورت منحصر به فرد، آیتم های لیست را از یک دیگر متمایز می کند. اغلب اوقات می توانیم از ID داده به عنوان کلید استفاده کنیم.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
در مواقعی که ID مناسبی برای رندر کردن آیتم ها در اختیار نداریم می توانیم از index آیتم به عنوان کلید استفاده کنیم.
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
بهتر است از index به عنوان کلید استفاده نکنیم زیرا ممکن است ترتیب آیتم ها تغییر کند و این موضوع می تواند روی Performance تاثیر منفی داشته باشد و باعث رخ دادن مسائلی در state کامپوننت شود. اگر کلیدی را برای عنصر در نظر نگیریم React به صورت خودکار از index برای کلید آرایه استفاده می کند.
استخراج کامپوننت با کلید
کلید ها فقط در چارچوب آرایه ای که در آن هستند معنی پیدا می کنند.
برای مثال اگر ما یک کامپوننت به نام ListItem را استخراج کنیم، کلید را به جای عناصر <li> باید به عناصر <ListItem/> که در آرایه ایجاد می شوند تخصیص دهیم.
استفاده نادرست از Key
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
استفاده صحیح از Key
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
به بیان ساده تر element هایی که داخل map فراخوانی می شوند نیاز به کلید دارند.
کلید ها باید در سطحی که عنصر قرار دارد منحصر به فرد باشند
کلید های استفاده شده در یک آرایه باید دقیقا در همان آرایه منحصر به فرد باشند و نیازی به اینکه به صورت کلی منحصر به فرد باشند نیست. این بدین معنی است که ما می توانیم از کلید های مشابه در دو آرایه مختلف استفاده کنیم.
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
کلیدها فقط توسط React استفاده می شوند
این موضوع بیانگر آن است که نمی توان در کامپوننت به کلید ها دسترسی داشت و اگر قصد دسترسی به آنها را داشته باشیم باید به صورت مستقیم به عنوان یک props به کامپوننت ارسال کنیم.
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
در مثال بالا کامپوننت Post می تواند به props.id دسترسی داشته باشد و آن را بخواند اما نمی تواند به props.key دسترسی داشته باشد و key فقط توسط خود React قابل استفاده است.
استفاده از map() در JSX
در مثال های بالا ما یک متغیر listItems جدا تعریف کردیم واز آن به صورت JSX استفاده کردیم.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
JSX به ما این امکان را می دهد که از هر عبارت جاوااسکریپتی در بین دو آکولاد استفاده کنیم. بنابراین می توانیم از تابع map هم در JSX و بین دو آکولاد استفاده کنیم.
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
بعضی اوقات استفاده از فرم های کوتاه تر باعث تمیز تر شدن کد می شود ولی گاهی اوقات هم ممکن است از آن استفاده نکنیم. همانند جاوااسکریپت این مساله ای است که مربوط به ماست و ما باید در این مورد انتخاب کنیم که کدام فرم مناسب است. باید در نظر داشته باشیم، زمانیکه map های ما تو در تو شدند، زمان تقسیم کامپوننت به کامپوننت های کوچکتر رسیده است.
فرم ها
فرم ها در React کمی متفاوت از فرم های HTML هستند زیرا عناصر فرم به صورت عادی دارای تعدادی state داخلی هستند. برای مثال در نمونه کد زیر فرمی ایجاد شده که یک نام را دریافت می کند.
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
این فرم دارای رفتار پیش فرض فرم های HTML است و زمانی که کاربر آن را submit می کند به صفحه جدید منتقل می شود. چنین کاری در React هم قابل پیاده سازی است اما در اغلب موارد بهتر است یک تابع جاوااسکریپت داشته باشیم تا submit فرم را اداره کند و به داده هایی که کاربر در فرم وارد کرده است دسترسی داشته باشد. راه استاندارد برای انجام چنین کاری تکنیکی به نام controlled component ها هستند.
کامپوننت های کنترلی
در HTML، عناصر فرم مانند: <input>، <textarea> و <select> معمولاstate خودشان را دارند و آن مقدار را بر اساس ورودی کاربر به روز رسانی می کنند. در صورتی که در React، state قابل تغییر معمولا در state کامپوننت قرار می گیرد و فقط با setState() بروزرسانی می شود.
می توانیم عناصر فرم HTML را با React و state آن ترکیب کنیم بنابراین کامپوننت React که فرم را رند می کند، آنچه که در فرم در جریان است مانند: ورودی های کاربر را کنترل می کند . یک عنصر فرم که مقدارش توسط React بدین صورت کنترل می شود “Controlled Component” خوانده می شود.
در نمونه کد زیر مثال قبل را که نام را در لاگ نمایش می داد به شکل کامپوننت کنترلی ایجاد کردیم:
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 مشخص شده برای آن را در input نمایش می دهد که در واقع از State در React استفاده می کند. سپس متد handleChanges پس از هر بارفشرده شدن کلید اجرا می شود و State با مقداری که کاربر وارد کرده است بروز می شود.
هر تغییر State در کامپوننت کنترلی با یک تابع اداره کننده مرتبط است که کار را برای ویرایش یا اعتبار سنجی ورودی کاربر آسان تر می کند. برای مثال، اگر ما بخواهیم همه حروف نامی که کاربر وارد می کند به صورت بزرگ باشد تابع handleChange را به صورت زیر تغییر می دهیم:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
تگ textarea
در HTML یک عنصر <textarea> متنش را با استفاده از گره فرزندش تعریف می کند.
<textarea>
Hello there, this is some text in a text area
</textarea>
در React به جای اینکه متن را در بین تگ textarea بنویسیم از مشخصه value استفاده می کنیم بدین صورت مانند باقی عناصر فرم می توانیم از تگ input یک خطی برای 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 را برای این گزینه در نظر گرفتیم. در React به جای استفاده از ،selected برای انتخاب پیش فرض یک آیتم در تگ select می توان از مشخصه value در خود تگ select استفاده کرد که این کار باعث می شود تا کار ما در کامپوننت کنترلی راحت تر باشد و برای بروز رسانی آن فقط لازم است تا value را بروز رسانی کنیم.
به عنوان مثال:
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 تعریف شده است انتخاب کنیم:
<select multiple={true} value={['B', 'C']}>
تگ file
در HTML یک تگ <input type=”file”> به کاربر این امکان را می دهد که یک یا چند فایل را از دستگاه خودش انتخاب کند و آن را روی سرور آپلود یا با استفاده از توابع جاوااسکریپت ویرایش کند.
<input type="file" />
چون مقادیر این نوع input، read-only هستند کامپوننت های غیر کنترلی محسوب می شوند و در فرصت های بعدی به صورت دقیق تر در مورد آنها بحث خواهیم کرد.
اداره کردن چند input
زمانی که شما نیاز به کنترل چندین عنصر input دارید، می توانید برای هر عنصر input ویژگی name را مشخص کنید و به تابع اداره کننده این امکان را بدهید که بر اساس مقدار 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>
);
}
}
توجه داشته باشید که از ES6 syntax برای بروز رسانی state استفاده کردیم و نام input را طبق این syntax به عنوان یک کلید در state تعریف کردیم.
this.setState({
[name]: value
});
که اینکار در ES5 به این صورت انجام می شد:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
مقدار Null برای Input های کنترلی
تعیین کردن مقدار برای مشخصه value در کامپوننت های کنترلی، از تغییر input های فرم توسط کاربر جلوگیری می کند اما اگر مقدار null را برای value مشخص کنیم، قابل ویرایش خواهد شد.
به طور مثال در نمونه کد زیر، در ابتدا Input، اجازه ویرایش ندارد اما بعد از یک تاخیر کوتاه قابل ویرایش می شود.
ReactDOM.render(<input value="hi" />, mountNode);
setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);
جایگزین های کامپوننت کنترلی
گاهی اوقات استفاده از کامپوننت های کنترلی می تواند باعث کند شدن روند توسعه بشود، زیرا باید برای هر راهی که کاربر می تواند داده را تغییر دهد، یک اداره کننده رویداد ایجاد کنیم و input state را از طریق React Component حفظ کنیم. این موضوع به خصوص زمان تبدیل یک اپلیکیشن قدیمی که از React استفاده نمی کند یا ترکیب یک اپلیکیشن React با یک کتابخانه غیر React مشکل ساز خواهد بود. در چنین مواقعی می توانیم از کامپوننت های غیر کنترلی استفاده کنیم که یک تکنیک جایگزین برای پیاده سازی فرم های input است.
برای کار با فرم ها و عناصر مربوط به آن می توانیم از Formik که اعتبار سنجی یا ادره submit فرم را ارائه می کند استفاده کنیم.
فایل های آموش React - قسمت دوم را می توانید از این لینک دانلود کنید.
منبع: reactjs.org
React | Javascript |