ثورة أم ألم؟ ياندكس رد فعل هوكس تقرير

اسمي أرتيوم بيرزين ، أنا مطور للعديد من الخدمات الداخلية في ياندكس. منذ ستة أشهر ، كنت أعمل بنشاط مع React Hooks. في هذه العملية ، كانت هناك بعض الصعوبات التي كان لا بد من خوضها. الآن أريد أن أشارككم هذه التجربة. في التقرير ، درست واجهة برمجة تطبيقات React Hook API من وجهة نظر عملية - لماذا نحتاج إلى ربطات ، هل يستحق التبديل ، وهو أمر يجب مراعاته عند النقل. من السهل ارتكاب الأخطاء أثناء الانتقال ، لكن تجنبها ليس بالأمر الصعب أيضًا.



- السنانير ليست سوى طريقة أخرى لوصف منطق مكوناتك. يتيح لك إضافة بعض الميزات الوظيفية التي كانت متأصلة في السابق إلى المكونات في الفئات فقط.



بادئ ذي بدء ، هو دعم للحالة الداخلية ، ثم - دعم الآثار الجانبية. على سبيل المثال - طلبات الشبكة أو طلبات WebSocket: الاشتراك ، إلغاء الاشتراك من بعض القنوات. أو ربما نتحدث عن طلبات لبعض واجهات برمجة التطبيقات للمستعرضات غير المتزامنة أو المتزامنة. أيضا ، تتيح لنا السنانير الوصول إلى دورة حياة المكون ، إلى بداية حياته ، أي في تصاعد مستمر ، وتحديث دعائمه وموته.



ربما أسهل طريقة لتوضيح في المقارنة. هنا هو أبسط رمز يمكن أن يكون فقط مع مكون في الطبقات. العنصر هو تغيير شيء ما. هذا عداد منتظم يمكن زيادته أو إنقاصه ، فقط حقل واحد في الولاية. بشكل عام ، أعتقد أنه إذا كنت معتادًا على React ، فإن الشفرة واضحة تمامًا لك.



يبدو المكون المماثل الذي يؤدي نفس الوظيفة تمامًا ، ولكن مكتوبًا في خطافات ، أكثر إحكاما. وفقًا لحساباتي ، في المتوسط ​​، عند النقل من المكونات على الفئات إلى المكونات على الخطافات ، ينخفض ​​الرمز حوالي مرة ونصف ، ويرغب في ذلك.

بضع كلمات حول كيفية عمل السنانير. الخطاف هو وظيفة عالمية يتم الإعلان عنها داخل React ويتم استدعاؤها في كل مرة يتم فيها تقديم مكون. React يتتبع المكالمات إلى هذه الوظائف ويمكن أن يغير سلوكه أو يقرر ما يجب أن يرد.



هناك بعض القيود على استخدام السنانير التي تميزها عن الوظائف العادية. بادئ ذي بدء ، لا يمكن استخدامها في مكونات على فئات ، فقط مثل هذا التقييد ينطبق لأنه لا يتم إنشاؤها من أجلهم ، ولكن بالنسبة للمكونات الوظيفية. لا يمكن استدعاء السنانير داخل الوظائف الداخلية ، داخل الحلقات ، الظروف. فقط في المستوى الأول من التعشيش ، داخل وظائف المكون. يتم فرض هذا التقييد بواسطة React نفسها حتى تتمكن من تتبع الخطافات التي تم استدعاؤها. وهو يكدسهم بترتيب معين في دماغه. بعد ذلك ، إذا تغير هذا الترتيب فجأة أو اختفى البعض ، فمن الممكن تصحيح الأخطاء المعقدة والمراوغة والصعبة.

ولكن إذا كان لديك منطق معقد للغاية وترغب في استخدامه ، على سبيل المثال ، السنانير داخل السنانير ، فمن المرجح أن هذه علامة على أنه يجب عليك أن تصنع خطاطيفًا. افترض أنك تقوم بتوصيل عدة خطافات متصلة مع بعضها البعض في خطاف مخصص منفصل. وداخله يمكنك استخدام السنانير المخصصة الأخرى ، وبالتالي بناء تسلسل هرمي من السنانير ، وتسليط الضوء على المنطق العام هناك.



السنانير توفر بعض المزايا على الطبقات. بادئ ذي بدء ، على النحو التالي من السابق ، باستخدام السنانير المخصصة ، يمكنك تبخير المنطق أسهل بكثير. في السابق ، باستخدام النهج مع مكونات النظام العالي ، وضعنا نوعًا من المنطق المشترك ، وكان التفافًا على المكون. الآن نضع هذا المنطق داخل السنانير. وبالتالي ، يتم تقليل شجرة المكونات: يتم تقليل تداخلها ، ويصبح من السهل على React تتبع تغييرات المكون ، وإعادة حساب الشجرة ، وإعادة حساب DOM الظاهري ، وما إلى ذلك. هذا يحل مشكلة ما يسمى بلفائف الجحيم. أولئك الذين يعملون مع Redux ، على ما أعتقد ، يعرفون هذا.

من السهل التقليل من الكود المكتوب باستخدام الخطافات باستخدام المصغرات الحديثة مثل Terser أو UglifyJS القديمة. الحقيقة هي أننا لسنا بحاجة إلى حفظ أسماء الطرق ، نحن لسنا بحاجة للتفكير في النماذج الأولية. بعد عملية التحويل ، إذا كان الهدف هو ES3 أو ES5 ، فعادة ما نحصل على مجموعة من النماذج الأولية التي تصحيحات. هنا كل هذا لا يحتاج إلى القيام به ، وبالتالي فمن الأسهل للتقليل. ونتيجة لعدم استخدام الفصول ، لا نحتاج إلى التفكير في هذا الأمر. بالنسبة للمبتدئين ، غالبًا ما تكون هذه مشكلة كبيرة ، وربما أحد الأسباب الرئيسية للخلل: ننسى أن هذه قد تكون نافذة ، ونحن بحاجة إلى ربط طريقة ، على سبيل المثال ، في المُنشئ أو بطريقة أخرى.

أيضا ، يتيح لك استخدام السنانير إبراز المنطق الذي يتحكم في أي تأثير جانبي واحد. في السابق ، كان يجب تقسيم هذا المنطق ، خاصةً عندما يكون لدينا العديد من الآثار الجانبية للمكون ، إلى طرق مختلفة من دورة حياة المكون. ونظرًا لظهور خطاطيف التقليل ، ظهرت React.memo ، والآن أصبحت المكونات الوظيفية مناسبة للتذكير ، أي لن يتم إعادة إنشاء هذا المكون أو تحديثه معنا إذا لم تتغير الدعائم الخاصة به. هذا لا يمكن القيام به من قبل ، والآن أصبح من الممكن. يمكن لف جميع المكونات الوظيفية في المذكرة. أيضًا ظهر خطاب useMemo ، والذي يمكننا استخدامه لحساب بعض القيم الثقيلة ، أو إنشاء مثيل لبعض فئات الأدوات مرة واحدة فقط.

سيكون التقرير غير مكتمل إذا لم أتحدث عن بعض السنانير الأساسية. بادئ ذي بدء ، هذه هي السنانير إدارة الدولة.



بادئ ذي بدء - useState.



مثال يشبه المثال الموجود في بداية التقرير. useState هي وظيفة تأخذ قيمة مبدئية وتُرجع مجموعة من القيمة الحالية ووظيفة لتغيير تلك القيمة. يتم تقديم كل السحر بواسطة React داخليًا. يمكننا ببساطة قراءة هذه القيمة أو تغييرها.

على عكس الفئات ، يمكننا استخدام العديد من كائنات الحالة التي نحتاجها ، ويقسم الحالة إلى أجزاء منطقية حتى لا يتم مزجها في كائن واحد ، كما في الفئات. وسيتم عزل هذه القطع تمامًا عن بعضها البعض: يمكن تغييرها بشكل مستقل عن بعضها البعض. النتيجة ، على سبيل المثال ، من هذا الرمز: نحن نغير متغيرين ، نحسب أزرار العرض والنتائج التي تسمح لنا بتغيير المتغير الأول هنا وهناك ، والمتغير الثاني هنا وهناك. تذكر هذا المثال ، لأنه بعد ذلك سنفعل شيئًا مشابهًا ، لكن أكثر تعقيدًا.



