مرحبا يا هبر.
بالضبط سنة مرت منذ أن بدأت دراسة React. خلال هذا الوقت ، تمكنت من إصدار العديد من تطبيقات الهاتف المحمول الصغيرة المكتوبة في React Native ، والمشاركة في تطوير تطبيق ويب باستخدام ReactJS. بإيجاز والنظر إلى كل تلك المكابس التي تمكنت من التقدم إليها ، كان لدي فكرة للتعبير عن تجربتي في شكل مقال. سأحفظ أنه قبل البدء في دراسة رد الفعل ، كانت لدي خبرة تطوير مدتها 3 سنوات في c ++ ، python ، بالإضافة إلى رأي مفاده أنه لا يوجد شيء معقد في التطور الأمامي ولن يكون من الصعب فهم كل شيء. لذلك ، أهملت في الأشهر الأولى قراءة الأدب التربوي وأساسا مجرد أمثلة مدونة جاهزة من google. وفقًا لذلك ، على الأرجح أن مطورًا مثاليًا يدرس الوثائق أولاً وقبل كل شيء ، لن يجد شيئًا جديدًا لنفسه هنا ، لكن ما زلت أعتقد أن عددًا قليلاً من الناس عند دراسة التكنولوجيا الجديدة يفضلون الطريق من الممارسة إلى النظرية. لذلك إذا كانت هذه المقالة تنقذ شخصًا ما من أشعل النار ، فعندئذٍ لم أذهب عبثا
نصيحة 1. العمل مع النماذج
الموقف الكلاسيكي: هناك نموذج يحتوي على عدة حقول يدخل فيها المستخدم البيانات ، ثم ينقر فوق الزر ، ويتم إرسال البيانات المدخلة إلى واجهة برمجة تطبيقات خارجية / محفوظة في الحالة / معروضة على الشاشة - تسطير ما يلزم.
الخيار 1. كيف لا تفعل
في React ، يمكنك إنشاء ارتباط لعقدة DOM أو مكون React.
this.myRef = React.createRef();
باستخدام سمة المرجع ، يمكن إرفاق الرابط الذي تم إنشاؤه بالمكون / العقدة المطلوبة.
<input id="data" type="text" ref={this.myRef} />
وبالتالي ، يمكن حل المشكلة أعلاه عن طريق إنشاء مرجع لكل حقل في النموذج ، وفي نص الوظيفة الذي يُدعى عند النقر فوق الزر ، احصل على البيانات من النموذج عن طريق الاتصال بالروابط الضرورية.
class BadForm extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.onClickHandler = this.onClickHandler.bind(this); } onClickHandler() { const data = this.myRef.current.value; alert(data); } render() { return ( <> <form> <label htmlFor="data">Bad form:</label> <input id="data" type="text" ref={this.myRef} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } }
كيف يمكن أن يحاول القرد الداخلي تبرير هذا القرار:- الشيء الرئيسي الذي يعمل ، لا يزال لديك 100500 مهمة ، ولا
تتم مشاهدة البرامج التلفزيونية ؛ لا يتم إغلاق التذاكر. اترك الأمر هكذا ، ثم غيره - تعرف على مقدار الكود القليل المطلوب لمعالجة النموذج. المرجع المعلن والوصول إلى البيانات أينما تريد.
- إذا قمت بتخزين القيمة في الحالة ، ثم في كل مرة تقوم فيها بتغيير بيانات الإدخال ، سيتم تقديم التطبيق بأكمله مرة أخرى ، وستحتاج فقط إلى البيانات النهائية. لذلك تبين أن هذه الطريقة جيدة للتحسين ، فقط اتركها بهذه الطريقة.
لماذا القرد خاطئ:المثال أعلاه هو antipattern الكلاسيكية في React ، والذي ينتهك مفهوم دفق البيانات أحادي الاتجاه. في هذه الحالة ، لن يتمكن تطبيقك من الاستجابة لتغيرات البيانات أثناء الإدخال ، نظرًا لعدم تخزينها في الولاية.
الخيار 2. الحل الكلاسيكي
لكل حقل نموذج ، يتم إنشاء متغير في الحالة التي سيتم تخزين نتيجة الإدخال فيها. يتم تعيين سمة القيمة هذا المتغير. يتم تعيين سمة onhange على وظيفة يتم فيها تغيير قيمة المتغير في الحالة من خلال setState (). وبالتالي ، يتم أخذ جميع البيانات من الحالة ، وعندما تتغير البيانات ، تتغير الحالة ويتم تقديم التطبيق مرة أخرى.
class GoodForm extends React.Component { constructor(props) { super(props); this.state = { data: '' }; this.onChangeData = this.onChangeData.bind(this); this.onClickHandler = this.onClickHandler.bind(this); } onChangeData(event) { this.setState({ data: event.target.value }); } onClickHandler(event) { const { data } = this.state; alert(data); } render() { const { data } = this.state; return ( <> <form> <label htmlFor="data">Good form:</label> <input id="data" type="text" value={data} onChange={this.onChangeData} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } }
الخيار 3. متقدم. عندما تصبح الأشكال كثيرة
يحتوي الخيار الثاني على عدد من العيوب: كمية كبيرة من التعليمات البرمجية القياسية ، لكل حقل من الضروري إعلان أسلوب onChange وإضافة متغير إلى الحالة. عندما يتعلق الأمر بالتحقق من صحة البيانات المدخلة وعرض رسائل الخطأ ، يزيد مقدار الشفرة أكثر. لتسهيل العمل باستخدام النماذج ، توجد مكتبة
Formik ممتازة تهتم بالمشكلات المتعلقة بصيانة النماذج ، كما تسهل إضافة نظام التحقق من الصحة.
import React from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; const SigninSchema = Yup.object().shape({ data: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Data required'), }); export default () => ( <div> <Formik initialValues={{ data: '' }} validationSchema={SigninSchema} onSubmit={(values) => { alert(values.data); }} render={(props) => ( <form onSubmit={props.handleSubmit}> <label>Formik form:</label> <input type="text" onChange={props.handleChange} onBlur={props.handleBlur} value={props.values.data} name="data" /> {props.errors.data && props.touched.data ? ( <div>{props.errors.data}</div> ) : null} <button type="submit">Ok</button> </form> )} /> </div> );
نصيحة 2. تجنب طفرة
النظر في تطبيق قائمة المهام البسيطة. في المنشئ ، نحدد في الحالة المتغير الذي سيتم تخزين قائمة المهام به. في طريقة التقديم () ، اعرض النموذج الذي من خلاله سنضيف حالات إلى القائمة. الآن النظر في كيف يمكننا تغيير الدولة.
خيار خاطئ يؤدي إلى طفرة الصفيف: this.state.data.push(item);
في هذه الحالة ، تم تغيير المصفوفة بالفعل ، لكن React لا تعرف شيئًا عنها ، مما يعني أنه لن يتم استدعاء طريقة التقديم () ، ولن يتم عرض تغييراتنا. الحقيقة هي أنه في JavaScript ، عند إنشاء صفيف أو كائن جديد ، يتم حفظ الرابط في المتغير وليس الكائن نفسه. وبالتالي ، بإضافة عنصر جديد إلى صفيف البيانات ، فإننا نقوم بتغيير الصفيف نفسه ، ولكن ليس الرابط الخاص به ، مما يعني أن قيمة البيانات المخزنة في الحالة لن تتغير.
يمكن مواجهة الطفرات في جافا سكريبت في كل منعطف. لتجنب حدوث طفرات في البيانات ، استخدم معامل الانتشار لأي من المصفوفات ، وطرق المرشح () و map () ، وبالنسبة للكائنات ، استخدم معامل الانتشار أو طريقة التخصيص ().
const newData = [...data, item]; const copy = Object.assign({}, obj);
بالعودة إلى تطبيقنا ، تجدر الإشارة إلى أن الخيار الصحيح لتغيير الحالة هو استخدام طريقة setState (). لا تحاول تغيير الحالة مباشرة في أي مكان آخر غير المنشئ ، لأن هذا يتناقض مع أيديولوجية React.
لا تفعل ذلك! this.state.data = [...data, item];
أيضا تجنب طفرة الدولة. حتى إذا كنت تستخدم setState () ، يمكن أن تؤدي الطفرات إلى الأخطاء عند محاولة تحسينها. على سبيل المثال ، إذا قمت بتمرير كائن متحور من خلال الدعائم إلى PureComponent التابع ، فلن يتمكن هذا المكون من فهم أن الدعائم المستلمة قد تغيرت ولن يتم إعادة عرضها.
لا تفعل ذلك! this.state.data.push(item); this.setState({ data: this.state.data });
الخيار الصحيح: const { data } = this.state; const newData = [...data, item]; this.setState({ data: newData });
ولكن حتى الخيار أعلاه يمكن أن يؤدي إلى أخطاء خفية. والحقيقة هي أنه لا أحد يضمن أنه خلال الوقت المنقضي بين تلقي متغير البيانات من الحالة وكتابة قيمته الجديدة إلى الحالة ، لن تتغير الحالة نفسها. وبالتالي ، فإنك تخاطر بفقد بعض التغييرات التي تم إجراؤها. لذلك ، في حالة الحاجة إلى تحديث قيمة المتغير في الحالة باستخدام قيمته السابقة ، فقم بذلك كما يلي:
الخيار الصحيح ، إذا كان الشرط التالي يعتمد على التيار: this.setState((state) => { return {data: [...state.data, item]}; });
نصيحة 3. محاكاة تطبيق متعدد الصفحات
يتم تطوير التطبيق الخاص بك ، وفي مرحلة ما تدرك أنك بحاجة إلى صفحات متعددة. ولكن ماذا تفعل ، لأن React هو تطبيق صفحة واحدة؟ في هذه المرحلة ، قد تتبادر إلى ذهنك الفكرة المجنونة التالية. أنت تقرر أنك ستحتفظ بمعرف الصفحة الحالية في الحالة العامة للتطبيق الخاص بك ، على سبيل المثال ، باستخدام مخزن الإرجاع. لعرض الصفحة المطلوبة ، سوف تستخدم التجسيد المشروط ، والتبديل بين الصفحات ، استدعاء الإجراء مع الحمولة المطلوبة ، وبالتالي تغيير القيم في مخزن المخزن.
App.js import React from 'react'; import { connect } from 'react-redux'; import './App.css'; import Page1 from './Page1'; import Page2 from './Page2'; const mapStateToProps = (state) => ({ page: state.page }); function AppCon(props) { if (props.page === 'Page1') { return ( <div className="App"> <Page1 /> </div> ); } return ( <div className="App"> <Page2 /> </div> ); } const App = connect(mapStateToProps)(AppCon); export default App;
Page1.js import React from 'react'; import { connect } from 'react-redux'; import { setPage } from './redux/actions'; function mapDispatchToProps(dispatch) { return { setPageHandle: (page) => dispatch(setPage(page)), }; } function Page1Con(props) { return ( <> <h3> Page 1 </h3> <input type="button" value="Go to page2" onClick={() => props.setPageHandle('Page2')} /> </> ); } const Page1 = connect(null, mapDispatchToProps)(Page1Con); export default Page1;
لماذا هذا سيء؟
- هذا الحل هو مثال للدراجة البدائية. إذا كنت تعرف كيفية صنع مثل هذه الدراجة بكفاءة وفهم ما الذي تنوي القيام به ، فليس لي أن أنصحك. خلاف ذلك ، ستكون التعليمات البرمجية الخاصة بك ضمنية ومربكة ومعقدة للغاية.
- لن تتمكن من استخدام زر الرجوع في المتصفح ، حيث لن يتم حفظ سجل الزيارات.
كيفية حل هذا؟
مجرد استخدام
رد فعل الموجه . هذه حزمة رائعة يمكنها بسهولة تحويل التطبيق الخاص بك إلى ملف متعدد الصفحات.
نصيحة 4. أين تضع طلبات api
في مرحلة ما ، كنت بحاجة إلى إضافة طلب إلى api خارجي في التطبيق الخاص بك. وهنا تتساءل: أين في التطبيق الخاص بك تحتاج إلى تنفيذ الطلب؟
في الوقت الحالي ، عند تركيب مكون React ، تكون دورة حياته كما يلي:
- منشئ ()
- ثابت getDerivedStateFromProps ()
- تقديم ()
- componentDidMount ()
دعنا نحلل جميع الخيارات بالترتيب.
في طريقة المُنشئ () ، لا تنصح
الوثائق بالقيام بأي شيء آخر بخلاف:
- تهيئة الحالة الداخلية من خلال تعيين كائن this.state.
- ارتباطات معالجات الأحداث إلى مثيل.
لا يتم تضمين المكالمات إلى api في هذه القائمة ، لذلك دعنا ننتقل.
توجد طريقة getDerivedStateFromProps () وفقًا
للوثائق لمواقف نادرة عندما تعتمد الحالة على التغييرات في الدعائم. مرة أخرى ، وليس قضيتنا.
الخطأ الأكثر شيوعًا هو موقع الكود الذي ينفذ طلبات api في طريقة التقديم (). يؤدي هذا إلى حقيقة أنه بمجرد تنفيذ طلبك بنجاح ، من المرجح أن تقوم بحفظ النتيجة في حالة المكون ، وسيؤدي ذلك إلى استدعاء جديد لطريقة التقديم () ، حيث سيتم تنفيذ طلبك إلى api مرة أخرى. وبالتالي ، سينتهي المكون الخاص بك في عرض لا نهاية له ، ومن الواضح أن هذا ليس هو ما تحتاجه.
وبالتالي فإن طريقة componentDidMount () هي المكان المثالي للوصول إلى api الخارجي.
استنتاج
أمثلة التعليمات البرمجية يمكن العثور عليها على
جيثب .