واجهات برمجة تطبيقات تفاعلية مفيدة لبناء مكونات مرنة باستخدام TypeScript
نشرت: 2022-03-10 هل سبق لك استخدام React.createElement
بشكل مباشر؟ ماذا عن React.cloneElement
؟ تعتبر React أكثر من مجرد تحويل JSX إلى HTML. أكثر من ذلك بكثير ، ولمساعدتك على رفع مستوى معرفتك بواجهات برمجة التطبيقات (APIs) الأقل شهرة (ولكنها مفيدة جدًا) التي توفرها مكتبة React. سنستعرض عددًا قليلاً منهم وبعض حالات الاستخدام الخاصة بهم التي يمكن أن تعزز بشكل كبير تكامل المكونات الخاصة بك وفائدتها.
في هذه المقالة ، سنستعرض عددًا قليلاً من واجهات برمجة تطبيقات React المفيدة التي ليست معروفة بشكل شائع ولكنها مفيدة للغاية لمطوري الويب. يجب أن يكون القراء من ذوي الخبرة في بناء جملة React و JSX ، ومعرفة الأنواع مفيدة ولكنها ليست ضرورية. سيتخلص القراء من كل ما يحتاجون إلى معرفته من أجل تحسين مكونات React بشكل كبير عند استخدامها في تطبيقات React.
React.cloneElement
ربما لم يسمع معظم المطورين عن cloneElement
أو استخدموه من قبل. تم تقديمه مؤخرًا نسبيًا ليحل محل وظيفة cloneWithProps
التي تم إهمالها الآن. يستنسخ cloneElement
عنصرًا ، ويتيح لك أيضًا دمج عناصر جديدة مع العنصر الحالي ، أو تعديلها أو تجاوزها كما تراه مناسبًا. يفتح هذا خيارات قوية للغاية لبناء واجهات برمجة تطبيقات ذات مستوى عالمي للمكونات الوظيفية. الق نظرة على التوقيع.
function cloneElement( element, props?, ...children)
هذا هو الإصدار المكتوب المكثف:
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
يمكنك أخذ عنصر ، وتعديله ، بل وحتى تجاوز العناصر الفرعية له ، ثم إعادته كعنصر جديد. الق نظرة على المثال التالي. لنفترض أننا نريد إنشاء مكون TabBar للروابط. قد يبدو هذا شيء من هذا القبيل.
export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }
TabBar عبارة عن قائمة روابط ، لكننا بحاجة إلى طريقة لتحديد جزأين من البيانات ، عنوان الارتباط وعنوان URL. لذلك سنريد أن يتم تمرير بنية بيانات بهذه المعلومات. لذا فإن مطورنا سيجعل مكوننا هكذا.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
هذا رائع ، ولكن ماذا لو أراد المستخدم عرض عناصر button
بدلاً a
العناصر؟ حسنًا ، يمكننا إضافة خاصية أخرى تخبر المكون بنوع العنصر الذي سيتم عرضه.
ولكن يمكنك أن ترى كيف سيصبح هذا صعبًا بسرعة ، فسنحتاج إلى دعم المزيد والمزيد من الخصائص للتعامل مع حالات الاستخدام المختلفة وحالات الحافة لتحقيق أقصى قدر من المرونة.
هذه طريقة أفضل ، باستخدام React.cloneElement
.
سنبدأ بتغيير واجهتنا للإشارة إلى نوع ReactNode
. هذا نوع عام يشمل أي شيء يمكن أن تقدمه React ، عادةً عناصر JSX ولكن أيضًا يمكن أن تكون سلاسل وحتى null
. يفيد هذا في تحديد رغبتك في قبول مكونات React أو JSX كوسائط مضمنة.
export interface ITabbarProps { links: ReactNode[] }
نحن الآن نطلب من المستخدم أن يعطينا بعض عناصر React ، وسنعرضها بالطريقة التي نريدها.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
هذا صحيح تمامًا وسيجعل عناصرنا. لكننا نسينا شيئين. لواحد ، key
! نريد إضافة مفاتيح حتى تتمكن React من عرض قوائمنا بكفاءة. نريد أيضًا تغيير العناصر الخاصة بنا لإجراء التحولات الضرورية حتى تتناسب مع className
، مثل اسم الفئة وما إلى ذلك.
يمكننا القيام بذلك باستخدام React.cloneElement
، ووظيفة أخرى React.isValidElement
للتحقق من توافق الوسيطة مع ما نتوقعه!
React.isValidElement
ترجع هذه الدالة true
إذا كان العنصر هو عنصر React صالحًا ويمكن لـ React عرضه. فيما يلي مثال على تعديل العناصر من المثال السابق.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
نحن هنا نضيف دعامة رئيسية لكل عنصر نمرره ونجعل كل رابط غامقًا في نفس الوقت! يمكننا الآن قبول عناصر React التعسفية كدعامات مثل:
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
يمكننا تجاوز أي من العناصر المحددة على عنصر ما ، وقبول أنواع مختلفة من العناصر بسهولة مما يجعل المكون الخاص بنا أكثر مرونة وسهولة في الاستخدام.
"
الميزة هنا هي أننا إذا أردنا تعيين معالج onClick
مخصص على زرنا ، فيمكننا القيام بذلك. يعد قبول عناصر React نفسها كوسيطات طريقة فعالة لإضفاء المرونة على تصميم المكون الخاص بك.
useState
Setter وظيفة
استخدم الخطافات! يعتبر خطاف useState
مفيدًا للغاية وواجهة برمجة تطبيقات رائعة لبناء الحالة بسرعة في مكوناتك مثل:
const [myValue, setMyValue] = useState()
نظرًا لوقت تشغيل JavaScript ، يمكن أن تحتوي على بعض السقطات. تذكر الإغلاق؟
في حالات معينة ، قد لا يكون المتغير هو القيمة الصحيحة بسبب السياق الموجود فيه ، كما هو الحال في أحداث for-loops الشائعة أو غير المتزامنة. هذا بسبب تحديد النطاق المعجمي. عند إنشاء وظيفة جديدة ، يتم الاحتفاظ بالنطاق المعجمي. نظرًا لعدم وجود وظيفة جديدة ، لا يتم الاحتفاظ بالنطاق المعجمي لـ newVal
، وبالتالي يتم إلغاء الإشارة إلى القيمة بالفعل بحلول وقت استخدامها.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
ما عليك القيام به هو استخدام أداة الإعداد كوظيفة. من خلال إنشاء دالة جديدة ، يتم الاحتفاظ بمرجع المتغيرات في النطاق المعجمي ويتم تمرير CurrentVal بواسطة React useState Hook نفسه.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
سيضمن هذا تحديث القيمة الخاصة بك بشكل صحيح لأنه يتم استدعاء دالة المعيِّن في السياق الصحيح. ما تفعله React هنا هو استدعاء وظيفتك في السياق الصحيح حتى يحدث تحديث لحالة React. يمكن استخدام هذا أيضًا في مواقف أخرى حيث يكون من المفيد العمل على القيمة الحالية ، تستدعي React وظيفتك مع الوسيط الأول كقيمة حالية.
ملاحظة : لقراءة إضافية حول موضوع عدم التزامن والإغلاق ، أوصي بقراءة " useState
Lazy Initialization and Function Updates" بواسطة Kent C. Dodds.
وظائف مضمنة JSX
إليك عرض Codepen لوظيفة مضمنة في JSX:
ليس بالضبط واجهة برمجة تطبيقات React لكل كلمة.
يدعم JSX الوظائف المضمنة ويمكن أن يكون مفيدًا حقًا للإعلان عن منطق بسيط مع المتغيرات المضمنة ، طالما أنه يقوم بإرجاع عنصر JSX.
"
هذا مثال:
function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }
نحن هنا نعلن عن كود داخل JSX ، يمكننا تشغيل كود عشوائي وكل ما علينا فعله هو إرجاع دالة JSX ليتم عرضها.
يمكننا جعلها مشروطة ، أو ببساطة تنفيذ بعض المنطق. قم بتدوين الأقواس المحيطة بالوظيفة المضمنة. أيضًا بشكل خاص هنا حيث نطلق على هذه الوظيفة ، يمكننا حتى تمرير حجة إلى هذه الوظيفة من السياق المحيط إذا أردنا ذلك!
})()}
يمكن أن يكون هذا مفيدًا في المواقف التي تريد فيها التصرف على هيكل بيانات المجموعة بطريقة أكثر تعقيدًا مما تسمح به الخريطة القياسية داخل عنصر .map
.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
هنا يمكننا تشغيل بعض التعليمات البرمجية للتكرار من خلال مجموعة من الأرقام ثم عرضها مضمنة. إذا كنت تستخدم منشئ موقع ثابت مثل Gatsby ، فسيتم حساب هذه الخطوة مسبقًا أيضًا.
component extends type
هذه الميزة مفيدة للغاية لإنشاء مكونات سهلة الإكمال التلقائي ، حيث تتيح لك إنشاء مكونات تعمل على توسيع عناصر HTMLElements
أو المكونات الأخرى. غالبًا ما يكون مفيدًا لكتابة واجهة العناصر بشكل صحيح في Typescript ، لكن التطبيق الفعلي هو نفسه بالنسبة إلى JavaScript.
إليك مثال بسيط ، لنفترض أننا نريد تجاوز خاصية أو اثنتين من خصائص عنصر button
، لكننا لا نزال نعطي المطورين خيار إضافة خصائص أخرى إلى الزر. مثل type='button'
أو type='submit'
. من الواضح أننا لا نريد إعادة إنشاء عنصر الزر بالكامل ، بل نريد فقط توسيع خصائصه الحالية ، وربما إضافة عنصر آخر.
import React, { ButtonHTMLAttributes } from 'react'
أولاً نقوم باستيراد ButtonHTMLAttributes
وصنف ButtonHTMLAttributes ، وهو نوع يشمل HTMLButtonElement
. يمكنك قراءة الكود المصدري لهذا النوع من الواجهة هنا:
ويمكنك أن ترى أن فريق React قد أعاد تطبيق جميع واجهات برمجة التطبيقات على الويب في TypeScript بحيث يمكن فحصها من النوع.
بعد ذلك ، نعلن أن واجهتنا هكذا ، بإضافة خاصية status
الخاصة بنا.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
وأخيرًا ، نقوم بأمرين. نستخدم التدمير ES6 لسحب الدعائم التي نهتم بها ( status
، children
) ، والإعلان عن أي خصائص أخرى على أنها rest
. وفي إخراج JSX الخاص بنا ، نعيد عنصر زر ، مع بنية ES6 لإضافة أي خصائص إضافية إلى هذا العنصر.
function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }
لذا يمكن للمطور الآن إضافة خاصية type
أو أي خاصية أخرى يمكن أن يحتوي عليها الزر عادةً. لقد قدمنا خاصية إضافية استخدمناها في className
لتعيين نمط الزر.
هذا هو المثال الكامل:
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }
هذا يجعل طريقة رائعة لإنشاء مكونات داخلية قابلة لإعادة الاستخدام تتوافق مع إرشادات النمط الخاص بك دون إعادة بناء عناصر HTML بالكامل! يمكنك ببساطة تجاوز الخاصيات بالكامل مثل تعيين className
بناءً على الحالة أو السماح بتمرير أسماء فئات إضافية أيضًا.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }
هنا نأخذ prop className
الذي تم تمريره إلى عنصر Button الخاص بنا ، وأدخله مرة أخرى ، مع التحقق من الأمان في حالة عدم تحديد undefined
.
خاتمة
تعتبر React مكتبة قوية للغاية ، وهناك سبب وجيه لاكتساب شعبية بسرعة. يمنحك مجموعة أدوات رائعة لإنشاء تطبيقات ويب فعالة وسهلة الصيانة. إنه مرن للغاية ولكنه صارم للغاية في نفس الوقت ، والذي يمكن أن يكون مفيدًا بشكل لا يصدق إذا كنت تعرف كيفية استخدامه. هذه ليست سوى عدد قليل من واجهات برمجة التطبيقات الجديرة بالملاحظة والتي يتم تجاهلها إلى حد كبير. جربهم في مشروعك القادم!
لمزيد من القراءة حول أحدث واجهات برمجة تطبيقات React ، الخطافات ، أوصي بقراءة useHooks (). تحتوي ورقة الغش المطبعية أيضًا على بعض المعلومات الرائعة حول React و Typescript Hooks.