هناك مثل useState على المنشطات لعشاق Redux. يسمح لك بتغيير الحالة بشكل أكثر اتساقًا باستخدام المخفض. أعتقد أن أولئك الذين هم على دراية بـ Redux لا يمكنهم حتى شرح ذلك ، بالنسبة لأولئك الذين ليسوا على دراية ، سأقول لهم.

المخفض هو وظيفة تقبل الحالة ، وبعض الكائنات ، التي تسمى عادةً الإجراء ، تصف كيفية تغيير هذه الحالة. بتعبير أدق ، فإنه يمر ببعض المعلمات ، وداخل المخفض الذي يقرره بالفعل ، اعتمادًا على المعلمات الخاصة بهم ، وكيف ستتغير الحالة ، ونتيجة لذلك ، يجب إعادة حالة جديدة وتحديثها.



بهذه الطريقة تقريبًا يتم استخدامه في رمز المكون. لدينا خطاف useReducer ، وهو يأخذ وظيفة المخفض ، والمعلمة الثانية هي القيمة الأولية للدولة. يتم إرجاع ، مثل useState والحالة الحالية والدالة لتغييره. إذا قمت بتمرير كائن إجراء لإرساله ، فسنقوم باستدعاء تغيير الحالة.



مهم جدا استخدامتأثير هوك. يسمح لك بإضافة تأثيرات جانبية للمكون ، مما يوفر بديلاً لدورة الحياة. في هذا المثال ، نستخدم طريقة بسيطة مع useEffect: إنها تطلب فقط بعض البيانات من الخادم ، مع واجهة برمجة التطبيقات ، على سبيل المثال ، ونعرض هذه البيانات على الصفحة.



يحتوي UseEffect على وضع متقدم ، وذلك عندما تقوم الدالة التي تم تمريرها إلى useEffect بإرجاع بعض الوظائف الأخرى ، ثم سيتم استدعاء هذه الوظيفة في الدورة التالية ، عندما يتم تطبيق هذا useEffect.

لقد نسيت أن أذكر ، يسمى useEffect بشكل غير متزامن ، مباشرة بعد تطبيق التغيير على DOM. بمعنى أنه يضمن تنفيذه بعد تقديم المكون ، ويمكن أن يؤدي إلى العرض التالي في حالة تغيير بعض القيم.



هنا نلتقي لأول مرة بمفهوم مثل التبعيات. بعض السنانير - useEffect ، useCallback ، useMemo - تأخذ مجموعة من القيم كالوسيطة الثانية ، مما سيتيح لنا تحديد ما يجب تتبعه. التغييرات في هذه المجموعة تؤدي إلى نوع من الآثار. على سبيل المثال ، هنا ، من الناحية الافتراضية ، لدينا نوع من المكونات لاختيار مؤلف من قائمة ما. ولوحة مع كتب هذا المؤلف. وعندما يتغير المؤلف ، سيتم استدعاء useEffect. عند تغيير هذا المؤلف ، سيتم استدعاء طلب وسيتم تحميل الكتب.

أذكر أيضًا في تمرير السنانير مثل useRef ، هذا بديل لـ React.createRef ، وهو شيء مشابه ل useState ، لكن التغييرات في المرجع لا تؤدي إلى العرض. مريحة في بعض الأحيان لبعض الخارقة. useImperativeHandle يسمح لنا بالإعلان عن بعض "الأساليب العامة" على المكون. إذا كنت تستخدم useRef في المكون الأصل ، فيمكنها سحب هذه الطرق. أن نكون صادقين ، لقد جربتها مرة واحدة لأغراض تعليمية ، في الممارسة العملية لم تكن مفيدة. useContext هو مجرد شيء جيد ، فهو يسمح لك بأخذ القيمة الحالية من السياق إذا كان الموفر قد حدد هذه القيمة أعلى في مكان ما في مستوى التسلسل الهرمي.

هناك طريقة واحدة لتحسين تطبيقات React على السنانير ؛ هذه هي الحفظ. يمكن تقسيم المذكرة إلى داخلية وخارجية. أولا عن الخارج.



هذا هو React.memo ، عملياً بديل لفئة React.PureComponent ، التي تتبع التغييرات في الدعائم والمكونات التي تم تغييرها فقط عند تغيير الدعائم أو الحالة.

