وارونگی کنترل
“اجرای وارونگی کنترل” را در egghead.io تماشا کنید
اگر تا به حال کدی ایجاد کرده اید که قبلاً در بیش از یک مکان استفاده شده است، پس
احتمالاً با این داستان آشنا هستید:
- شما یک بیت کد قابل استفاده مجدد می سازید (یک تابع، یک جزء React یا یک React hook،
و غیره) و آن را به اشتراک بگذارید (با همکاران یا انتشار آن به عنوان OSS). - شخصی با یک مورد استفاده جدید که کد شما ارائه نمی دهد به شما مراجعه می کند خیلی زیاد
تعمیر و نگهداری، اما می تواند با کمی تغییرات انجام شود. - شما یک آرگومان/نسبت/گزینه به کد قابل استفاده مجدد و منطق مرتبط خود اضافه می کنید
برای حمایت از این مورد استفاده - مراحل 2 و 3 را چند بار (یا چند بار 😬) تکرار کنید.
- کد قابل استفاده مجدد اکنون برای استفاده و نگهداری یک کابوس است 😭
و دقیقاً چه چیزی باعث میشود که استفاده و نگهداری کد به کابوس تبدیل شود؟
چندین مورد وجود دارد که می تواند مشکل ساز باشد:
- 😵 اندازه بسته بندی و/یا عملکرد: فقط کد بیشتری برای دستگاه ها وجود دارد
و این می تواند عملکرد را به طور منفی تحت تاثیر قرار دهد. گاهی اوقات ممکن است بد باشد
به اندازه ای که مردم تصمیم بگیرند با استفاده از کد شما به هیچ وجه کاوش نکنند
به خاطر این مشکلات - 😖 سربار تعمیر و نگهداری: قبلاً، کد قابل استفاده مجدد شما فقط تعداد کمی داشت
گزینه ها بود و روی انجام یک کار خوب متمرکز بود، اما اکنون می تواند یکسری کار را انجام دهد
از چیزهای مختلف است و برای آن ویژگی ها به اسناد نیاز دارید. ج
علاوه بر این، بسیاری از مردم از شما سوالاتی در مورد نحوه استفاده از آن می پرسند
برای موارد استفاده خاص آنها، که ممکن است به خوبی به موارد استفاده نشان داده شود یا نباشد
شما قبلاً پشتیبانی را اضافه کرده اید. شما حتی ممکن است دو عملکرد داشته باشید که
اساساً همان چیزی را مجاز میدانید اما کمی متفاوت است، بنابراین شما خواهید بود
پاسخ به سؤالاتی در مورد اینکه کدام رویکرد بهتر است. - 🐛 پیچیدگی پیاده سازی: همین هرگز “فقط یکی
ifبیانیه.” همه
یک شاخه از منطق در کد شما با شاخه های منطق موجود ترکیب می شود.
در واقع، موقعیت هایی وجود دارد که می توانید ترکیبی از آنها را حفظ کنید
آرگومانها/گزینهها/برنامههایی که هیچکس استفاده نمیکند، اما شما باید مطمئن شوید
وقتی ویژگیهای جدید اضافه میکنید خراب نمیشود، زیرا نمیدانید کسی است یا خیر
از این ترکیب استفاده می کنید یا نه - 😕 پیچیدگی API: هر آرگومان/گزینه/سرپناه جدیدی که به قابلیت استفاده مجدد خود اضافه می کنید
کد کار را برای کاربران نهایی سخت تر می کند زیرا اکنون یک سایت بزرگ README/docs دارید که تمام ویژگی ها و افراد موجود را مستند می کند.
برای یادگیری همه چیز در دسترس برای استفاده موثر از آنها. لذت کمتری دارد
استفاده کنید زیرا اغلب پیچیدگی API شما به توسعهدهنده برنامه وارد میشود
کد را به گونه ای که کد آنها را نیز پیچیده تر می کند.
بنابراین اکنون همه از این موضوع ناراحت هستند. برای تحویل باید چیزی گفته شود
زمانی که ما برنامه ها را توسعه می دهیم بسیار مهم است. اما من فکر می کنم که خواهد شد
عالی است اگر بتوانیم مراقب انتزاعات خود باشیم (بخوانید
برنامه نویسی AHA) و برای دریافت برنامه های ما اگر وجود دارد
کاری که میتوانیم برای کاهش مشکلات کدهای قابل استفاده مجدد انجام دهیم
بهره مندی از مزایای این انتزاعات.
یکی از اصولی که یاد گرفتم یک مکانیسم واقعا موثر است
سادگی انتزاعی «وارونگی کنترل» است. در اینجا چیست
وارونگی صفحه کنترل ویکی پدیا
در مورد آن می گوید:
در برنامه نویسی سنتی، کد سفارشی که هدف را بیان می کند
این برنامه کتابخانه های قابل استفاده مجدد را برای مراقبت از وظایف رایج فراخوانی می کند
با وارونگی کنترل، این فریم است که سفارشی یا
کد مخصوص کار
شما می توانید آن را اینگونه در نظر بگیرید: «تجرید خود را وادار کنید کارهای کمتری انجام دهد و مال شما باشد
کاربران در عوض این کار را انجام می دهند.” این ممکن است خلاف واقع به نظر برسد زیرا بخشی از آن است
انتزاعات را آنقدر بزرگ می کند که ما می توانیم هر چیزی پیچیده و تکراری را کنترل کنیم
وظایف در انتزاع، بنابراین بقیه کد ما می تواند “ساده”، “خالص” یا
“تمیز”. اما همانطور که قبلاً تجربه کرده ایم، گاهی اوقات انتزاعات سنتی
اینطور رفتار نکن
اول، در اینجا یک مثال فوق العاده زیبا آورده شده است:
// let's pretend that Array.prototype.filter does not exist
function filter(array) {
let newArray = []
for (let index = 0; index array.length; index++) {
const element = array[index]
if (element !== null && element !== undefined) {
newArray[newArray.length] = element
}
}
return newArray
}
// use case:
filter([0, 1, undefined, 2, null, 3, 'four', ''])
// [0, 1, 2, 3, 'four', '']
حالا بیایید «چرخه زندگی یک انتزاع» را با چرخاندن یک دسته بازی کنیم
موارد استفاده مرتبط جدید تحت این انتزاع و “به طور بی فکر بر آن بهبود می بخشد”.
این موارد استفاده جدید را پشتیبانی می کند:
// let's pretend that Array.prototype.filter does not exist
function filter(
array,
{
filterNull = true,
filterUndefined = true,
filterZero = false,
filterEmptyString = false,
} = {},
) {
let newArray = []
for (let index = 0; index array.length; index++) {
const element = array[index]
if (
(filterNull && element === null) ||
(filterUndefined && element === undefined) ||
(filterZero && element === 0) ||
(filterEmptyString && element === '')
) {
continue
}
newArray[newArray.length] = element
}
return newArray
}
filter([0, 1, undefined, 2, null, 3, 'four', ''])
// [0, 1, 2, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterNull: false })
// [0, 1, 2, null, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterUndefined: false })
// [0, 1, 2, undefined, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterZero: true })
// [1, 2, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterEmptyString: true })
// [0, 1, 2, 3, 'four']
بسیار خوب، بنابراین ما به معنای واقعی کلمه فقط شش مورد استفاده داریم که برنامه ما به آنها اهمیت می دهد، اما
ما در واقع از هر ترکیبی از این ویژگیها پشتیبانی میکنیم، که 16 است (اگر من خودم را بسازم
ریاضی درست است).
و به طور کلی یک انتزاع بسیار ساده است. من مطمئن هستم که می تواند باشد
ساده شده اما اغلب وقتی پس از چرخ به انتزاع برمی گردی
زمان برای مدتی گذشته است، متوجه می شوید که می تواند شدید باشد
برای موارد استفاده ای که در واقع پشتیبانی می کند، ساده شده است. متأسفانه همانطور که
به محض اینکه یک انتزاع از چیزی پشتیبانی می کند (مانند انجام دادن
{filterZero: true, filterUndefined: false}ما می ترسیم این را حذف کنیم
عملکرد از ترس شکستن یک توسعه دهنده برنامه با استفاده از انتزاع ما.
ما حتی آزمایش هایی را برای موارد استفاده ای که در واقع نداریم می نویسیم، فقط به این دلیل
انتزاع ما از آن پشتیبانی می کند و ما “ممکن است” نیاز به انجام این کار در آینده داشته باشیم. و
سپس هنگامی که موارد استفاده دیگر مورد نیاز نیست، پشتیبانی از آنها را حذف نمی کنیم
زیرا ما فقط فراموش می کنیم، فکر می کنیم ممکن است در آینده به آنها نیاز داشته باشیم، یا می ترسیم
برای ضربه زدن روی کد
خوب، بیایید اکنون مقداری انتزاع متفکرانه را برای این تابع و اعمال کنیم
وارونگی کنترل را برای پشتیبانی از همه این موارد استفاده اجرا کنید:
// let's pretend that Array.prototype.filter does not exist
function filter(array, filterFn) {
let newArray = []
for (let index = 0; index array.length; index++) {
const element = array[index]
if (filterFn(element)) {
newArray[newArray.length] = element
}
}
return newArray
}
filter(
[0, 1, undefined, 2, null, 3, 'four', ''],
(el) => el !== null && el !== undefined,
)
// [0, 1, 2, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], (el) => el !== undefined)
// [0, 1, 2, null, 3, 'four', '']
filter([0, 1, undefined, 2, null, 3, 'four', ''], (el) => el !== null)
// [0, 1, 2, undefined, 3, 'four', '']
filter(
[0, 1, undefined, 2, null, 3, 'four', ''],
(el) => el !== undefined && el !== null && el !== 0,
)
// [1, 2, 3, 'four', '']
filter(
[0, 1, undefined, 2, null, 3, 'four', ''],
(el) => el !== undefined && el !== null && el !== '',
)
// [0, 1, 2, 3, 'four']
خوب همین است راه ساده تر کاری که ما انجام دادیم این بود که کنترل ها را تغییر دادیم! ما تغییر کرده ایم
مسئولیت تصمیم گیری در مورد کدام عنصر برای ورود به آرایه جدید
filter عملکرد به تماس گیرنده filter تابع توجه داشته باشید که
filter خود تابع هنوز هم در نوع خود یک انتزاع مفید است اما
بسیار توانمندتر است
اما آیا نسخه قبلی این انتزاع خیلی بد بود؟ شاید نه. اما
از آنجایی که کنترلها را تغییر دادهایم، اکنون میتوانیم موارد استفاده بسیار منحصربهفردتری را پشتیبانی کنیم:
filter(
[
{ name: 'dog', legs: 4, mammal: true },
{ name: 'dolphin', legs: 0, mammal: true },
{ name: 'eagle', legs: 2, mammal: false },
{ name: 'elephant', legs: 4, mammal: true },
{ name: 'robin', legs: 2, mammal: false },
{ name: 'cat', legs: 4, mammal: true },
{ name: 'salmon', legs: 0, mammal: false },
],
(animal) => animal.legs === 0,
)
// [
// {name: 'dolphin', legs: 0, mammal: true},
// {name: 'salmon', legs: 0, mammal: false},
// ]
تصور کنید باید قبل از چرخاندن کنترل، پشتیبانی برای آن اضافه کنید؟ فقط می شود
احمق…
یکی از شکایات رایجی که از مردم درباره API کنترل معکوس میشنوم
چیزی که من ایجاد کردم این بود: “بله، اما اکنون استفاده از آن سخت تر از قبل است.” اینو بگیر
مثال:
// before
filter([0, 1, undefined, 2, null, 3, 'four', ''])
// after
filter(
[0, 1, undefined, 2, null, 3, 'four', ''],
(el) => el !== null && el !== undefined,
)
بله، استفاده از یکی از آنها به وضوح راحت تر از دیگری است. اما موضوع اینجاست
در مورد API با کنترلهای معکوس، میتوانید از آنها برای پیادهسازی مجدد API قبلی و
معمولاً انجام این کار بسیار پیش پا افتاده است. به عنوان مثال:
function filterWithOptions(
array,
{
filterNull = true,
filterUndefined = true,
filterZero = false,
filterEmptyString = false,
} = {},
) {
return filter(
array,
(element) =>
!(
(filterNull && element === null) ||
(filterUndefined && element === undefined) ||
(filterZero && element === 0) ||
(filterEmptyString && element === '')
),
)
}
باحال درسته!؟ بنابراین میتوانیم انتزاعها را در بالای API کنترلشده وارونه بسازیم
که API ساده تری را که مردم به دنبال آن هستند ارائه می دهد. و چه چیزی بیشتر، اگر مال ما باشدAPI “ساده تر” برای مورد استفاده آنها کافی نیست، سپس آنها می توانند از همان استفاده کنند
بلوکهای ساختمانی که برای ساختن API سطح بالاتر خود برای دستیابی به موارد بیشتر استفاده کردیم
یک کار دشوار لازم نیست از ما بخواهند ویژگی جدیدی به آن اضافه کنیم
filterWithOptions و صبر کنید تا تمام شود آنها دارند
بلوک های ساختمانی که آنها نیاز دارند تا وسایل خود را تحویل دهند زیرا ما هستیم
ابزار این کار را به آنها داد.
اوه، و فقط برای سرگرمی:
function filterByLegCount(array, legCount) {
return filter(array, (animal) => animal.legs === legCount)
}
filterByLegCount(
[
{ name: 'dog', legs: 4, mammal: true },
{ name: 'dolphin', legs: 0, mammal: true },
{ name: 'eagle', legs: 2, mammal: false },
{ name: 'elephant', legs: 4, mammal: true },
{ name: 'robin', legs: 2, mammal: false },
{ name: 'cat', legs: 4, mammal: true },
{ name: 'salmon', legs: 0, mammal: false },
],
0,
)
// [
// {name: 'dolphin', legs: 0, mammal: true},
// {name: 'salmon', legs: 0, mammal: false},
// ]
شما می توانید این موارد را بنویسید، اما می خواهید به موارد استفاده رایج بپردازید
شما دارید
بنابراین این برای مورد استفاده ساده کار می کند، اما این مفهوم در دنیای واقعی چقدر مفید است؟ خوب، احتمالاً بدون اینکه متوجه شوید همیشه از APIهای کنترل معکوس استفاده می کنید.
به عنوان مثال، در واقعی Array.prototype.filter تابع کنترل را معکوس می کند. مانند
انجام می دهد Array.prototype.map تابع
همچنین الگوهایی وجود دارد که ممکن است با آنها آشنا باشید که اساساً نوعی از آنها هستند
وارونگی کنترل
دو الگوی مورد علاقه من برای این هستند
“اجزا” و
“کاهش دهنده های ایالتی”. در اینجا یک مثال سریع از
چگونه می توان از این مدل ها استفاده کرد
اجزای تشکیل دهنده
فرض کنید می خواهید یک بسازید Menu جزء که دارای دکمه ای برای باز کردن است
منو و لیستی از آیتم های منو که با کلیک روی آن نمایش داده می شود. سپس زمانی که یک آیتم است
انتخاب شده، عملی را انجام خواهد داد. رویکرد رایج به این گونه
جزء ایجاد پشتیبانی برای هر یک از این موارد است:
function App() {
return (
Menu
buttonContents={
Actions span aria-hidden>▾span>
>
}
items={[
{ contents: 'Download', onSelect: () => alert('Download') },
{ contents: 'Create a Copy', onSelect: () => alert('Create a Copy') },
{ contents: 'Delete', onSelect: () => alert('Delete') },
]}
/>
)
}
این به ما امکان می دهد تا موارد زیادی را برای آیتم منوی خود سفارشی کنیم. اما اگر بخواهیم چه می شد
قبل از آیتم منو حذف یک خط وارد کنید؟ آیا باید گزینه ای به آن اضافه کنیم
اشیاء اشیاء؟ مثلا نمیدونم: precedeWithLine? شاید داشته باشیم
نوع خاصی از آیتم های منو که a {contents:
}. حدس میزنم همین باشد
کار می کند، اما پس از آن ما باید با این مورد کنار بیاییم که اینطور نیست onSelect ارائه شده است. و
این صادقانه یک API بی دست و پا است.
وقتی به نحوه ایجاد یک API خوب برای افرادی که در تلاش هستند فکر می کنید
کارها را کمی متفاوت انجام دهید به جای رسیدن به آن if اظهارات و
سه تایی، کنترل معکوس را در نظر بگیرید. در این مورد چه می شود اگر
آیا ما فقط مسئولیت ارائه کاربر به منوی خود را دادیم؟ بیایید از یکی از آنها استفاده کنیم
قوی ترین نقاط React برای ترکیب:
function App() {
return (
Menu>
MenuButton>
Actions span aria-hidden>▾span>
MenuButton>
MenuList>
MenuItem onSelect={() => alert('Download')}>DownloadMenuItem>
MenuItem onSelect={() => alert('Copy')}>Create a CopyMenuItem>
MenuItem onSelect={() => alert('Delete')}>DeleteMenuItem>
MenuList>
Menu>
)
}
نکته کلیدی در اینجا این است که هیچ حالتی برای کاربر قابل مشاهده نیست
اجزاء حالت به طور ضمنی بین این مؤلفه ها مشترک است. همین است
ارزش اولیه مدل جزء با استفاده از این فرصت،
اکنون نیز کنترلی بر روی رندر به کاربر اجزای خود داده ایم
اضافه کردن یک خط اضافی در آنجا (یا هر چیز دیگری برای آن موضوع) بسیار است
پیش پا افتاده و شهودی بدون جستجوی اسناد API و هیچ ویژگی اضافی، کد یا
تست های اضافه یک برد بزرگ برای همه
می توانید در مورد این مدل بیشتر بخوانید
در وبلاگ من نوک کلاهک
رایان فلورانس که این الگو را به من آموخت.
کاهنده حالت
این الگویی است که من برای حل یک مشکل منطقی کامپوننت ارائه کردم
سفارشی سازی شما می توانید در مورد وضعیت خاص در پست وبلاگ من “الگوی کاهش دهنده ایالت” بیشتر بخوانید
خلاصه این است که من یک کتابخانه ورودی جستجو/پیشرفت/تکمیل خودکار به نام داشتم Downshift
و شخصی در حال ساخت یک نسخه چند گزینه ای از کامپوننت بود
می خواستم حتی پس از انتخاب یک مورد، منو باز بماند.
ج Downshift ما منطقی داشتیم که می گفت وقتی انتخاب انجام می شود باید بسته شود.
شخصی که به این ویژگی نیاز دارد پیشنهاد کرد که یک پایه به نام اضافه شود
closeOnSelection. من آن را رها کردم زیرا با آن برخورد کردم
جاده آخرالزمان
قبلا و می خواستم از آن اجتناب کنم.
بنابراین به جای آن یک API برای انسانها ارائه کردم تا نحوه تغییر وضعیت را کنترل کند
اتفاق افتاد یک کاهنده حالت را به عنوان تابعی در نظر بگیرید که در هر زمان فراخوانی می شود
وضعیت یک جزء تغییر می کند و به توسعه دهنده برنامه اجازه می دهد آن را تغییر دهد
تغییر حالتی که در شرف وقوع است.
در اینجا نمونهای از کارهایی است که اگر میخواهید Downshift بسته نشود، انجام میدهید
منوی بعد از اینکه کاربر یک مورد را انتخاب کرد:
function stateReducer(state, changes) {
switch (changes.type) {
case Downshift.stateChangeTypes.keyDownEnter:
case Downshift.stateChangeTypes.clickItem:
return {
...changes,
// we're fine with any changes Downshift wants to make
// except we're going to leave isOpen and highlightedIndex as-is.
isOpen: state.isOpen,
highlightedIndex: state.highlightedIndex,
}
default:
return changes
}
}
// then when you render the component
//
پس از افزودن این پشتیبانی، درخواستهای بسیار کمتری برای سفارشیسازی دریافت کردیم
جزء بسیار توانمندتر شده است و انجام آن برای مردم بسیار آسان تر شده است
هر کاری می خواستند بکنند
Render Props
فقط با دادن یک فریاد سریع به
یک الگوی پایه که a است را به تصویر بکشید
مثال کاملی از وارونگی کنترل، اما ما اغلب به آنها نیاز نداریم
در حال حاضر، بنابراین من در مورد آنها صحبت نمی کنم.
بخوانید چرا دیگر به Render Props نیازی نداریم
وارونگی کنترل یک راه فوق العاده برای دور زدن مشکل ایجاد یک است
فرض اشتباه در مورد موارد استفاده آتی کد قابل استفاده مجدد ما. اما قبل از
بیا فقط میخوام یه نصیحت بهت کنم بیایید به داستان های تخیلی خودمان برگردیم
مثال خیلی سریع:
// let's pretend that Array.prototype.filter does not exist
function filter(array) {
let newArray = []
for (let index = 0; index array.length; index++) {
const element = array[index]
if (element !== null && element !== undefined) {
newArray[newArray.length] = element
}
}
return newArray
}
// use case:
filter([0, 1, undefined, 2, null, 3, 'four', ''])
// [0, 1, 2, 3, 'four', '']
چه می شود اگر این تمام چیزی باشد که ما همیشه به آن نیاز داشتیم filter انجام دهیم و ما هرگز با a مواجه نشده ایم
وضعیتی که ما مجبور بودیم هر چیزی را فیلتر کنیم جز null و undefined? ج
در این مورد، افزودن وارونگی کنترل برای یک مورد استفاده تنها کار می کند
کد پیچیده تر است و ارزش زیادی ارائه نمی دهد.
مانند هر انتزاعی، لطفاً مراقب باشید و اصل را اعمال کنید
برنامه نویسی AHA و اجتناب از انتزاعات عجولانه!
امیدوارم این برای شما مفید باشد. من چندین مدل را در React به شما نشان دادم
جامعه ای که از مفهوم معکوس کنترل سود می برد. وجود دارد
بیشتر در آنجا وجود دارد، و این مفهوم در مورد چیزی بیش از React صدق می کند (همانطور که با آن دیدیم
در filter مثال). دفعه بعد که متوجه شدید که یکی دیگر را اضافه می کنید if بیانیه
به coreBusinessLogic عملکرد برنامه خود، به این فکر کنید که چگونه می توانید معکوس کنید
منطق را کنترل کرده و به جایی که از آن استفاده می شود (یا اگر در آن استفاده می شود، منتقل کنید
چندین مکان، سپس می توانید یک انتزاع سفارشی تر برای آن بسازید
مورد استفاده خاص).
اگر می خواهید با مثال در این پست وبلاگ بازی کنید، راحت باشید:
موفق باشید!
P.S. اگر این پست وبلاگ را دوست داشتید، احتمالاً این بحث را دوست خواهید داشت:



