نشرنا مؤخرًا مواد
عن وظائف الترتيب الأعلى في JavaScript تستهدف أولئك الذين يتعلمون JavaScript. المقالة التي نترجمها اليوم مخصصة للمطورين المبتدئين. ويركز على مكونات النظام الأعلى (HOC).

مبدأ DRY ومكونات النظام الأعلى في React
لن تتمكن من التقدم كثيرًا بما يكفي في دراسة البرمجة ولا تصادف مبدأ DRY تقريبًا (لا تكرر نفسك ، لا تكرر). في بعض الأحيان يذهب أتباعه إلى أبعد من ذلك ، ولكن في معظم الحالات ، يجدر بنا السعي للامتثال. هنا سوف نتحدث عن نمط تطوير التفاعل الأكثر شيوعًا ، والذي يضمن الامتثال لمبدأ DRY. حول مكونات النظام الأعلى. من أجل فهم قيمة مكونات النظام الأعلى ، دعنا أولاً نقوم بصياغة وفهم المشكلة التي تهدف إلى حلها.
افترض أنك بحاجة إلى إعادة إنشاء لوحة تحكم مشابهة للوحة Stripe. تحتوي العديد من المشاريع على عقار يتم تطويره وفقًا للمخطط ، عندما يسير كل شيء على ما يرام حتى لحظة اكتمال المشروع. عندما تعتقد أن العمل على وشك الانتهاء ، تلاحظ أن لوحة التحكم بها العديد من تلميحات الأدوات المختلفة التي يجب أن تظهر عند التمرير فوق عناصر معينة.
لوحة التحكم وتلميحات الأدواتمن أجل تنفيذ مثل هذه الوظيفة ، يمكنك استخدام العديد من الأساليب. لقد قررت القيام بذلك: حدد ما إذا كان المؤشر أعلى مكون فردي ، ثم قرر ما إذا كنت تريد إظهار تلميحًا له أم لا. هناك ثلاثة مكونات تحتاج إلى أن تكون مجهزة بوظائف مماثلة. هذه هي
Info
و
TrendChart
و
DailyChart
.
لنبدأ بمكون
Info
. الآن هو رمز SVG بسيط.
class Info extends React.Component { render() { return ( <svg className="Icon-svg Icon--hoverable-svg" height={this.props.height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> ) } }
نحتاج الآن إلى جعل هذا المكون قادرًا على تحديد ما إذا كان مؤشر الماوس فوقه أم لا. يمكنك استخدام فعاليات الماوس
onMouseOut
و
onMouseOut
لهذا الغرض. سيتم استدعاء الوظيفة التي تم تمريرها إلى
onMouseOver
إذا سقط مؤشر الماوس في منطقة المكون ، وسيتم استدعاء الوظيفة التي تم تمريرها إلى
onMouseOut
عندما يترك المؤشر المكون. من أجل تنظيم كل هذا بطريقة مقبولة في React ، نضيف خاصية
hovering
إلى المكون المخزن في الحالة ، مما يسمح لنا بإعادة عرض المكون عن طريق إظهار تلميح الأداة أو إخفائه إذا تغيرت هذه الخاصية.
class Info extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <> {this.state.hovering === true ? <Tooltip id={this.props.id} /> : null} <svg onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} className="Icon-svg Icon--hoverable-svg" height={this.props.height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> </> ) } }
اتضح بشكل جيد. الآن نحن بحاجة إلى إضافة نفس الوظيفة إلى مكونين آخرين -
TrendChart
و
DailyChart
. تعمل الآلية المذكورة أعلاه لمكون
Info
بشكل جيد ، ولا يلزم إصلاح ما لم يتم كسره ، لذا دعنا نعيد إنشاء نفسه في المكونات الأخرى باستخدام نفس الرمز. قم بإعادة تدوير التعليمات البرمجية لمكون
TrendChart
.
class TrendChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <> {this.state.hovering === true ? <Tooltip id={this.props.id}/> : null} <Chart type='trend' onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} /> </> ) } }
ربما فهمت بالفعل ما يجب فعله بعد ذلك. ويمكن القيام
DailyChart
نفسه مع المكون الأخير -
DailyChart
.
class DailyChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <> {this.state.hovering === true ? <Tooltip id={this.props.id}/> : null} <Chart type='daily' onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} /> </> ) } }
الآن كل شيء جاهز. ربما تكون قد كتبت بالفعل شيئًا مشابهًا على React. هذا ، بالطبع ، ليس أسوأ رمز في العالم ، لكنه لا يتبع مبدأ DRY بشكل جيد. كما ترون ، من خلال تحليل رمز المكون ، نكرر ، في كل واحد منهم ، نفس المنطق.
يجب أن تصبح المشكلة التي تواجهنا واضحة للغاية الآن. هذا رمز مكرر. لحلها ، نريد التخلص من الحاجة إلى نسخ نفس الرمز في الحالات التي يحتاج فيها مكون جديد بالفعل إلى ما قمنا بتطبيقه بالفعل. كيفية حلها؟ قبل أن نتحدث عن هذا ، سنركز على العديد من مفاهيم البرمجة التي ستسهل إلى حد كبير فهم الحل المقترح هنا. نحن نتحدث عن رد الاتصال ووظائف الترتيب الأعلى.
وظائف ترتيب أعلى
وظائف JavaScript هي كائنات من الدرجة الأولى. هذا يعني أنها ، مثل الكائنات أو المصفوفات أو السلاسل ، يمكن تعيينها لمتغيرات أو تمريرها إلى دالات كوسيطة أو إرجاعها من دالات أخرى.
function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } addFive(10, add) // 15
إذا لم تكن معتادًا على هذا السلوك ، فقد يبدو الرمز أعلاه غريبًا بالنسبة لك. لنتحدث عما يحدث هنا. وبالتحديد ، نقوم بتمرير وظيفة
add
إلى وظيفة
addFive
كوسيطة ،
addReference
تسميتها إلى
addReference
ثم نسميها.
عند استخدام مثل هذه الإنشاءات ، تُسمى الدالة التي يتم تمريرها كوسيطة إلى أخرى باسم رد الاتصال (وظيفة رد الاتصال) ، وتسمى الوظيفة التي تتلقى وظيفة أخرى كوسيطة بوظيفة أعلى مرتبة.
تسمية الكيانات في البرمجة مهمة ، لذلك هنا هو نفس الرمز المستخدم الذي يتم فيه تغيير الأسماء وفقًا للمفاهيم التي تمثلها.
function add (x,y) { return x + y } function higherOrderFunction (x, callback) { return callback(x, 5) } higherOrderFunction(10, add)
يجب أن يبدو هذا النمط مألوفًا لك. والحقيقة هي أنك إذا استخدمت ، على سبيل المثال ، طرق صفيف جافا سكريبت ، وعملت مع jQuery أو Lodash ، فأنت قد استخدمت بالفعل وظائف عالية المستوى وعمليات رد.
[1,2,3].map((i) => i + 5) _.filter([1,2,3,4], (n) => n % 2 === 0 ); $('#btn').on('click', () => console.log('Callbacks are everywhere') )
دعونا نعود إلى مثالنا. ماذا لو ، بدلاً من إنشاء وظيفة
addFive
فقط ، نريد إنشاء وظيفة
addTwenty
، و
addTwenty
، وغيرها مثل ذلك. بالنظر إلى كيفية
addFive
وظيفة
addFive
، سيتعين علينا نسخ
addFive
وتغييرها لإنشاء الوظائف المذكورة أعلاه بناءً عليها.
function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } function addTen (x, addReference) { return addReference(x, 10) } function addTwenty (x, addReference) { return addReference(x, 20) } addFive(10, add) // 15 addTen(10, add) // 20 addTwenty(10, add) // 30
وتجدر الإشارة إلى أن الكود الخاص بنا لم يكن كابوسًا جدًا ، ولكن من الواضح أن العديد من الأجزاء فيه متكررة. هدفنا هو أن نتمكن من إنشاء العديد من الوظائف التي تضيف أرقامًا معينة إلى الأرقام التي تم تمريرها إليها (
addFive
و
addTen
و
addTwenty
وما إلى ذلك) بقدر ما نحتاج ، مع تقليل تكرار التعليمات البرمجية. ربما لتحقيق ذلك ، نحتاج إلى إنشاء وظيفة
makeAdder
؟ يمكن أن تأخذ هذه الوظيفة رقمًا معينًا ورابطًا إلى وظيفة
add
. نظرًا لأن الغرض من هذه الوظيفة هو إنشاء وظيفة جديدة تضيف الرقم الذي تم تمريره إليها إلى الوظيفة المحددة ، يمكننا أن نجعل وظيفة
makeAdder
ترجع وظيفة جديدة تحتوي على رقم معين (مثل الرقم 5 في
makeFive
) والتي يمكن أن تأخذ أرقامًا للإضافة إلى هذا الرقم.
نلقي نظرة على مثال على تنفيذ الآليات المذكورة أعلاه.
function add (x, y) { return x + y } function makeAdder (x, addReference) { return function (y) { return addReference(x, y) } } const addFive = makeAdder(5, add) const addTen = makeAdder(10, add) const addTwenty = makeAdder(20, add) addFive(10)
الآن يمكننا إنشاء العديد من
add
حسب الحاجة ، مع تقليل مقدار تكرار الكود.
إذا كان الأمر مثيرًا للاهتمام ، فإن المفهوم القائل بوجود وظيفة معينة تعالج وظائف أخرى بحيث يمكن استخدامها بمعلمات أقل من ذي قبل يُطلق عليه "التطبيق الجزئي للدالة". يستخدم هذا النهج في البرمجة الوظيفية. مثال على استخدامه هو أسلوب
.bind
المستخدم في JavaScript.
كل هذا جيد ، ولكن ما علاقة رد الفعل بالمشكلة المذكورة أعلاه المتمثلة في تكرار التعليمات البرمجية لمعالجة أحداث الماوس عند إنشاء مكونات جديدة تحتاج إلى هذه الميزة؟ والحقيقة هي أنه تمامًا مثل وظيفة الترتيب الأعلى ،
makeAdder
تقليل تكرار التعليمات البرمجية إلى الحد الأدنى ، فإن ما يُسمى "مكون الترتيب الأعلى" سيساعدنا في التعامل مع نفس المشكلة في تطبيق React. ومع ذلك ، هنا كل شيء سيبدو مختلفًا قليلاً. وبالتحديد ، بدلاً من مخطط العمل ، الذي تُرجع خلاله دالة الترتيب الأعلى وظيفة جديدة تستدعي رد الاتصال ، يمكن لمكون النظام الأعلى تنفيذ مخططه الخاص. أي أنه قادر على إرجاع مكون جديد يجعل مكونًا يلعب دور "رد الاتصال". ربما قلنا بالفعل الكثير من الأشياء ، لذلك حان الوقت للانتقال إلى الأمثلة.
وظيفة النظام الأعلى لدينا
تحتوي هذه الميزة على الميزات التالية:
- إنها وظيفة.
- تقبل كحجة رد اتصال.
- تقوم بإرجاع دالة جديدة.
- يمكن للدالة التي تقوم بإرجاعها استدعاء رد الاتصال الأصلي الذي تم تمريره إلى دالة الترتيب الأعلى الخاصة بنا.
function higherOrderFunction (callback) { return function () { return callback() } }
عنصر النظام الأعلى لدينا
يمكن وصف هذا المكون على النحو التالي:
- إنه مكون.
- كحجة ، يأخذ عنصر آخر.
- تقوم بإرجاع مكون جديد.
- يمكن أن يعرض المكون الذي يقوم بإرجاع المكون الأصلي الذي تم تمريره إلى المكون ذي الترتيب الأعلى.
function higherOrderComponent (Component) { return class extends React.Component { render() { return <Component /> } } }
تنفيذ HOC
الآن بعد أن توصلنا ، بشكل عام ، إلى تحديد الإجراءات التي يقوم بها مكون الترتيب الأعلى بالضبط ، سنبدأ في إجراء تغييرات على رمز التفاعل الخاص بنا. إذا كنت تتذكر ، فإن جوهر المشكلة التي نحلها هو أنه يجب نسخ الشفرة التي تنفذ منطق معالجة أحداث الماوس إلى جميع المكونات التي تحتاج إلى هذه الميزة.
state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false })
نظرًا لذلك ، نحتاج إلى مكون النظام الأعلى (دعنا نسميه بـ
withHover
) لتغليف كود معالجة حدث الماوس ، ثم تمرير خاصية
hovering
إلى المكونات التي يعرضها. سيسمح لنا هذا بمنع تكرار الكود المقابل بوضعه في المكون
withHover
.
في النهاية ، هذا ما نريد تحقيقه. عندما نحتاج إلى مكون يحتاج إلى فكرة عن خاصية
hovering
الخاصة به ، يمكننا تمرير هذا المكون إلى مكون عالي الترتيب مع
withHover
. أي أننا نريد العمل مع المكونات كما هو موضح أدناه.
const InfoWithHover = withHover(Info) const TrendChartWithHover = withHover(TrendChart) const DailyChartWithHover = withHover(DailyChart)
بعد ذلك ، عندما يتم عرض ما
withHover
، سيكون المكون المصدر الذي يتم
hovering
خاصية
hovering
.
function Info ({ hovering, height }) { return ( <> {hovering === true ? <Tooltip id={this.props.id} /> : null} <svg className="Icon-svg Icon--hoverable-svg" height={height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> </> ) }
في الواقع ، علينا الآن فقط تنفيذ مكون
withHover
. مما سبق ، يمكن فهم أنه يجب عليه القيام بثلاثة إجراءات:
- خذ حجة إلى المكون.
- إرجاع مكون جديد.
- قم بعرض الوسيطة Component بتمرير خاصية
hovering
إليها.
▍ قبول حجة المكون
function withHover (Component) { }
▍إرجاع مكون جديد
function withHover (Component) { return class WithHover extends React.Component { } }
▍ عرض مكون المكون مع تمرير خاصية التحويم إليه
نواجه الآن السؤال التالي: كيفية الوصول إلى خاصية
hovering
؟ في الواقع ، لقد كتبنا بالفعل رمز للعمل مع هذه الخاصية. نحتاج فقط إلى إضافته إلى المكون الجديد ، ثم تمرير خاصية
hovering
إليه عند عرض المكون الذي تم تمريره إلى المكون ذي الترتيب الأعلى في شكل الوسيطة
Component
.
function withHover(Component) { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component hovering={this.state.hovering} /> </div> ); } } }
أفضل التحدث عن هذه الأشياء بالطريقة التالية (كما تقول وثائق التفاعل): المكون يحول الخصائص إلى واجهة مستخدم ، والمكون الأعلى رتبة يحول المكون إلى مكون آخر. في حالتنا ، سنقوم بتحويل مكونات
DailyChart
و
DailyChart
و
DailyChart
إلى مكونات جديدة ، بفضل خاصية
hovering
، تعرف ما إذا كان مؤشر الماوس فوقها.
ملاحظات إضافية
في هذه المرحلة ، قمنا بمراجعة جميع المعلومات الأساسية حول مكونات النظام الأعلى. ومع ذلك ، هناك بعض الأشياء الأكثر أهمية للمناقشة.
إذا
withHover
نظرة على
withHover
، فستلاحظ أن لديها نقطة ضعف واحدة على الأقل. هذا يعني أن مكون جهاز الاستقبال لخاصية
hovering
لن يواجه أي مشاكل مع هذه الخاصية. في معظم الحالات ، من المحتمل أن يكون هذا الافتراض مبررًا ، ولكن قد يحدث أن هذا غير مقبول. على سبيل المثال ، ماذا لو كان للمكون بالفعل خاصية
hovering
؟ في هذه الحالة ، سيكون هناك تضارب في الأسماء. لذلك ، يمكن إجراء
withHover
على المكون
withHover
، وهو السماح لمستخدم هذا المكون بتحديد الاسم الذي يجب أن تتحمله خاصية
hovering
تم تمريرها إلى المكونات. نظرًا لأن
withHover
هي مجرد وظيفة ، فلنقم بإعادة كتابتها بحيث
withHover
وسيطة ثانية تحدد اسم الخاصية ليتم تمريرها إلى المكون.
function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { const props = { [propName]: this.state.hovering } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); } } }
الآن ، وبفضل آلية المعلمة الافتراضية ES6 ، قمنا بتعيين القيمة القياسية للوسيطة الثانية على أنها
hovering
، ولكن إذا أراد مستخدم المكون
withHover
تغيير هذا ،
withHover
تمرير الاسم ، الذي يحتاج إليه في هذه الوسيطة الثانية.
function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { const props = { [propName]: this.state.hovering } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); } } } function Info ({ showTooltip, height }) { return ( <> {showTooltip === true ? <Tooltip id={this.props.id} /> : null} <svg className="Icon-svg Icon--hoverable-svg" height={height} viewBox="0 0 16 16" width="16"> <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" /> </svg> </> ) } const InfoWithHover = withHover(Info, 'showTooltip')
مشكلة في تنفيذ التمرير
ربما لاحظت مشكلة أخرى في تنفيذ
withHover
. إذا قمنا بتحليل مكون
Info
بنا ، ستلاحظ أنه ، من بين أمور أخرى ، يقبل خاصية
height
. الطريقة التي رتبنا بها كل شيء الآن تعني أنه سيتم تعيين
height
إلى
undefined
. والسبب في ذلك هو أن المكون
withHover
هو المكون المسؤول عن عرض ما يتم تمريره إليه كوسيطة
Component
. الآن نحن لا ننقل أي خصائص بخلاف
hovering
الذي
hovering
لمكون المكون.
const InfoWithHover = withHover(Info) ... return <InfoWithHover height="16px" />
يتم تمرير خاصية
height
إلى مكون
InfoWithHover
. وما هذا المكون؟ هذا هو العنصر الذي نعود منه مع
withHover
.
function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() { console.log(this.props)
داخل مكون
WithHover
this.props.height
، ولكن في المستقبل لن نقوم بأي شيء مع هذه الخاصية. نحتاج إلى تمرير هذه الخاصية إلى الوسيطة
Component
، التي نقدمها.
render() { const props = { [propName]: this.state.hovering, ...this.props, } return ( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {...props} /> </div> ); }
حول مشاكل العمل مع مكونات الطرف الثالث من أعلى مرتبة
نحن نعتقد أنك قدرت بالفعل مزايا استخدام مكونات عالية الترتيب في إعادة استخدام المنطق في مكونات مختلفة دون الحاجة إلى نسخ نفس الرمز. الآن دعونا نسأل أنفسنا إذا كانت هناك أي عيوب في مكونات النظام الأعلى. يمكن الإجابة على هذا السؤال بشكل إيجابي ، وقد التقينا بالفعل بهذه العيوب.
عند استخدام HOC ، يحدث
انقلاب التحكم . تخيل أننا نستخدم مكونًا عالي الترتيب لم نقم بتطويره ، مثل
withRouter
Router. وفقًا للوثائق ،
withRouter
match
location
history
إلى المكون الذي لفه عند عرضه.
class Game extends React.Component { render() { const { match, location, history } = this.props
يرجى ملاحظة أننا لا ننشئ عنصر
Game
(أي -
<Game />
). نحن ننقل بالكامل مكون React Router ونثق في هذا المكون ليس فقط للعرض ، ولكن أيضًا لتمرير الخصائص الصحيحة إلى المكون الخاص بنا. لقد واجهنا بالفعل هذه المشكلة من قبل عندما تحدثنا عن تعارض محتمل في الاسم عند تمرير خاصية
hovering
. ولإصلاح ذلك ، قررنا السماح
withHover
باستخدام الوسيطة الثانية لتكوين اسم الخاصية المقابلة. باستخدام
withRouter
لشخص آخر مع جهاز
withRouter
ليس لدينا مثل هذه الفرصة. إذا تم استخدام خصائص
match
أو
location
أو
history
بالفعل في مكون
Game
، فيمكننا القول أننا لم نعد محظوظين. وبالتحديد ، يتعين علينا إما تغيير هذه الأسماء في المكون الخاص بنا ، أو رفض استخدام
withRouter
.
الملخص
بالحديث عن HOC في React ، هناك شيئان مهمان يجب مراعاتهما. بادئ ذي بدء ، HOC هو مجرد نمط. لا يمكن حتى تسمية المكونات الأعلى بشيء خاص بـ React ، على الرغم من حقيقة أنها مرتبطة ببنية التطبيق. ثانيًا ، لتطوير تطبيقات React ، لا تحتاج إلى معرفة مكونات النظام الأعلى. قد تكون غير مألوف بالنسبة لهم ، لكن اكتب برامج ممتازة. ومع ذلك ، كما هو الحال في أي عمل تجاري ، كلما كان لديك المزيد من الأدوات ، كلما كانت نتيجة عملك أفضل. وإذا قمت بكتابة تطبيقات باستخدام React ، فسوف تضر نفسك دون إضافة HOC إلى ترسانتك.
أعزائي القراء! هل تستخدم مكونات النظام الأعلى في React؟
