نقوم بصياغة استراتيجية للتعامل مع الأخطاء في React

كيفية جعل سقوط لينة؟




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

القضايا وتحديد الأهداف


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

دعنا ندرس لترتيب المشاكل التي واجهناها:

  1. حتى إذا كان الخطأ ضئيلًا أو مترجمًا داخل الوحدة النمطية ، في أي حال ، يصبح التطبيق بأكمله غير فعال
    قبل الإصدار 16 من React ، لم يكن لدى المطورين آلية واحدة لملائمة الأخطاء القياسية وكانت هناك مواقف عندما أدى تلف البيانات إلى انخفاض في العرض فقط في الخطوات التالية أو سلوك تطبيق غريب. تعامل كل مطور مع الأخطاء لأنه كان معتادًا على ذلك ، كما أن النموذج الضروري مع try / catch لم يتناسب بشكل عام مع مبادئ التصريح React. في الإصدار 16 ، ظهرت أداة Error Boundaries ، التي حاولت حل هذه المشكلات ، سننظر في كيفية تطبيقها.
  2. الخطأ مستنسخ فقط في بيئة الإنتاج أو لا يمكن استنساخه دون بيانات إضافية.
    في عالم مثالي ، تكون بيئة التطوير هي نفس بيئة الإنتاج ، ويمكننا إعادة إنتاج أي خلل محليًا ، لكننا نعيش في العالم الحقيقي. لا توجد أدوات تصحيح على نظام القتال. من الصعب وليس مثمرًا اكتشاف مثل هذه الحوادث ، في الأساس يجب عليك التعامل مع الشفرة الغامضة ونقص المعلومات حول الخطأ ، وليس مع جوهر المشكلة. لن ننظر في مسألة كيفية التقريب بين شروط بيئة التطوير وظروف الإنتاج ، ومع ذلك ، سننظر في الأدوات التي تتيح لك الحصول على معلومات مفصلة حول الحوادث التي وقعت.

كل هذا يقلل من سرعة التطوير وولاء المستخدم لمنتج البرنامج ، لذلك حددت لنفسي أهم ثلاثة أهداف:

  1. تحسين تجربة المستخدم مع التطبيق في حالة وجود أخطاء ؛
  2. تقليل الوقت بين الدخول في الإنتاج والكشف عنه ؛
  3. تسريع عملية البحث عن وتصحيح المشاكل في التطبيق للمطور.

ما المهام التي تحتاج إلى حل؟

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

معالجة الأخطاء الحرجة

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



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

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

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

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

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



لدينا لوحة تنقل مكونة من 3 روابط ، يؤدي كل منها إلى مكونات مستقلة عن بعضها البعض ، لذلك نريد تحقيق مثل هذا السلوك حتى إذا لم يعمل أحد المكونات ، فيمكننا الاستمرار في العمل مع الباقي.

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

النظر في سرد ​​التطبيق بأكمله الذي يتم التفاف في ErrorBoundary

const AppWithBoundary = () => (   <ErrorBoundary errorMessage="Application has crashed">     <App/>   </ErrorBoundary> ) 

 function App() {  return (    <Router>      <Layout>        <Sider width={200}>          <SideNavigation />        </Sider>        <Layout>          <Header>            <ActionPanel />          </Header>          <Content>            <Switch>              <Route path="/link1">                <Page1                  title="Link 1 content page"                  errorMessage="Page for link 1 crashed"                />              </Route>              <Route path="/link2">                <Page2                  title="Link 2 content page"                  errorMessage="Page for link 2 crashed"                />              </Route>              <Route path="/link3">                <Page3                  title="Link 3 content page"                  errorMessage="Page for link 3 crashed"                />              </Route>              <Route path="/">                <MainPage                  title="Main page"                  errorMessage="Only main page crashed"                />              </Route>            </Switch>          </Content>        </Layout>      </Layout>    </Router>  ); } 

