یکی از ویژگیهای مفید jQuery در هنگام توسعهی نرمافزارهای تحت وب، رویدادهای( event ) سراسری است. با تعریف این رویدادها میتوانید در هنگام هر فراخوانی ajax در هر بخش از نرمافزار، آگاه شوید و عملیات دلخواه مانند نمایش عبارت «loading» یا جلوگیری از بارگزاری مجدد صفحه یا هر عمل متناسب دیگر را به انجام برسانید
FirstName
firstName
strFirstName lblFirstName
<Company>.<Technology|Produt|Project>[.<Feature>][.<SubNamespace>]
Microsoft.Reporting.WebForms Kara.Support.Manager.Enums Kara.CSS.HQ.WebUI.Configuration
PackageManager , PacakgeConfigGenerator Circle , Utility , Package
CreateConfig , classdata CManager , ClsPackage , Config_Creator , Config1389
CenterCollection , PackageCollection
public delegate void Logger (string log); public delegate void LoggingCallback (object sender, string reason);
AddEventArgs , EditEventArgs , DeleteEventArgs
Adding , Add , Added
public delegate void <EventName>EventHandler (object sender, <EventName>EventArgs e);
public event EventHandler <AddEventArgs> Adding;
DisplayNameAttribute , MessageTypeAttribute
IComponent (اسم) IConnectionProvider (موصوف) ICloneable (صفت)
public enum FileMode { Append, Read, … }
[Flag] public enum KeyModifiers { Alt = 1, Control = 2, Shift = 4 }
public enum OperationState { DoneState, FaultState, RollbackState }
AddDays , Save , DeleteRow , BindData , Close , Open
Radius , ReportType , DataSource , Mode , CurrentCenterId
public CenterCollection Centers { get; set; }
if (list.Contains(item)) if (regularExpression.Matches(text)) if (stream.CanSeek) if (context.Created) if (form.Enabled)
if (list.IsContains(item)) if (regularExpression.Match(text)) if (stream.Seekable) if (context.IsCreated) if (form.IsEnabled)
public Color Color { get; set; }
firstName , e , id , packageId , centerName , name
public static MyType operator +(MyType left, MyType right) public static bool operator ==(MyType left, MyType right)
public static MyType operator ++(MyType value)
public static MyType operator /(MyType dividend, MyType divisor)
public static MyType operator -(MyType d1, MyType d2) // incorrect!
Area , DataBinder , PublicCacheName
_centersList _firstName _currentCenter
parameterType , packageOperationTypeId
lblName (Label) txtHeader (TextBox) btnSave (Button)
ArgumentNullException , InvalidOperaionException
System.Data
<Company>.<Component>.dll <Company>.<Project|Product|Technology>.<Component>.dll
Microsoft.CSharp.dll , Kara.CSS.Manager.dll
public int IComparer<T> {…} public delegate bool Predicate<T> (T item)
public int ISessionChannel<TSession> {…} public delegate TOutput Converter<TInput, TOutput> (TInput from) public class Nullable<T> {…} public class List<T> {…}
ArgumentExceptionIllegalCharacters ArgumentExceptionInvalidName ArgumentExceptionFileNotFound
Pascal Casing |
camel Casing |
Wrong |
Callback |
callback |
CallBack |
BitFlag |
bitFlag |
Bitflag / bitflag |
Canceled |
canceled |
Cancelled |
DoNot |
doNot |
Donot / Don’t |
|
|
|
Endpoint |
endpoint |
EndPoint / endPoint |
FileName |
fileName |
Filename / filename |
Gridline |
gridline |
GridLine / gridLine |
Hashtable |
hashtable |
HashTable / hashTable |
Id |
id |
ID |
Indexes |
indexes |
Indices |
LogOff |
logOff |
Logoff / LogOut ! |
LogOn |
logOn |
Logon / LogIn ! |
SignOut |
signOut |
Signout / SignOff |
SignIn |
signIn |
Signin / SignOn |
Metadata |
metadata |
MetaData / metaData |
Multipanel |
multipanel |
MultiPanel / multiPanel |
Multiview |
multiview |
MultiView / multiView |
Namespace |
namespace |
NameSpace / nameSpace |
Ok |
ok |
OK |
Pi |
pi |
PI |
Placeholder |
placeholder |
PlaceHolder / placeHolder |
UserName |
username |
Username / username |
WhiteSpace |
whiteSpace |
Whitespace / whitespace |
Writable |
writable |
Writeable / writeable |
public class Color { … public static Color FromArgb(…) { … } }
ایجاد ساختار ابتدایی پروژه
برای ساخت پروژه، به خط فرمان مراجعه کرده و با دستور زیر، یک پروژهی react از نوع typescript را ایجاد میکنیم.
npx create-react-app todo-mobx --template typescript cd todo-mobx
برای توسعهی این مثال، از محیط توسعهی VSCode استفاده میکنیم. اگر VSCode بر روی سیستم شما نصب باشد، در همان مسیری که خط فرمان باز است، دستور زیر را اجرا کنید؛ پروژهی شما در VSCode باز میشود:
code
سپس در محیط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest npm install mobx mobx-react-lite --save
در ادامه برای استایل بندی بهتر برنامه از کتابخانههای bootstrap و font-awesome استفاده میکنیم:
npm install bootstrap --save npm install font-awesome --save
سپس فایل index.tsx را باز کرده و دو خط زیر را به آن اضافه میکنیم:
import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css";
کتابخانهی MobX، از تزئین کنندهها یا decorators استفاده میکند. بنابراین نیاز است به tsconfig پروژه مراجعه کرده و خط زیر را به آن اضافه کنیم:
"compilerOptions": { .... , "experimentalDecorators": true }
ایجاد مخازن حالت MobX
در ادامه نیاز است storeهای MobX را ایجاد کنیم و بعد آنها را به react اتصال دهیم. بدین منظور یک پوشهی جدید را در مسیر src، به نام stores ایجاد میکنیم و سپس فایل جدیدی را به نام todo-item.ts در آن با محتوای زیر ایجاد میکنیم:
import { observable, action } from "mobx"; export default class TodoItem { id = Date.now(); @observable text: string = ''; @observable isDone: boolean = false; constructor(text: string) { this.text = text; } @action toggleIsDone = () => { this.isDone = !this.isDone } @action updateText = (text: string) => { this.text = text; } }
در همان مسیر stores، فایل دیگری را نیز به نام todo-list.ts، با محتوای زیر ایجاد میکنیم:
import { observable, computed, action } from "mobx"; import TodoItem from "./todo-item"; export class TodoList { @observable.shallow list: TodoItem[] = []; constructor(todos: string[]) { todos.forEach(this.addTodo); } @action addTodo = (text: string) => { this.list.push(new TodoItem(text)); } @action removeTodo = (todo: TodoItem) => { this.list.splice(this.list.indexOf(todo), 1); }; @computed get finishedTodos(): TodoItem[] { return this.list.filter(todo => todo.isDone); } @computed get openTodos(): TodoItem[] { return this.list.filter(todo => !todo.isDone); } }
توضیحات:
مفهوم observable@: کل شیء state را به صورت یک شیء قابل ردیابی JavaScript ای ارائه میکند.
مفهوم computed@: این نوع خواص، مقدار خود را زمانیکه observableهای وابستهی به آنها تغییر کنند، به روز رسانی میکنند.
مفهوم action@: جهت به روز رسانی state و سپس نمایش تغییرات یا نمایش نمونهی دیگری در DOM میباشند.
import { createContext, useContext } from "react"; import { TodoList } from "../stores/todo-list"; export const StoreContext = createContext<TodoList>({} as TodoList); export const StoreProvider = StoreContext.Provider; export const useStore = (): TodoList => useContext(StoreContext);
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css"; import { TodoList } from './stores/todo-list'; import { StoreProvider } from './providers/store-provider'; const todoList = new TodoList([ 'Read Book', 'Do exercise', 'Watch Walking dead series' ]); ReactDOM.render( <StoreProvider value={todoList}> <App /> </StoreProvider> , document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
import React, { useState } from 'react'; import { useStore } from '../providers/store-provider'; export const TodoNew = () => { const [newTodo, setTodo] = useState(''); const todoList = useStore(); const addTodo = () => { todoList.addTodo(newTodo); setTodo(''); }; return ( <div className="input-group mb-3"> <input type="text" className="form-control" placeholder="Add To do" value={newTodo} onChange={(e) => setTodo(e.target.value)} /> <div className="input-group-append"> <button className="btn btn-success" type="submit" onClick={addTodo}>Add Todo</button> </div> </div> ) };
import React from 'react'; import { TodoItem } from "./TodoItem"; import { useObserver } from "mobx-react-lite"; import { useStore } from '../providers/store-provider'; export const TodoList = () => { const todoList = useStore(); return useObserver(() => ( <div> <h1>Open Todos</h1> <table className="table"> <thead className="thead-dark"> <tr> <th>Name</th> <th className="text-left">Do It?</th> <th>Actions</th> </tr> </thead> <tbody> { todoList.openTodos.map(todo => <tr key={`${todo.id}-${todo.text}`}> <TodoItem todo={todo} /> </tr>) } </tbody> </table> <h1>Finished Todos</h1> <table className="table"> <thead className="thead-light"> <tr> <th>Name</th> <th className="text-left">Do It?</th> <th>Actions</th> </tr> </thead> <tbody> { todoList.finishedTodos.map(todo => <tr key={`${todo.id}-${todo.text}`}> <TodoItem todo={todo} /> </tr>) } </tbody> </table> </div> )); };
import React, { useState } from 'react'; import TodoItemClass from "../stores/todo-item"; import { useStore } from '../providers/store-provider'; interface Props { todo: TodoItemClass; } export const TodoItem = ({ todo }: Props) => { const todoList = useStore(); const [newText, setText] = useState(''); const [isEditing, setEdit] = useState(false); const saveText = () => { todo.updateText(newText); setEdit(false); setText(''); }; return ( <React.Fragment> { isEditing ? <React.Fragment> <td> <input className="form-control" placeholder={todo.text} type="text" onChange={(e) => setText(e.target.value)} /> </td> <td></td> <td> <button className="btn btn-xs btn-success " onClick={saveText}>Save</button> </td> </React.Fragment> : <React.Fragment> <td> {todo.text} </td> <td className="text-left"> <input className="form-check-input" type="checkbox" onChange={todo.toggleIsDone} defaultChecked={todo.isDone}></input> </td> <td> <button className="btn btn-xs btn-warning " onClick={() => setEdit(true)}> <i className="fa fa-edit"></i> </button> <button className="btn btn-xs btn-danger ml-2" onClick={() => todoList.removeTodo(todo)}> <i className="fa fa-remove"></i> </button> </td> </React.Fragment> } </React.Fragment> ) };
20- :link
<style> :link { color: red; } </style> <a href="page1.html">Link 1</a> <a>Link 2</a> <a href="page2.html">Link 3</a>
|
|
|
|
| Selector | نسخه CSS |
3.1 | 9.6 | 7.0 | 2.0 | 4.0 | :link | 1 |
21- :visited
<style> :visited { color: green; } </style> <a href="page1.html">Link 1</a> <a>Link 2</a> <a href="page2.html">Link 3</a>
|
|
|
|
| Selector | نسخه CSS |
3.1 | 9.6 | 7.0 | 2.0 | 4.0 | :visited | 1 |
22- :focus
<style> :focus { background: yellow; } </style> <input type="text"/> <input type="password"/> <input type="text" />
|
|
|
|
| Selector | نسخه CSS |
3.1 | 9.6 | 8.0 | 2.0 | 4.0 | :focus | 2 |
23- :hover
<style> input:hover { background: yellow; } </style> <input type="text" /> <input type="password" /> <input type="text" />
|
|
|
|
| Selector | نسخه CSS |
3.1 | 9.6 | 7.0 | 2.0 | 4.0 | :hover | 1 |
24- :active
<style> button:active { background: yellow; } </style> <button>Button 1</button> <button>Button 2</button>
|
|
|
|
| Selector | نسخه CSS |
3.1 | 9.6 | 7.0 | 2.0 | 4.0 | :active | 1 |
25- :target
<style> :target { color: green; } </style> <h2><a href="#part1">Link 1</a></h2> <h2><a href="#part2">Link 2</a></h2> <p>This is a paragraph</p> <h1><a name="part1">Part 1</a></h1> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. </p> <h1><a name="part2">Part 2</a></h1> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. </p>
|
|
|
|
| Selector | نسخه CSS |
3.2 | 9.6 | 9.0 | 3.5 | 4.0 | :target | 3 |
26- :first-letter
<style> :first-letter { font-size: xx-large; } </style> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. </p>
|
|
|
|
| Selector | نسخه CSS |
1.0 | 7.0 | 9.0 | 1.0 | 1.0 | :first-letter | 1 |
27- :first-line
<style> :first-line { color: red; } </style> <p> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. </p>
|
|
|
|
| Selector | نسخه CSS |
1.0 | 7.0 | 9.0 | 1.0 | 1.0 | :first-letter | 1 |
28- :empty
<style> :empty { background: gray; } </style> <table border="1" cellpadding="10" cellspacing="10"> <tr> <td>A</td> <td>B</td> <td></td> </tr> <tr> <td>C</td> <td></td> <td>D</td> </tr> <tr> <td></td> <td>E</td> <td>F</td> </tr> </table>
|
|
|
|
| Selector | نسخه CSS |
3.2 | 9.6 | 9.0 | 3.5 | 4.0 | :target | 3 |
29- :dir(direction)
<style> :dir(rtl) { color: red; } </style> <div dir="rtl">متن 1</div> <div>متن 2</div>
|
|
|
|
| Selector | نسخه CSS |
No | No | No | No | No | :dir(direction) | 4 |
30- :lang(language1, language2,...)
<style> :lang(en) { color: red; } </style> <div lang="en">Text 1</div> <div>Text 2</div> <div lang="en">Text 3</div>
|
|
|
|
| Selector | نسخه CSS |
3.1 | 9.6 | 8.0 | 2.0 | 4.0 | :lang(language1) | 2 |
|
|
|
|
| Selector | نسخه CSS |
No | No | No | No | No | :lang(language1, language2,...) | 4 |
تا به اینجا مثالهایی که زدهایم تاثیر کامپوننتهای React را بر روی UI، نشان دادند. در این بخش به رویدادهای سمت UI و ورودیهای کاربر میپردازیم.
رویدادهای ترکیبی React
React روش مدیریت رویدادهای خودش را دارد و به آنها رویدادهای Synthetic یا ترکیبی گفته میشود. در زیر مقایسهای داریم از رویدادهای معمول در JavaScript و رویدادهای React و تفاوتها را بررسی میکنیم.
<!-- HTML Buttons --> <button type="button" onclick="console.log('Button Clicked')">Click Me</button> // React Buttons <button type="button" onClick={console.log("Button Clicked")}>Click Me</button>
- باید نام رویدادها را بصورت camelCase تایپ کنیم.
- از جاوااسکریپت به طور مستقیم استفاده میکنیم؛ نه بین quotation markها.
- برای رویدادها از توابع استفاده میکنیم و بهتر است تابع اجرایی هر رویداد در خود کامپوننت ساخته شود.
- رویداد onClick در React به نوعی override شده رویداد onclick مرورگر است و به جای آن عمل میکند.
رفتار رویدادهای React در مرورگرهای مختلف یکسان است. برای مثال رویداد onChange هر تغییری را برای هر نوع تگ ورودی اعمال میکند. هر کلیدی که در یک input یا textarea زده شود، اگر یک check box را انتخاب یا از انتخاب خارج کنیم و یا اگر موردی را از یک drop-down انتخاب کنیم، React رویداد onChange را اجرا میکند. React اکثر رویدادهای مرسوم را پوشش میدهد و همچنین رویدادهایی را برای کار با کلیپبرد، رسانههای مختلف و تصاویر دارد. برای اطلاعات بیشتر به مستندات آن رجوع کنید.
وقتی با کتابخانه React کار میکنیم، همه چیز مجازی اتفاق میافتد؛ مانند ساخت تگ و نمایش آنها، همچنین مدیریت تگها و رویدادها. اما به این معنا نیست که ارتباط React با HTML DOM در مرورگر قطع است. اگر لازم باشد به HTML DOM در کامپوننتها دسترسی داشته باشیم میتوانیم از خاصیت ref در React استفاده کنیم. برای مثال فرض کنید یک ورودی را برای ایمیل بهصورت <input type="email" /> تعریف کردهایم. میخواهیم پیش از ذخیره بدانیم آیا داده وارد شده به فرمت ایمیل هست یا نه.
const EmailForm = React.createClass({ clickHandler() { if (this.inputEmail.checkValidity()) console.log("Email is OK to save it."); else console.log("Email is not in right format."); }, render() { return ( <div> <input type="email" ref={inputEmail => this.inputEmail = inputEmail} /> <button type="submit" onClick={this.clickHandler}>Save</button> </div> ) }
در مثال بالا clickHandler وظیفه مدیریت رویداد کلیک دکمه را به عهده دارد. در ادامه، وقتی از خاصیت ref در تگ input استفاده میکنیم و مقدار آن را یک تابع قرار میدهیم، React این تابع را زمانیکه کامپوننت به طور کامل در HTML DOM ساخته شد، اجرا میکند. React همچنین ارجاعی را به عنوان پارامتر این تابع به DOM همراه با تابع ارسال میکند (inputEmail). داخل تابع ref میتوانیم به نمونه ساخته شده از کامپوننت در DOM دسترسی داشته باشیم. inputEmail که به صورت ارجاع به تابع فرستاده شده، تگ ساخته شده input را برمیگرداند، در نتیجه میتوانیم در کامپوننت به آن دسترسی داشته باشیم.
تغییر وضعیت کامپوننت
اگر از کامپوننتهای Sateful که دارای وضعیت هستند استفاده میکنیم، میتوانیم وضعیت کامپوننت را بر اساس ورودیهای کاربر تغییر دهیم. مثال بالا را به این شکل تغییر میدهیم که در ابتدا وضعیت کامپوننت، یک ایمیل پیشفرض باشد و اگر کاربر آدرس متفاوتی را وارد کرد، آدرس جدید به عنوان وضعیت جدید کامپوننت در نظر گرفته شود.
const EmailForm = React.createClass({ getInitialState() { return { currentEmail: this.props.currentEmail } }, setCurrentEmailState(se) { this.setState({ currentEmail: se.target.value }); }, clickHandler() { if (this.inputEmail.checkValidity()) console.log("Email is OK to save it."); else console.log("Email is not in right format."); }, render() { return ( <div> <input type="email" ref={inputEmail => this.inputEmail = inputEmail} value={this.state.currentEmail} onChange={this.setCurrentEmailState} /> <button type="submit" onClick={this.clickHandler}>Save</button> </div> ) } })
در خط 20 از مثال بالا با قرار دادن مقدار value برابر با ایمیل جاری (وضعیت کامپوننت)، کاربر آدرس پیشفرض را در input میبیند، اما هیچ تغییری را نمیتواند در آن ایجاد کند و input عملا تبدیل به یک تگ فقط خواندنی میشود. علت این است که React دو وضعیت را ایجاد کرده، یکی در حافظه به عنوان وضعیت پیشفرض و دیگری وضعیتی که در DOM ساخته. وقتی در سطح DOM تغییری را ایجاد میکنیم، React به صورت خودکار متوجه آن نمیشود و ما باید با روشی React را در جریان این تغییرات قرار دهیم! برای این کار رویداد onChange را برای تگی که قرار است تغییر کند پیادهسازی میکنیم. در مثال بالا متد setCurrentEmailState و رویداد onChange برای همین منظور به کار گرفته شدهاند.
در قسمت بعد که آخرین قسمت است، به مسئله چرخه زندگی (Lifecycle) کامپوننتهای React میپردازیم.
انواع رخدادها: بومی و سفارشی
دو رده بندی عمومی رخدادها در مرورگرها وجود دارند: بومی و سفارشی.
بومیها همانهایی هستند که در مستندات رسمی استانداردهای وب ذکر شدهاند؛ مانند click که توسط ماوس و یا صفحه کلید فعال میشود و یا load که در زمان بارگذاری کامل صفحه، تصاویر و یا یک iframe رخ میدهد.
رخدادهای سفارشی مواردی هستند که توسط یک کتابخانهی خاص و یا جهت یک برنامهی خاص تهیه شدهاند. مانند یک رخداد سفارشی که زمان شروع آپلود یک فایل را اعلام میکند.
رخدادهای سفارشی که بدون jQuery ایجاد و رخمیدهند، توسط jQuery نیز قابل بررسی و مدیریت هستند و نه برعکس. به عبارتی رخدادهای سفارشی ایجاد شدهی توسط jQuery غیراستاندارد بوده و صرفا مختص به API آن هستند.
در این بین، شیء استاندارد Event کار اتصال رخدادهای سفارشی و استاندارد را انجام میدهد. هر نوع رخداد DOM (سفارشی و یا بومی)، توسط یک شیء Event بیان میشود که آن نیز به همراه تعدادی خاصیت و متد، جهت مدیریت این رخداد است. برای مثال رخداد click دارای خاصیت type ایی به نام click است که در شیء Event متناظر با آن تعریف شدهاست.
انتشار رخدادها در صفحه
در روزهای آغازین وب، Netscape روش event capturing را برای انتشار رخدادها در صفحه ارائه داد و در مقابل آن IE روش event bubbling را معرفی کرد که متضاد یکدیگر بودند. در سال 2000 با ارائه استاندارد DOM Level 2 Events Specification، این وضعیت تغییر کرد و شامل هر دو مورد event capturing و event bubbling است و در حال حاضر تمام مرورگرهای مدرن این استاندارد را پیاده سازی کردهاند. بر اساس این استاندارد، زمانیکه رویدادی خلق میشود، فاز capturing آغاز میگردد که از شیء window شروع، سپس به شیء document منتشر میشود و این روند تا رسیدن به المانی که سبب بروز رخداد شدهاست ادامه پیدا میکند. پس از پایان فاز capturing، فاز جدید bubbling شروع میشود. در این فاز، رخداد از تمام والدین شیء هدف عبور میکند تا به شیء window برسد.
برای مثال اگر سند HTML ما چنین تعریفی را داشته باشد و بر روی المان «child of child of one» کلیک شده باشد:
<!DOCTYPE html> <html> <head> <title>event propagation demo</title> </head> <body> <section> <h1>nested divs</h1> <div>one <div>child of one <div>child of child of one</div> </div> </div> </section> </body> </html>
1.window 2.document 3.<html> 4.<body> 5.<section> 6.<div>one 7.<div>child of one 8.<div>child of child of one
9.<div>child of child of one 10.<div>child of one 11.<div>one 12.<section> 13.<body> 14.<html> 15.document 16.window
البته باید درنظر داشت که jQuery از روش ارائه شدهی توسط مرورگر برای فاز Bubbling استفاده نمیکند و این مسیر را خودش مجددا محاسبه و رخدادگردانهای این مسیر را به صورت دستی اجرا میکند. به همین جهت کارآیی آن نسبت به روش توکار و بومی مرورگرها کمتر است.
ایجاد رخدادهای DOM و صدور آنها در jQuery
برای نمایش ایجاد و صدور رخدادهای DOM با و بدون jQuery، از قطعه کد HTML زیر استفاده میکنیم:
<div> <button type="button">do something</button> </div> <form method="POST" action="/user"> <label>Enter user name: <input name="user"> </label> <button type="submit">submit</button> </form>
// submits the form $('FORM').trigger('submit'); // submits the form by clicking the button $('BUTTON[type="submit"]').trigger('click'); // focuses the text input $('INPUT').trigger('focus'); // removes focus from the text input $('INPUT').trigger('blur');
هرچند روش دومی نیز در jQuery API برای انجام همینکارها نیز پیش بینی شدهاست:
// submits the form $('FORM').submit(); // submits the form by clicking the button $('BUTTON[type="submit"]').click(); // focuses the text input $('INPUT').focus(); // removes focus from the text input $('INPUT').blur();
در ادامه فرض کنید یک دکمه داخل یک div قرار گرفتهاست و آن div نیز به همراه یک مدیریت کنندهی رخداد کلیک است. در این حالت اگر بخواهیم با کلیک بر روی دکمه سبب اجرای رویدادگردان div والد نشویم، میتوان از متد triggerHandler استفاده کرد:
// clicks the first button - the click event does not bubble $('BUTTON[type="button"]').triggerHandler('click');
ایجاد رخدادهای DOM و صدور آنها در جاوا اسکریپت (بدون استفاده از jQuery)
در web API مرورگرها، برای انجام بروز رخدادهای معادل مثالی که با jQuery مطرح شد، میتوان متدهای بومی متناظر با این رخدادها را بر روی المانها فراخوانی کرد:
// submits the form document.querySelector('FORM').submit(); // submits the form by clicking the button document.querySelector('BUTTON[type="submit"]').click(); // focuses the text input document.querySelector('INPUT').focus(); // removes focus from the text input document.querySelector('INPUT').blur();
متدهای توکار و بومی click ،focus و blur بر روی تمام عناصر DOM که از اینترفیس HTMLElement مشتق شده باشند، وجود دارند. متد submit فقط بر روی المانهایی از نوع <form> وجود دارد و قابل فراخوانی است.
باید دقت داشت که فراخوانی متدهای click و submit از نوع bubbling است؛ اما متدهای focus و blur خیر. از این جهت که این دو رخداد فاز capturing را سبب میشوند.
متدهای یاد شده را توسط سازندهی شیء Event و یا متد createEvent شیء document نیز میتوان ایجاد کرد. یکی از کاربردهای آن، ارائهی رفتاری سفارشی مانند triggerHandler جیکوئری است:
var clickEvent; if (typeof Event === 'function') { clickEvent = new Event('click', {bubbles: false}); } else { clickEvent = document.createEvent('Event'); clickEvent.initEvent('click', false, true); } document.querySelector('BUTTON[type="button"]').dispatchEvent(clickEvent);
ایجاد و صدور رخدادهای سفارشی
فرض کنید در حال تهیهی کتابخانهای هستیم که افزودن و حذف آیتمها را به یک گالری عکس ارائه میدهد. میخواهیم روشی را در اختیار مصرف کننده قرار دهیم تا بتواند به این رخدادهای سفارشی (غیر استانداردی که جزو W3C نیستند) گوش فرا دهد.
در جیکوئری برای ایجاد رخدادهای سفارشی به صورت زیر عمل میشود:
// Triggers a custom "image-removed" element, // which bubbles up to ancestor elements. $libraryElement.trigger('image-removed', {id: 1});
در خارج از جیکوئری و توسط web API استاندارد مرورگرها ایجاد و صدور رخدادهای سفارشی به همراه bubbling آن به صورت زیر است:
var event = new CustomEvent('image-removed', { bubbles: true, detail: {id: 1} }); libraryElement.dispatchEvent(event);
var event = document.createEvent('CustomEvent'); event.initCustomEvent('image-removed', false, true, {id: 1}); libraryElement.dispatchEvent(event);
var event; // If the `CustomEvent` constructor function is not supported, // fall back to `createEvent` method. if (typeof CustomEvent === 'function') { event = new CustomEvent('image-removed', { bubbles: true, detail: {id: 1} }); } else { event = document.createEvent('CustomEvent'); event.initCustomEvent('image-removed', false, true, { id: 1 }); } libraryElement.dispatchEvent(event);
گوش فرادادن به رخدادهای صادر شده، توسط jQuery
در جیکوئری با استفاده از متد on آن میتوان به تمام رخدادهای استاندارد و همچنین سفارشی گوش فرا داد:
$(window).on('resize', function() { // react to new window size });
// remove all resize listeners - usually a bad idea $(window).off('resize');
روش بهتر انجام اینکار، ذخیرهی ارجاعی به متدی است که قرار است این رویداد گردانی را انجام دهد:
var resizeHandler = function() { // react to new window size }; $(window).on('resize', resizeHandler); // ...later // remove only our resize handler $(window).off('resize', resizeHandler);
همچنین اگر یک گوش فراهندهی به رخدادی تنها قرار است یکبار در طول عمر برنامه اجرا شود، میتوان از متد one استفاده کرد:
$(someElement).one('click', function() { // handle click event });
گوش فرادادن به رخدادهای صادر شده، توسط جاوا اسکریپت خالص (یا همان web API مرورگرها)
ابتداییترین روش گوش فرادادن به رخدادها که از زمان آغاز معرفی آنها در دسترس بودهاست، روش تعریف inline آنها است:
<button onclick="handleButtonClick()">click me</button>
روش دیگر ثبت رویدادگردان click، انتساب متد آن به خاصیت رخداد متناظری در آن المان ویژه است:
buttonEl.onclick = function() { // handle button click };
البته باید دقت داشت که یکی از دو روش یاد شده را میتوانید استفاده کنید. در اینجا آخرین رویدادگردان متصل شدهی به المان، همواره تمام نمونههای موجود دیگر را بازنویسی میکند.
اگر نیاز به معرفی رویدادگردانهای متعددی برای یک المان در ماژولهای مختلف برنامه وجود داشت، از زمان IE 9.0 به بعد، متد addEventListener برای این منظور تدارک دیده شدهاست و syntax آن بسیار شبیه به متد on جیکوئری است:
buttonEl.addEventListener('click', function() { // handle button click });
برای نمونه معادل قطعه کد جیکوئری که پیشتر با متد on نوشتیم، با جاوا اسکریپت خالص به صورت زیر است:
window.addEventListener('resize', function() { // react to new window size });
var resizeHandler = function() { // react to new window size }; window.addEventListener('resize', resizeHandler); // ...later // remove only our resize handler window.removeEventListener('resize', resizeHandler);
در اینجا حتی امکان تعریف متد one جیکوئری نیز پیش بینی شدهاست (البته جزو استانداردهای جدید وب از سال 2016 است):
someElement.addEventListener('click', function(event) { // handle click event }, { once: true });
var clickHandler = function() { // handle click event // ...then unregister handler someElement.removeEventListener('click', clickHandler); }; someElement.addEventListener('click', clickHandler);
کنترل انتشار رخدادها
فرض کنید میخواهیم جلوی انتخاب المانهای صفحه مانند تصاویر و متن را توسط ماوس بگیریم. روش انجام اینکار با jQuery به صورت زیر است:
$(window).on('mousedown', function(event) { event.preventDefault(); });
window.addEventListener('mousedown', function(event) { event.preventDefault(); });
برای جلوگیری کردن از انتشار رخدادی مانند click جهت رسیدن به سایر رویدادگردانهای ثبت شدهی در بین راه فاز bubbling، میتوان از متد stopPropagation استفاده کرد. روش انجام اینکار در جیکوئری:
$someElement.on('click', function(event) { event.stopPropagation(); });
و با web Api جهت جلوگیری از انتشار رخدادها در فاز capturing (این تنها راه مدیریت فاز capturing است):
// stop propagation during capturing phase someElement.addEventListener('click', function(event) { event.stopPropagation(); }, true);
// stop propagation during bubbling phase someElement.addEventListener('click', function(event) { event.stopPropagation(); });
$someElement.on('click', function(event) { event.stopImmediatePropagation(); });
someElement.addEventListener('click', function(event) { event.stopImmediatePropagation(); });
یک نکته: در این حالت اگر متد رویدادگردانی مقدار false را برگرداند، به معنای فراخوانی هر دوی متد preventDefault و stopPropagation است.
ارسال اطلاعات به رویدادگردانها
روش ارسال اطلاعات اضافی به رویداد گردانها در جیکوئری به صورت زیر است:
$uploaderElement.trigger('uploadError', { filename: 'picture.jpeg' });
$uploaderParent.on('uploadError', function(event, data) { showAlert('Failed to upload ' + data.filename); });
روش انجام اینکار با web API مرورگرها به صورت زیر است:
// send the failed filename w/ an error event var event = new CustomEvent('uploadError', { bubbles: true, detail: {filename: 'picture.jpeg'} }); uploaderElement.dispatchEvent(event); // ...and this is a listener for the event uploaderParent.addEventListener('uploadError', function(event) { showAlert('Failed to upload ' + event.detail.filename); });
و اگر میخواهید از IE هم پشتیبانی کنید، روش جایگزین کردن شیء CustomEvent با createEvent به صورت زیر است:
// send the failed filename w/ an error event var event = document.createEvent('CustomEvent'); event.initCustomEvent('uploadError', true, true, { filename: 'picture.jpeg' }); uploaderElement.dispatchEvent(event); // ...and this is a listener for the event uploaderParent.addEventListener('uploadError', function(event) { showAlert('Failed to upload ' + event.detail.filename); });
متوجه شدن زمان بارگذاری یک شیء در صفحه
در حین توسعهی برنامههای وب، با این نوع سؤالات زیاد مواجه خواهید شد: چه زمانی تمام و یا بعضی از المانهای صفحه کاملا بارگذاری و رندر شدهاند؟
پاسخ به این نوع سؤالات در W3C UI Events specification توسط رویداد استاندارد load داده شدهاست.
- چه زمانی تمام المانهای موجود در صفحه کاملا بارگذاری و رندر شده و همچنین شیوهنامههای تعریف شده نیز به آنها اعمال گردیدهاند؟
روش انجام اینکار با jQuery:
$(window).on('load', function() { // page is fully rendered });
window.addEventListener('load', function() { // page is fully rendered });
- چه زمانی markup استاتیک صفحهی جاری در جای خود قرار گرفتهاند؟
اهمیت این موضوع، به دسترسی به زمان مناسب و امن ایجاد تغییرات در DOM بر میگردد. برای این منظور رویداد استاندارد DOMContentLoaded پیشبینی شدهاست که زودتر از رویداد load، در دسترس برنامه نویس قرار میگیرد. در جیکوئری توسط یکی از دو روش معروف زیر به رویداد یاد شده دسترسی خواهید داشت:
$(document).ready(function() { // markup is on the page }); //or $(function() { // markup is on the page });
document.addEventListener('DOMContentLoaded', function() { // markup is on the page });
یک نکته: بهتر است این تعریف web API را پیش از تگهای <link> قرار دهید. زیرا بارگذاری آنها، اجرای هر نوع اسکریپتی را تا زمان پایان عملیات، سد میکند.
- چه زمانی المانی خاص در صفحه بارگذاری شدهاست و چه زمانی بارگذاری یک المان با شکست مواجه شدهاست؟
در جیکوئری توسط بررسی رویدادهای load و error میتوان به وضعیت نهایی بارگذاری المانهایی خاص دسترسی یافت:
$('IMG').on('load', function() { // image has successfully loaded }); $('IMG').on('error', function() { // image has failed to load });
document.querySelector('IMG').addEventListener('load', function() { // image has successfully loaded }); document.querySelector('IMG').addEventListener('error', function() { // image has failed to load });
- جلوگیری از ترک اتفاقی صفحهی جاری
گاهی از اوقات نیاز است برای از جلوگیری از تخریب صفحهی جاری و از دست رفتن اطلاعات ذخیره نشدهی کاربر، اگر بر روی دکمهی close بالای صفحه کلیک کرد و یا کاربر به اشتباه به صفحهی دیگری هدایت شد، جلوی اینکار را بگیریم. برای این منظور رخداد استاندارد beforeunload درنظر گرفته شدهاست. روش استفادهی از این رویداد در جیکوئری:
$(window).on('beforeunload', function() { return 'Are you sure you want to unload the page?'; });
window.addEventListener('beforeunload', function(event) { var message = 'Are you sure you want to unload the page?'; event.returnValue = message; return message; });
- ساخت برنامههای دسکتاپ به صورت چندسکویی (ویندوز، لینوکس، مک)
- استفاده از HTML,CSS,JavaScript که طراحان وب در این زمینه با آن به آسانی ارتباط برقرار میکنند.
- قابلیت استفاده از کتابخانههای قدرتمند تحت وب چون Bootstrap,Jquery,Angular Js و ...
- متن باز و رایگان است.
D:\electron\test1>npm init
npm i electron-prebuilt --save-dev
لازم است در اینجا توضیحی کوتاه در مورد انواع وابستگیها داشته باشیم:
Dependencies این نوع از وابستگیها، بستههایی را نصب میکنند که شما از آنها در کدهایتان استفاده میکنید و در آینده به همراه پروژه کمپایل میشوند. به طور خودکار وقتی بستهای را به عنوان وابستگی معرفی میکنید، npm وابستههای آن بسته را به صورت درختی بررسی میکند و آنها را هم نصب میکند.
DevDependencies : این نوع از وابستگیهای برای کارهای دیباگینگ و ... است؛ مثل آزمون واحد و ... که نیازی نیست در کامپایل نهایی لحاظ گردند. اگر این نوع کتابخانهها را به جای devdependencies به dependencies ارسال کنید، اتفاق خاصی نمیافتد. ولی در حجم برنامهی نهایی شما تاثیرگذار خواهند بود.
PeerDependencies: این نوع وابستگیها برای معرفی بستههایی استفاده میشوند که در پلاگینهایی که استفاده میکنید تاثیر دارند. ممکن است پلاگینی نیاز به استفادهی از یک بسته را دارد، ولی آن را در کد، Require نکرده باشد (در مورد Require بعدا صحبت میکنیم). ولی برای اجرا نیاز به این بسته دارد. به همین دلیل از نسخهی 3 به بعد، به شما هشدار میدهد که این بستهها را نیز لحاظ کنید (تا نسخهی npm2 به طور خودکار نصب میشد). همچنین نسخه بندی این وابستگیها را نیز در نظر میگیرد. این حالت را میتوانید مانند پلاگینهای جیکوئری تصور کنید که نیاز است قبل از آنها، کتابخانهی جیکوئری صدا زده شود؛ در صورتی که در خود پلاگین، جی کوئری صدا زده نشده است.
ویرایشگر اتم
قبل از اینکه بخواهیم کدنویسی با هر زبانی را آغاز کنیم، عموما یک ادیتور مناسب را برای کارمان بر میگزینیم. الکترون نیازی به ادیتور خاصی ندارد و از Notepad گرفته تا هر ادیتور قدرتمند دیگری را میتوانید استفاده کنید. ولی ادیتور اتم Atom که توسط خود الکترون هم تولید شده است، برای استفاده رایج است. ویژوال استودیو هم در این زمینه بسیار خوب و قدرتمند ظاهر شده است و حاوی Intellisense هوشمندی است.
این ادیتور که با ظاهری جذاب، توسط تیم گیت هاب تولید شده است، یک ویرایشگر متن باز با قابلیت توسعه و تغییر پذیری بالاست و از بستههای Node.js پشتیانی میکند و به صورت داخلی مجهز به سیستم گیت میباشد. بیشتر فناوریهای استفاده شده در این ویرایشگر، رایگان بوده و دارای جامعهی بزرگ متن باز میباشند. از فناوریهای مورد استفادهی آن میتوان به الکترون، CoffeScript ، Node.js ,LESS و ... اشاره کرد. شعار سازندگان این ادیتور «یک ویرایشگر قابل هک برای قرن 21» میباشد.
برای پشتیبانی از زبانهای مختلف، حاوی تعدادی زیادی پلاگین پیش فرض است مانند روبی ، سی شارپ، PHP ,Git,Perl,C/C++, Go,Objective-C,YAML و ...
آغاز کدنویسی
بگذارید کدنویسی را شروع کنیم. اگر اتم را نصب کرده باشید، میتوانید با وارد کردن عبارت زیر، پروژه خود را در ادیتور باز کنید:
atom .
{ "name": "electron", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "electron ." }, "author": "", "license": "ISC" }
const electron = require('electron'); const {app} = electron; const {BrowserWindow} = electron;
let win; app.on('ready', function() { // Create the browser window. win = new BrowserWindow({ width: 800, height: 600 }); });
اکنون در کنسول مینویسیم:
npm start
برای اینکه اولین برنامه واقعا خالی نباشد و ظاهری به آن بدهیم، یک فایل html میسازیم و در callback رویداد ready، بعد از ساخت پنجره آن را صدا میزنیم:
win.loadURL(`file://${__dirname}/index.html`);
در این مثال برای نمایش پیام به صورت notification، از کتابخانه toastr استفاده میکنیم که از طریق nuget میتوانید آن را به پروژه اضافه کنید:
PM> Install-Package toastr
toastr.info("نمایش یک پیام - info"); toastr.success("نمایش یک پیام - success"); toastr.error("نمایش یک پیام - error"); toastr.warning("نمایش یک پیام - warning");
toastr.success("نمایش یک پیام - success", "عنوان");
toastr.options = { tapToDismiss: true, toastClass: 'toast', containerId: 'toast-container', debug: false, showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery showDuration: 300, showEasing: 'swing', //swing and linear are built into jQuery onShown: undefined, hideMethod: 'fadeOut', hideDuration: 1000, hideEasing: 'swing', onHidden: undefined, extendedTimeOut: 1000, iconClasses: { error: 'toast-error', info: 'toast-info', success: 'toast-success', warning: 'toast-warning' }, iconClass: 'toast-info', positionClass: 'toast-top-right', timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky titleClass: 'toast-title', messageClass: 'toast-message', target: 'body', closeHtml: '<button>×</button>', newestOnTop: true, preventDuplicates: false, progressBar: false };
public class NotificationHub : Hub { private readonly IProductService _productService; public NotificationHub(IProductService productService) { _productService = productService; } public void SendNotification() { Clients.Others.ShowNotification(_productService.GetLastProduct()); } }
var notify = $.connection.notificationHub; notify.client.showNotification = function (data) { toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید"); }; $.connection.hub.start().done(function () { @{ if (ViewBag.NotifyUsers) { <text>notify.server.sendNotification();</text> } } });
var positionClasses = { topRight: 'toast-top-right', bottomRight: 'toast-bottom-right', bottomLeft: 'toast-bottom-left', topLeft: 'toast-top-left', topCenter: 'toast-top-center', bottomCenter: 'toast-bottom-center' }; var notify = $.connection.notificationHub; notify.client.showNotification = function (data) { toastr.options = { showDuration: 300, positionClass: positionClasses.bottomRight, onclick: function () { $('#table tr:last').after("<tr>" + "<td>" + data.Title + "</td>" + "<td>" + data.Description + "</td>" + "<td>" + data.Price + "</td>" + "<td>" + data.Category + "</td>" + "<td> </td>" + "</tr>"); } }; toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید"); }; $.connection.hub.start().done(function () { @{ if (ViewBag.NotifyUsers) { <text>notify.server.sendNotification();</text> } } });
onclick: function () { $('#table tr:last').after("<tr>" + "<td>" + data.Title + "</td>" + "<td>" + data.Description + "</td>" + "<td>" + data.Price + "</td>" + "<td>" + data.Category + "</td>" + "<td> </td>" + "</tr>"); }
data {Id: 12, Title: "Item1", Description: "Des", Price: 100000, Category: 0}
export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
برای رفع این مشکل، میتوان رخداد کلیک ماوس بر روی یک دکمه را از نوع بسیار عمومی React.MouseEvent تعریف کرد:
import React from "react"; type Props = { onClick: (e: React.MouseEvent) => void; }; export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} />
زمانیکه نوع یک رویداد به صورت کلی e: React.MouseEvent تعریف میشود، اگر به تصویر فوق دقت کنیم، تایپاسکریپت آنرا قابل اعمال به هر نوع Element ای میداند. اما اگر بخواهیم دامنهی دید آنرا صرفا به المان استاندارد button تعریف شده محدود کنیم، اشارهگر ماوس را در فایل src\components\Button.tsx بر روی رویداد onClick المان button تعریف شده قرار میدهیم:
همانطور که مشخص است، نوع این المان را به صورت HTMLButtonElement ذکر کردهاست. بنابراین میتوان تعریف کلی قبلی را به صورت زیر محدود کرد:
import React from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; }; export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
تعیین نوع رخداد onChange
در ادامه فرض کنید میخواهیم اطلاعات رویداد onChange را نیز صادر کنیم. روش عمومی تشخیص نوع آن، قرار دادن اشارهگر ماوس بر روی رویداد مدنظر و سپس استفاده از همان نوعی است که نمایش میدهد؛ مانند تصویر زیر:
import React from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: React.FormEvent<HTMLInputElement>) => void; }; export const Button = ({ onClick, onChange }: Props) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>Click Me</button> </> ); };
سؤال: اطلاعات نمایش داده شدهی نوعهای متناظر با رویدادهای مختلف در تصاویر فوق، از کجا تامین میشوند؟
اگر به فایل package.json پروژهی تایپاسکریپتی ایجاد شده مراجعه کنید، میتوانید حداقل دو مدخل جدید زیر را نیز در آن مشاهده کنید:
{ "dependencies": { "@types/react": "^16.9.35", "@types/react-dom": "^16.9.8", },
سؤال: آیا بستههای @types دار موجود در فایل package.json، حجم فایلهای نهایی برنامه را افزایش میدهند؟ آیا برنامههای React مبتنی بر TypeScript، حجم بیشتری را نسبت به نمونههای ES6 آن دارند؟
اکثر این تعاریف @types به صورت اینترفیسهای تایپاسکریپتی تعریف شدهاند که صرفا در زمان کامپایل برنامه، کمک حال کامپایلر آن خواهند بود و پس از کامپایل، تبخیر شده و در کدهای نهایی تولیدی، حضور نخواهند داشت. به عبارتی استفادهی از تایپاسکریپت، حجم نهایی پروژهها را افزایش نمیدهد؛ چون اینترفیسهای تایپاسکریپت، معادلی را در جاوااسکریپت استاندارد ندارند و فقط به عنوان راهنمای کامپایلر تایپاسکریپت عمل میکنند.
یک نکته: تعریف رویدادهای مدنظر را میتوان در importها نیز قرار داد:
import React, { FormEvent } from "react"; type Props = { onChange?: (e: FormEvent<HTMLInputElement>) => void; };
تعریف نوع Children Props
زمانیکه داخل تعریف المان یک کامپوننت، کامپوننت دیگری ذکر میشود، به عنوان فرزند او در React درنظر گرفته میشود. برای نمونه تعریف کامپوننت Button را در فایل src\App.tsx به صورت زیر تغییر میدهیم تا فرزندی را به صورت «Hello world» بپذیرد:
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} > Hello world! </Button>
با توجه به اینکه این فرزند، از نوع رشتهای است، فقط کافی است خاصیت children را با همین نوع، به Props اضافه کرده و از آن استفاده کنیم:
import React, { FormEvent } from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: FormEvent<HTMLInputElement>) => void; children: string; }; export const Button = ({ onClick, onChange, children }: Props) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>{children}</button> </> ); };
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} > <img src={logo} className="App-logo" alt="logo" /> </Button>
عنوان میکند که با این تغییر، نوع children به Element تغییر یافتهاست؛ اما در تعریف Props آن، به صورت رشتهای معرفی شدهاست. در این حالت اگر به تعریف Props مراجعه کنیم و نوع string را به Element تغییر دهیم، باز هم این خطا برطرف نمیشود. حتی اگر children: HTMLImageElement را نیز اضافه کنیم، باز هم این خطا تغییری نمیکند.
روش صحیح حل این مشکل را در ادامه مشاهده میکنید:
import React, { FormEvent } from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: FormEvent<HTMLInputElement>) => void; // children: HTMLImageElement; }; export const Button: React.FC<Props> = ({ onClick, onChange, children }) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>{children}</button> </> ); };
- سپس ذکر نوع Props را هم از Object Destructuring صورت گرفته، حذف میکنیم.
- در آخر، نوع کامپوننت جاری را به صورت <React.FC<Props معرفی میکنیم. در اینجا FC یعنی Functional Component و با تعریف آن، میتوان نوع props را به صورت آرگومان جنریک آن مشخص کرد. پس از آن دیگر نیازی به ذکر خاصیت children، در Props نیست؛ چون این خاصیت جزئی از React.FC میباشد و به صورت خودکار شناسایی میشود:
بنابراین در حالت کلی اگر از خاصیت children استفاده نمیکنید، از همان syntax قبلی که به صورت export const Button = ({ onClick, onChange }: Props) است، استفاده کنید؛ در غیراینصورت استفادهی از نوع <React.FC<T، ضروری خواهد بود.