هنا ، شيء مشابه ، مع ذلك ، بدون دولة. كما تراقب التغييرات في الدعائم ، وإذا تغيرت الدعائم ، يحدث العارض. إذا لم تتغير الدعائم ، فلن يتم تحديث المكون ، ونحن نحفظه في هذا.



الطرق الداخلية للتحسين. بادئ ذي بدء ، هذا شيء منخفض المستوى - useMemo ، نادرًا ما يستخدم. يسمح لك بحساب بعض القيمة وإعادة حسابها فقط إذا تغيرت القيم المحددة في التبعيات.



هناك حالة خاصة من useMemo لوظيفة تسمى useCallback. يستخدم بشكل أساسي لتدوين قيمة وظائف معالج الأحداث التي سيتم تمريرها إلى المكونات الفرعية بحيث لا يمكن تقديم هذه المكونات الفرعية مرة أخرى. يتم استخدامه ببساطة. نحن نصف وظيفة معينة ، نلفها في useCallback ، ونشير إلى المتغيرات التي يعتمد عليها.

كثير من الناس لديهم سؤال ، لكن هل نحن بحاجة لهذا؟ هل نحن بحاجة إلى السنانير؟ هل نتحرك أو نبقى كما كان من قبل؟ لا توجد إجابة واحدة ، كل هذا يتوقف على التفضيلات. بادئ ذي بدء ، إذا كنت مرتبطًا بشكل صارم بالبرمجة الموجهة للكائنات ، وإذا كانت مكوناتك ، معتادًا على كونك فئة ، فلديها طرق يمكن سحبها ، ومن ثم ، قد يبدو هذا الشيء غير ضروري بالنسبة لك. من حيث المبدأ ، بدا لي عندما سمعت لأول مرة عن السنانير أنه كان مفرطًا في التعقيد ، تمت إضافة نوع من السحر ، ولم يكن من الواضح سبب ذلك.

بالنسبة لعشاق الوظائف ، يجب أن يكون هذا الأمر ضروريًا ، لأن الخطافات وظائف ، وتقنيات البرمجة الوظيفية قابلة للتطبيق عليها. على سبيل المثال ، يمكنك الجمع بينهما أو القيام بأي شيء على الإطلاق ، باستخدام ، على سبيل المثال ، المكتبات مثل Ramda وما شابه.



منذ تخلصنا من الفصول ، لم نعد بحاجة إلى ربط هذا السياق بالطرق. إذا كنت تستخدم هذه الأساليب بمثابة عمليات الاسترجاعات. عادةً ما كانت هذه مشكلة ، لأنه كان عليك أن تتذكر ربطها في المُنشئ ، أو استخدام ملحق غير رسمي في بناء جملة اللغة ، مثل وظائف السهم كخاصية. ممارسة شائعة جدا. لقد استخدمت الديكور الخاص بي ، والذي هو أيضًا ، من حيث المبدأ ، على الطرق.



هناك فرق في كيفية عمل دورة الحياة ، وكيفية إدارتها. تربط Hooks جميع إجراءات دورة الحياة تقريبًا مع useEffect hook ، والذي يسمح لك بالاشتراك في ولادة أحد المكونات وتحديثها ووفاته. في الفصول الدراسية ، لهذا ، كان علينا إعادة تعريف عدة طرق ، مثل componentDidMount و componentDidUpdate و componentWillUnmount. أيضاً ، يمكن الآن استبدال الأسلوب shouldComponentUpdate بـ React.memo.



هناك فرق صغير إلى حد ما في كيفية التعامل مع الدولة. أولاً ، تحتوي الفئات على كائن حالة واحد. كان علينا أن نحشر أي شيء هناك. في الخطافات ، يمكننا تقسيم الحالة المنطقية إلى بعض القطع ، وهو ما سيكون مناسبًا لنا للعمل بشكل منفصل.

setState () من المكونات على الفئات المسموح بها لتحديد تصحيح حالة ، وبالتالي تغيير واحد أو أكثر من حقول الحالة. في السنانير ، يتعين علينا تغيير الحالة بأكملها ككل ، وهذا أمر جيد ، لأنه من المألوف استخدام جميع أنواع الأشياء غير القابلة للتغيير ولا نتوقع أبدًا تغيير كائناتنا. هم دائما جديد معنا.