لا يوجد أي سحر في ErrorBoundary ، إنه مجرد مكون صفي يتم فيه تعريف طريقة componentDidCatch ، أي أنه يمكن إجراء أي مكون ErrorBoundary ، إذا قمت بتعريف هذه الطريقة فيه.

 class ErrorBoundary extends React.Component {  state = {    hasError: false,  }  componentDidCatch(error) {    //            this.setState({ hasError: true });  }  render() {    if (this.state.hasError) {      return (        <Result          status="warning"          title={this.props.errorMessage}          extra={            <Button type="primary" key="console">              Some action to recover            </Button>          }  />      );    }    return this.props.children;  } }; 

هذا ما يشبه ErrorBoundary لمكون الصفحة ، والذي سيتم تقديمه في كتلة المحتوى:

 const PageBody = ({ title }) => (  <Content title={title}>    <Empty className="content-empty" />  </Content> ); const MainPage = ({ errorMessage, title }) => (  <ErrorBoundary errorMessage={errorMessage}>    <PageBody title={title} />  </ErrorBoundary> 

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

هام: ErrorBoundary يمكن التقاط الأخطاء فقط في المكونات التي تحتها في الشجرة.

في القائمة أدناه ، لن يتم اعتراض الخطأ بواسطة ErrorBoundary المحلي ، ولكن سيتم إلقاؤه واعتراضه بواسطة المعالج أعلى الشجرة:

 const Page = ({ errorMessage }) => (  <ErrorBoundary errorMessage={errorMessage}>    {null.toString()}  </ErrorBoundary> ); 

وهنا يتم اكتشاف الخطأ بواسطة ErrorBoundary المحلي:

 const ErrorProneComponent = () => null.toString(); const Page = ({ errorMessage }) => (  <ErrorBoundary errorMessage={errorMessage}>    <ErrorProneComponent />  </ErrorBoundary> ); 

بعد أن قمنا بلف كل مكون منفصل في ErrorBoundary ، حققنا السلوك اللازم ، ووضعنا الكود الخاطئ المتعمد في المكون باستخدام link3 ونرى ما سيحدث. ننسى عمدا أن نخطو خطوات المعلمة:

 const PageBody = ({ title, steps }) => (  <Content title={title}>    <Steps current={2} direction="vertical">      {steps.map(({ title, description }) => (<Step title={title} description={description} />))}    </Steps>  </Content> ); const Page = ({ errorMessage, title }) => (  <ErrorBoundary errorMessage={errorMessage}>    <PageBody title={title} />  </ErrorBoundary> ); 



سيُعلمنا التطبيق بحدوث خطأ ، لكنه لن يسقط تمامًا ، يمكننا التنقل من خلال قائمة التنقل والعمل مع الأقسام الأخرى.



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

حفظ معلومات الخطأ

الآن وقد وضعنا ما يكفي من ErrorBoundary في تطبيقنا ، فمن الضروري حفظ المعلومات حول الأخطاء من أجل اكتشافها وتصحيحها في أسرع وقت ممكن. أسهل طريقة هي استخدام خدمات SaaS ، مثل Sentry أو Rollbar. لديهم وظائف مماثلة للغاية ، حتى تتمكن من استخدام أي خدمة مراقبة الأخطاء.

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

للاتصال ، يجب عليك التسجيل على موقع الويب الرسمي الخاص به وتصفح دليل البدء السريع ، والذي سيوجهك فورًا بعد التسجيل.

في تطبيقنا ، نضيف فقط سطرين وكل شيء تقلع.

 import * as Sentry from '@sentry/browser'; Sentry.init({dsn: “https://12345f@sentry.io/12345”}); 

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



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



وإجراء المستخدم الأخير قبل الخطأ (فتات الخبز).



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

النتيجة:

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

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

ملاحظة: يمكنك تجربة خيارات تكوين ErrorBoundary المختلفة أو توصيل Sentry بالتطبيق بنفسك في فرع feature_sentry ، مع استبدال المفاتيح بتلك التي تم الحصول عليها أثناء التسجيل على الموقع.

تطبيق بوابة جيت التجريبي
وثائق React الرسمية لخطأ الحدود

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


All Articles