جاسازی داده ها در عناصر React/JSX
آنچه من می خواهم به شما نشان دهم واقعاً بسیار اساسی است. بنابراین کدگوروهای خارج از کشور می توانند با خیال راحت به این مقاله ادامه دهند. اما من به ندرت از این تکنیک استفاده کرده ام، حتی در پایگاه های کد “تثبیت شده” که توسط توسعه دهندگان ارشد ساخته شده اند. بنابراین تصمیم گرفتم این را بنویسم.
این تکنیک برای استخراج بیت هایی از داده ها طراحی شده است که در یک عنصر JSX (یا… HTML ساده) جاسازی شده اند. چرا باید این کار را انجام دهید؟ خب… خوشحالم که پرسیدی.
سناریو
بیایید به یک تابع واقعاً اساسی نگاه کنیم:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td key={`cell-${rowIndex}-${cellIndex}`}>
{paintIndex}
</td>
);
})
}
این تابع به سادگی یک ردیف خاص از سلول های جدول می سازد. در برنامه https://paintmap.studio من برای ساختن یک “نقشه رنگی” استفاده می شود. این یک شبکه (جدول) غول پیکر ایجاد می کند که به من نشان می دهد، برای هر بلوک در شبکه، کدام یک از رنگ های من بیشتر با آن بلوک خاص مطابقت دارد.
هنگامی که این ویژگی ساخته شد، تصمیم گرفتم که می خواهم یک ویژگی اضافه کنم onClick
رویداد برای هر سلول ایده این است که، وقتی روی هر سلولی کلیک میکنید، هر سلولی را در شبکه که رنگی مشابه آن چیزی که روی آن کلیک کردهاید، برجسته میکند.
هر زمان که روی یک سلول کلیک کنید، onClick
مدیریت رویداد باید درک کند که رنگی که انتخاب کرده اید به عبارت دیگر، شما نیاز دارید عبور رنگ به را onClick
کنترل کننده رویداد بارها و بارها، من کدی را می بینم که به نظر می رسد برای انجام چنین عملکردی:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
key={`cell-${rowIndex}-${cellIndex}`}
onClick={paintIndex => handleCellClick(paintIndex)}
>
{paintIndex}
</td>
);
})
}
کد بالا “اشتباه” نیست. عبور خواهد کرد paintIndex
به مسئول رویداد اما واقعا اینطور نیست… بهینه. ناکارآمدی که به وجود می آید این است که برای هر سلول جدول، یک تعریف تابع کاملاً جدید ایجاد می کنیم. این چیزی است که paintIndex => handleCellClick(paintIndex)
میکند. این یک عملکرد کاملاً جدید را می چرخاند. اگر میز بزرگی دارید، این تعاریف تابع لوتا است. و این توابع نه فقط در رندر اولیه کامپوننت، بلکه باید دوباره تعریف شوند هر زمان که این جزء دوباره فراخوانی شود.
در حالت ایده آل، شما باید یک ایستا تعریف تابع برای کنترل کننده رویداد. که چیزی شبیه به این خواهد بود:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
در کد بالا، handleCellClick
قبلاً خارج از این تابع تعریف شده است. بنابراین React نیازی به بازسازی یک تعریف تابع کاملاً جدید در هر بار که یک سلول جدول را ارائه می کنیم، ندارد. متأسفانه، این نیز کاملاً کار نمی کند. زیرا اکنون، هر بار که کاربر روی یک سلول جدول کلیک می کند، کنترل کننده رویداد هیچ ایده ای نخواهد داشت خاص سلول کلیک شد. بنابراین آن را نمی داند که paintIndex
برجسته کردن
باز هم به روش من به طور معمول مشاهده این پیاده سازی، حتی در پایگاه های کد به خوبی ساخته شده، استفاده از paintIndex => handleCellClick(paintIndex)
رویکرد. اما همانطور که قبلاً اشاره کردم، این ناکارآمد است.
پس بیایید به چند راه برای رفع این مشکل نگاه کنیم.
اجزای لفاف
یک روش این است که یک جزء بسته بندی برای سلول های جدول من ایجاد کنم. که شبیه این خواهد بود:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<MyTableCell
key={`cell-${rowIndex}-${cellIndex}`}
paintIndex={paintIndex}
>
{paintIndex}
</MyTableCell>
);
})
}
در این سناریو، ما دیگر از ویژگی پایه HTML استفاده نمی کنیم <td>
. در عوض، یک جزء سفارشی وجود دارد که می پذیرد paintIndex
به عنوان یک تکیه گاه، و سپس احتمالاً از آن پایه برای ساخت کنترلر رویداد استفاده کنید. MyTableCell
چیزی شبیه به این خواهد بود:
const MyTableCell = paintIndex => {
const handleCellClick = () => {
// event handler logic using paintIndex
}
return (
<td onClick={handleCellClick}>
{paintIndex}
</td>
)
}
با استفاده از این رویکرد، ما مجبور نیستیم از آن عبور کنیم paintIndex
ارزش به درون handleCellClick
کنترل کننده رویداد، زیرا ما به سادگی می توانیم آن را از پایه رجوع کنیم. در این راه حل چیزهای زیادی برای دوست داشتن وجود دارد زیرا با رویکرد اصطلاحی React سازگار است. در حالت ایده آل، شما حتی می خواهید حفظ کردن را MyTableCell
کامپوننت تا دوباره سوار نشود (و دوباره ارائه شده است) هر بار که سلول جدولی می سازیم که از همان رنگ رنگ استفاده می کند.
با این حال، این رویکرد همچنین می تواند کمی سخت به نظر برسد، زیرا ما جزء دیگری را صرفاً به خاطر ساختن آن استفاده می کنیم. onClick
رویداد کارآمدتر همچنین، اگر handleCellClick
کنترل کننده رویداد باید منطق دیگری را انجام دهد که بر آن حالت تأثیر می گذارد در مولفه فراخوان، کد حاصل می تواند کمی “سنگین” شود.
گاهی اوقات شما می خواهید منطق مربوط به کنترل کننده رویداد درست در داخل مولفه فراخوانی مدیریت شود. خوشبختانه راه های دیگری نیز برای این کار وجود دارد.
ویژگی های HTML
HTML به ما آزادی زیادی میدهد تا دادهها را در جایی که مورد نیاز است، قرار دهیم. به عنوان مثال، شما می توانید استفاده کنید longdesc
ویژگی برای جاسازی paintIndex
مستقیماً به خود عنصر HTML. متاسفانه، longdesc
فقط “مجاز” است <frame>
، <iframe>
، و <img>
عناصر.
مسلماً، مرورگرها در مورد استفاده از ویژگی های HTML بسیار بخشنده هستند. بنابراین اگر قرار بود شروع به گذاشتن کنید longdesc
ویژگی های همه انواع عناصر HTML “غیرقانونی”، واقعا اینطور نخواهد بود زنگ تفريح هر چیزی. مرورگر اساسا فقط چشم پوشی صفات غیر اصطلاحی در واقع، شما حتی می توانید خود را اضافه کنید سفارشی ویژگی های عناصر HTML
با این وجود، معمولاً تمرین خوبی است که از قرار دادن ویژگیهای غیرمجاز یا کاملاً سفارشی در عناصر HTML خودداری کنید. اما ما گزینه های بیشتری داریم. گزینه های “استاندارد” بیشتر.
(نزدیک) ویژگی های جهانی HTML
اگر میخواهید مشخصهای پیدا کنید که بتوانید تقریباً روی هر عنصری قرار دهید، اولین چیز این است که به ویژگیهایی که در (تقریبا) هر عنصر مجاز هستند نگاه کنید. آنها به شرح زیر است:
id
class
style
title
dir
lang
/xml:lang
نکته خوب در مورد این ویژگی ها این است که تقریباً می توانید از آنها در هر جایی از آن استفاده کنید بدن از HTML شما، تقریباً روی هر عنصر HTML، و شما لازم نیست نگران باشید که آیا آنها “مجاز” هستند یا خیر. برای مثال می توانید یک را قرار دهید title
صفت بر روی الف <div>
، یا الف dir
صفت بر روی الف <td>
. این همه “قابل قبول” است – با استانداردهای HTML، یعنی.
بنابراین اگر میخواهید از یکی از این ویژگیها برای “انتقال” دادهها به یک مدیریت رویداد استفاده کنید، بهترین انتخاب کدام خواهد بود؟
title
اول از همه، به همان اندازه که ممکن است استفاده از چیزی شبیه وسوسه انگیز باشد title
، من می خواهم نه این را توصیه کنید title
توسط صفحهخوانها استفاده میشود و اگر مجموعهای از دادههای برنامهنویسی را در آن ویژگی قرار دهید، دسترسی به سایت خود را افزایش میدهید – دادههایی که باید نه توسط یک صفحه خوان خوانده شود.
dir
، lang
، xml:lang
به طور مشابه، شما باید از تصاحب آن اجتناب کنید dir
، lang
، یا xml:lang
ویژگی های. استفاده از این ویژگی ها می تواند ابزار سایت را برای کاربران بین المللی (یعنی کسانی که از سایت شما استفاده می کنند) افزایش دهد. با زبانی متفاوت). پس لطفا آنها را هم تنها بگذارید.
style
همچنین، می توانید سعی کنید داده های “سفارشی” را در یک جمع کنید style
صفت. اما IMHO، به نظر پیچیده به نظر می رسد. در تئوری، می توانید یک ویژگی سبک سفارشی را مانند این تعریف کنید:
<td style={{paintIndex: `${paintIndex}`}}>
سپس می توانید سعی کنید این ویژگی سبک ساخته شده را در زمانی که عنصر در کنترل کننده رویداد ثبت می شود، بخوانید. اما… من چنین رویکردی را توصیه نمی کنم. اصلا. اولا اگر دارید مشروع style
ویژگی های تنظیم شده بر روی عنصر، شما در نهایت با یک مخلوط از واقعی و ساخته شده. مصنوعی خواص دوم، هیچ دلیلی برای جاسازی داده ها در چنین قالب پرمخاطبی وجود ندارد. شما می توانید این کار را با گزینه های دیگر در اختیار ما بسیار “تمیزتر” انجام دهید.
id
این id
ویژگی می تواند مکانی عالی برای “جاسازی” داده ها باشد. در اینجا به نظر می رسد:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
id={paintIndex}
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
در اینجا، ما از paintIndex
به عنوان id
. چرا این کار را انجام دهیم؟ زیرا در این صورت میتوانیم یک مدیریت رویداد ایجاد کنیم که به شکل زیر باشد:
const handleCellClick = (event = {}) => {
uiState.toggleHighlightedColor(event.target.id);
}
این کار به این دلیل کار میکند که رویداد مصنوعی که به کنترل کننده رویداد منتقل میشود دارای این است id
عنصر کلیک شده که در آن جاسازی شده است. این به ما اجازه می دهد تا از یک کنترل کننده رویداد عمومی در هر سلول جدول استفاده کنیم، در حالی که به کنترل کننده رویداد اجازه می دهد دقیقاً درک کند. که paintIndex
روی آن کلیک شد.
این هنوز هم می تواند معایبی داشته باشد. اول از همه، id
s قرار است باشد منحصر بفرد. در مثال بالا، یک مورد داده شده است paintIndex
ممکن است در یک سلول جدول وجود داشته باشد – یا در صدها مورد. و اگر به سادگی از آن استفاده کنیم paintIndex
ارزش به عنوان id
، در نهایت به سلول های جدول زیادی خواهیم رسید که دارای یکسان هستند id
ارزش های. (برای واضح بودن، داشتن نسخه تکراری id
s HTML شما را خراب نمی کند نمایش دادن. اما در برخی سناریوها می تواند جاوا اسکریپت شما را خراب کند منطق.)
خوشبختانه، ما می توانیم آن را نیز برطرف کنیم. توجه داشته باشید که سلول های جدول ما دارند key
ارزش های. و کلیدها باید منحصر به فرد باشد در این سناریو، من این مشکل را با استفاده از تعداد ردیف/سلول برای ساخت کلید برطرف کردم. زیرا هیچ دو سلولی ترکیب یکسانی از شماره ردیف/سلول را نخواهد داشت. ما می توانیم همان قالب را به خود اضافه کنیم id
. به نظر می رسد این است:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
id={`cell-${rowIndex}-${cellIndex}-${paintIndex}`}
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
در حال حاضر، هرگز تکراری وجود نخواهد داشت id
روی هر یک از سلول های ما اما ما هنوز آن را داریم paintIndex
ارزش تعبیه شده به که id
. بنابراین چگونه میتوانیم مقدار را در مدیریت رویداد خود استخراج کنیم؟ به نظر می رسد این است:
const handleCellClick = (event = {}) => {
const paintIndex = event.target.id.split('-').pop();
uiState.toggleHighlightedColor(paintIndex);
}
از آنجایی که ما این کد را نوشتیم، و از زمانی که قرارداد نامگذاری را تعیین کردیم id
، ما همچنین می دانیم که paintIndex
value آخرین مقدار در رشته ای از مقادیر خواهد بود که با آن محدود شده اند -
. اگر ما split('-')
آن رشته و سپس pop()
آخرین مقدار از پایان آن، ما می دانیم که در حال دریافت آن هستیم paintIndex
ارزش.
class
class
همچنین یک مکان عالی برای “جاسازی” داده است – حتی اگر به هیچ کلاس CSS که در دسترس اسکریپت است نگاشت نشود. اگر با jQuery UI آشنا هستید، احتمالاً موارد زیادی را دیده اید class
به عنوان یک نوع “سوئیچ” استفاده می شود که در واقع سبک های CSS را هدایت نمی کند. در عوض، کد جاوا اسکریپت را می گوید چه باید کرد.
البته در JSX ما استفاده نمی کنیم class
. ما استفاده می کنیم className
. بنابراین آن راه حل به شکل زیر خواهد بود:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
className={`${paintIndex}`}
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
و کنترل کننده رویداد به شکل زیر است:
const handleCellClick = (event = {}) => {
uiState.toggleHighlightedColor(event.target.className);
}
درست همانطور که قبلا چنگ زدیم paintIndex
از شی رویداد id
میدان، ما اکنون آن را از آن می گیریم className
. اگر کلاسهای CSS “واقعی” را نیز در آن داشته باشید، چگونه کار میکند className
ویژگی؟ که شبیه این خواهد بود:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
className={`cell ${paintIndex}`}
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
و کنترل کننده رویداد به شکل زیر است:
const handleCellClick = (event = {}) => {
const paintIndex = event.target.className.split(' ').pop();
uiState.toggleHighlightedColor(paintIndex);
}
کلاس “سلول” در <td>
یک کلاس “واقعی” است – به این معنی که به ویژگی های CSS از پیش تعریف شده نگاشت می شود. اما ما آن را نیز تعبیه کردیم paintIndex
ارزش به className
ویژگی و ما آن را با تقسیم رشته در فضاهای خالی استخراج کردیم.
انصافاً، این رویکرد ممکن است کمی احساس شود… “شکننده”. چون بستگی به paintIndex
مقدار آخرین مقدار در فضای محدود شده است className
رشته اگر توسعه دهنده دیگری وارد شد و کلاس CSS دیگری را به انتهای آن اضافه کرد className
زمینه، مانند این:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
className={`cell ${paintIndex} anotherCSSClass`}
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
منطق خواهد بود زنگ تفريح. زیرا کنترل کننده رویداد میگیرد anotherCSSClass
از انتهای رشته خارج شوید – و سعی کنید با آن مانند آن رفتار کنید paintIndex
. اگر می خواهید آن را کمی قوی تر کنید، می توانید منطق را به چیزی شبیه به این تغییر دهید:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
className={`cell paintIndex-${paintIndex} anotherCSSClass`}
key={`cell-${rowIndex}-${cellIndex}`}
onClick={handleCellClick}
>
{paintIndex}
</td>
);
})
}
و سپس کنترل کننده رویداد را به صورت زیر به روز کنید:
const handleCellClick = (event = {}) => {
const paintIndex = event.target.className
.split(' ')
.find(className => className.includes('paintIndex-'))
.split('-')
.pop();
uiState.toggleHighlightedColor(paintIndex);
}
با انجام این کار، مقداری که برای آن استخراج شده است paintIndex
وابسته به بودن نیست آخر مورد در رشته با فاصله محدود. این می تواند در هر جایی در داخل وجود داشته باشد className
ویژگی، تا زمانی که با “paintIndex-” اضافه شده باشد.
چرا باید اهمیت دهید؟
صادقانه بگویم، در برنامه های کوچک، داشتن چنین چیزی دقیقاً یک جرم فدرال نیست:
const getTableCells = (cells = [rgbModel], rowIndex = -1) => {
return cells.map((cell, cellIndex) => {
const paintIndex = colors.findIndex(color => color.name === cell.name);
return (
<td
key={`cell-${rowIndex}-${cellIndex}`}
onClick={paintIndex => handleCellClick(paintIndex)}
>
{paintIndex}
</td>
);
})
}
عملکرد “ضربه” شما با تعریف یک تعریف تابع جدید در داخل onClick
دارایی … حداقل است. در برخی موارد، تلاش برای “رفع” آن می تواند به طور قابل درک به عنوان “بهینه سازی خرد” تعریف شود. اما من معتقدم که عادت کردن به اجتناب از آنها در هر زمان ممکن، تمرین محکمی است.
هنگامی که کنترل کننده رویداد نیازی به ارسال اطلاعات از عنصر کلیک شده به آن نداشته باشد، دور نگه داشتن توابع پیکان از ویژگی های رویداد شما کار بیهوده ای است. اما زمانی که آن را میکند به اطلاعات خاص عنصر نیاز دارد، اغلب من می بینم که افراد کورکورانه به روش آسان رها کردن توابع فلش در ویژگی های خود باز می خورند. اما راه های زیادی برای جلوگیری از این امر وجود دارد – و آنها به تلاش اضافی کمی نیاز دارند.