الميزة الرئيسية للفئات التي لا تحتوي على السنانير: يمكننا الاشتراك في تغييرات الحالة. أي أننا نغير الحالة ، ونشترك فورًا في تغييراتها ، ونقوم بمعالجة شيء فورًا بعد تطبيق التغييرات. في السنانير ، هذا لا يعمل فقط. يجب القيام بذلك بطريقة مثيرة للاهتمام للغاية ، وسأخبرك أكثر.

والقليل عن الطريقة الوظيفية للتحديث. إنه يعمل هناك وهناك ، عندما تقبل وظائف تغيير الحالة وظيفة أخرى ، لا ينبغي أن تتغير هذه الحالة ، بل تخلقها. وإذا كان في حالة مكون الفئة يمكنه إرجاع نوع من التصحيح إلينا ، ثم في السنانير يجب أن نعيد القيمة الجديدة بالكامل.

بشكل عام ، من غير المرجح أن تحصل على إجابة سواء كنت تتحرك أم لا. لكنني أنصح على الأقل لمحاولة ، على الأقل لرمز جديد ، ليشعر به. عندما بدأت للتو العمل مع السنانير ، حددت على الفور العديد من السنانير المخصصة المناسبة لنفسي لمشروعي. في الأساس ، حاولت استبدال بعض الميزات التي قمت بتطبيقها من خلال مكونات النظام العالي.



useDismounted - بالنسبة لأولئك الذين لديهم دراية بـ RxJS ، هناك فرصة لإلغاء الاشتراك على نطاق واسع من كل ما يمكن ملاحظته داخل مكون واحد ، أو داخل وظيفة واحدة ، عن طريق الاشتراك في كل ملاحظة إلى كائن خاص ، موضوع ، وعندما يتم إغلاقه ، يتم إلغاء جميع الاشتراكات. يكون هذا مناسبًا جدًا إذا كان المكون معقدًا ، وإذا كان هناك العديد من العمليات غير المتزامنة داخل Observable ، فمن الملائم إلغاء الاشتراك من كل مرة ، وليس من كل على حدة.

useObservable بإرجاع قيمة من Observable عندما تظهر واحدة جديدة هناك. استخدام مماثل BehaviourSubject إرجاع من BehaviourSubject. الفرق بينه وبين الملاحظة هو أنه في البداية له بعض المعاني.

يتيح لنا الخطاف المخصص المريح useDebouncedValue أن ننظم ، على سبيل المثال ، مقترحًا لسلسلة البحث ، بحيث لا تقوم في كل مرة تضغط فيها على مفتاح ما بإرسال شيء ما إلى الخادم ، ولكن الانتظار حتى ينتهي المستخدم من الكتابة.

اثنين من السنانير مماثلة. useWindowResize بإرجاع القيم الفعلية الحالية لأحجام الإطارات. الخطاف التالي لموضع التمرير هو useWindowScroll. يمكنني استخدامها لإعادة فرز بعض النوافذ المنبثقة أو الإطارات المشروطة ، إذا كان هناك أي أشياء معقدة لا يمكن القيام بها باستخدام CSS.

ومثل هذا الخطاف الصغير لتنفيذ المفاتيح الساخنة ، وهو المكون ، عندما يكون موجودًا على الصفحة ، فإنه مشترك في بعض المفاتيح الساخنة. عندما يموت ، يحدث إلغاء الاشتراك التلقائي.

ما هي هذه السنانير المخصصة مريحة ل؟ أن نتمكن من حشر إلغاء الاشتراك داخل الخطاف ، ولا يتعين علينا التفكير في إلغاء الاشتراك يدويًا في مكان ما في المكون الذي يستخدم فيه هذا الخطاف.

منذ وقت ليس ببعيد ، ألقوا بي رابطًا إلى مكتبة استخدام التفاعل ، واتضح أن معظم هذه الروابط المخصصة قد تم تنفيذها بالفعل هناك. و كتبت دراجة. هذا مفيد في بعض الأحيان ، لكن في المستقبل ، على الأرجح ، سأطرحهم على الأرجح وأستخدم تفاعلي. كما أنصحك أيضًا بمعرفة ما إذا كنت تنوي استخدام الخطافات.



في الواقع ، فإن الهدف الرئيسي للتقرير هو إظهار كيفية الكتابة بشكل غير صحيح ، وما هي المشاكل التي يمكن أن تكون وكيفية تجنبها. أول شيء ، ربما أي شخص يدرس هذه السنانير ويحاول كتابة شيء ما ، هو استخدام useEffect بشكل غير صحيح. إليك الرمز المماثل الذي كتبه الجميع بنسبة 100٪ إذا جربوا السنانير. ويرجع ذلك إلى حقيقة أن useEffect ينظر إليها في البداية عقليا ، كبديل لـ componentDidMount. ولكن ، على عكس componentDidMount ، والذي يتم استدعاؤه مرة واحدة فقط ، يتم استدعاء useEffect على كل تجسيد. والخطأ هنا هو أنه يتغير ، على سبيل المثال ، متغير البيانات ، وفي الوقت نفسه ، يؤدي تغييره إلى تقديم مكون ، نتيجة لذلك ، سيتم إعادة طلب التأثير. وبالتالي ، نحصل على سلسلة لا نهائية من طلبات AJAX إلى الخادم ، ويقوم المكون نفسه بتحديث التحديثات والتحديثات باستمرار.



إصلاحها بسيط للغاية. تحتاج إلى إضافة مجموعة فارغة من تلك التبعيات التي يعتمد عليها ، والتغييرات التي ستعيد تشغيل التأثير. إذا كانت لدينا قائمة فارغة من التبعيات المحددة هنا ، فلن يتم إعادة تشغيل التأثير ، وفقًا لذلك. هذا ليس نوعًا من الاختراق ، إنه ميزة أساسية لاستخدام useEffect.



دعنا نقول أننا إصلاحه. الآن معقدة بعض الشيء. لدينا مكون يعرض شيئًا ما يجب أخذه من الخادم للحصول على نوع ما من الهوية. في هذه الحالة ، من حيث المبدأ ، كل شيء يعمل بشكل جيد حتى نغير الكيان في الوالد ، وربما هذا ليس له صلة بالمكون الخاص بك.



ولكن على الأرجح ، إذا تغيرت أو كانت هناك حاجة إلى تغييره ، وكان لديك مكون قديم على صفحتك واتضح أنه لا يتم تحديثه ، فمن الأفضل أن تضيف الكيان - هنا ، تبعية ، تسبب التحديث ، وتحديث البيانات.



مثال أكثر تعقيدًا مع useCallback. هنا ، للوهلة الأولى ، كل شيء على ما يرام. لدينا صفحة معينة تحتوي على نوع من مؤقت العد التنازلي ، أو ، على العكس من ذلك ، مؤقت مؤقت. على سبيل المثال ، قائمة المضيفين ، وفي المقدمة عوامل تصفية تسمح لك بتصفية قائمة المضيفين هذه. حسنًا ، تمت إضافة الصيانة هنا فقط لتوضيح القيمة المتغيرة باستمرار والتي تترجم إلى عارض.

, , maintenance , , , onChange. onChange, . , HostFilters - , , dropdown, . , . , .



onChange useCallback. , .

, . , , . Facebook, React. , , , , '. , , confusing .



? — , - , , , , , . .

, , , , , , . , Garbage Collector , . , , , , . , , , reducer, , . , .

, , . - , , setValue - , , setState . - useEffect.

useEffect - , - , , , useEffect. useEffect , . , , Backbone, : , , , - . , , - , . - . , , , , - . , , , , , , . .

, , . , , . , . , . , , , dropdown . , . dropdown pop-up, useWindowScroll, useWindowResize , . , , — , .

, , . , , , , , . , , , , , .



, «», . , , TypeScript . . , reducer Redux , action. , action , action. , , , .

. , action. , , IncrementA 0, 1, 2, . . , , , , . action action, - . UnionType “Action”, , , action. .

— . , initialState, . , - . TypeScript. . , typeState , initialState.



reducer. State, Action, : switch action.type. TypeScript UnionType: case, - , type. action .

, : , , . .



? , . . , reducer. , action creator , , dispatch.



extension Dev Tools. . .

, , . , , . useDebugValue , - Dev Tool. useConstants, - , loaded, , , .



— . , . , . , , , . , , — - , — .

. Facebook ESLint, . , , . , dependencies . , , , .

, , , - , . , , , . . , - - .

— , , - . , , . , , - . , . . :

Source: https://habr.com/ru/post/ar464071/


All Articles