چگونه از Error Boundaryها در React 16 استفاده کنیم؟

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 16 تیر 1397
دسته بندی ها : جاوا اسکریپت

آیا چنین مواردی را در کنسول خود دیده‌اید؟

Cannot read property ‘getHostNode’ of null 
TypeError: Cannot read property ‘_currentElement’ of null

اگر دارای یک برنامه React هستید، می‌دانید که این نوع پیام‌های خطا می‌توانند یکی از خسته کننده‌ترین و سخت‌ترین ارور‌ها باشند. علائم رایج ‌آن‌ها می‌توانند Stack Traceهایی باشند که در بخش‌های داخلی React وجود دارند، بدون این که هیچ‌گونه اشاره‌ای به حتی یک خط از کد شما داشته باشند:

TypeError: Cannot read property 'getHostNode' of null ?
  at getHostNode(~/react/lib/ReactReconciler.js:64:0)
  at getHostNode(~/react/lib/ReactCompositeComponent.js:383:0) ?
  at getHostNode(~/react/lib/ReactReconciler.js:64:0)
  at getHostNode(~/react/lib/ReactChildReconciler.js:114:0)?
  at updateChildren(~/react/lib/ReactMultiChild.js:215:0)
  at _reconcilerUpdateChildren(~/react/lib/ReactMultiChild.js:314:0)
  at _updateChildren(~/react/lib/ReactMultiChild.js:301:0)
  at updateChildren(~/react/lib/ReactDOMComponent.js:942:0)
  at _updateDOMChildren(~/react/lib/ReactDOMComponent.js:760:0) ?
  at updateComponent(~/react/lib/ReactDOMComponent.js:718:0)
  at receiveComponent(~/react/lib/ReactReconciler.js:126:0)
  at receiveComponent(~/react/lib/ReactCompositeComponent.js:751:0) ?
  at _updateRenderedComponent(~/react/lib/ReactCompositeComponent.js:721:0)
  at _performComponentUpdate(~/react/lib/ReactCompositeComponent.js:642:0)
  at updateComponent(~/react/lib/ReactCompositeComponent.js:544:0)
  at receiveComponent(~/react/lib/ReactReconciler.js:126:0) ?
  at receiveComponent(~/react/lib/ReactCompositeComponent.js:751:0)
  at _updateRenderedComponent(~/react/lib/ReactCompositeComponent.js:721:0)
  at _performComponentUpdate(~/react/lib/ReactCompositeComponent.js:642:0)
  at updateComponent(~/react/lib/ReactCompositeComponent.js:544:0)
  at receiveComponent(~/react/lib/ReactReconciler.js:126:0)  ?
  at receiveComponent(~/react/lib/ReactCompositeComponent.js:751:0)
  at _updateRenderedComponent(~/react/lib/ReactCompositeComponent.js:721:0)
  at _performComponentUpdate(~/react/lib/ReactCompositeComponent.js:642:0)
  at updateComponent(~/react/lib/ReactCompositeComponent.js:544:0) ?
  at receiveComponent(~/react/lib/ReactReconciler.js:126:0)
  at receiveComponent(~/react/lib/ReactCompositeComponent.js:751:0) ?
  at _updateRenderedComponent(~/react/lib/ReactCompositeComponent.js:721:0)
  at _performComponentUpdate(~/react/lib/ReactCompositeComponent.js:642:0)
  at updateComponent(~/react/lib/ReactCompositeComponent.js:558:0)
  at performUpdateIfNecessary(~/react/lib/ReactReconciler.js:158:0)
  at performUpdateIfNecessary(~/react/lib/ReactUpdates.js:151:0) ?
  at call(~/react/lib/Transaction.js:138:0)
  at call(~/react/lib/Transaction.js:138:0)
  at call(~/react/lib/ReactUpdates.js:90:0)
  at perform(~/react/lib/ReactUpdates.js:173:0)
  at call(~/react/lib/Transaction.js:204:0)
  at closeAll(~/react/lib/Transaction.js:151:0)
  at perform(~/react/lib/ReactDefaultBatchingStrategy.js:63:0) ?
  at batchedUpdates(~/react/lib/ReactUpdates.js:98:0)
  at batchedUpdates(~/react/lib/ReactEventListener.js:150:0)

اگر این نوع Stack Trace از یک خطای React برای شما آشنا است، خبرهای خوبی برای شما داریم!

جدول محتویات:

  1. به React 16 سلام کنید
  2. استفاده از Error Boundaryها را شروع کنید
  3. وقتی که خطایی را می‌گیرید،‌ چه کاری باید انجام دهید؟
  4. خطاها را به Sentry بفرستید

به React 16 سلام کنید

React 16 به طور رسمی در تاریخ 26 سپتامبر 2017 منتشر شد. React 16 یک بازنویسی از React، با قابلیت سازگاری با API و اهداف بلند پروازانه‌ای مانند رندر کردن ناهمگام و پیشگیرانه و فراهم‌سازی ابزار جدید برای نمایش زیبای سلسله مراتب کامپوننت‌ها، مانند قطعات و پورتال‌ها که امروزه پر استفاده هستند، است. هدف دیگر معماری آن، رسیدگی به خطاها با یک استراتژی جدید، دقیق‌تر و صحیح است.

این استراتژی به این معنی است که React 16 از موقعیت‌هایی که خطایی در داخل یک فرایند رندر کردن باعث بروز یک State غیر معتبر و در نتیجه رفتار تعریف نشده و خطاهای گیج کننده (مانند دوست عزیزمان «Cannot read property ‘getHostNode’ of null») می‌شوند، با قطع کردن ارتباط با کل درخت کامپوونت‌ها، جلوگیری می‌کند.

برخورد خشن‌تر با این خطاها، به این معنی است که زمانی که برنامه در یک وضعیت خراب کار می‌کند، از آن جلوگیری شود؛ و در نتیجه فاصله میان جایی که مشکل اصلی بروز می‌دهد و جایی که React کل فرایند را به خاطر آن خطا قطع می‌کند، کمتر شود. این کار باعث می‌شود خطاهای زیادی در کامپوننت‌های React ساده‌تر درک شده، و اصلاح شوند.

البته، این یعنی خطاهایی که به طور ساکت یا غیر فاجعه بار مشکل ساز می‌شدند نیز باعث توقف کلی برنامه می‌شوند.

راه حل این مشکل، ابزار جدید React 16 برای رسیدگی خارجی به انتشار خطاها، یعنی Error Boundaryها است. Error Boundaryها مشابه روش try{ }catch(e){ } هستند، اما داخل کامپوننت‌ها وجود دارند و توسط سلسله مراتب آن‌ها، به جای یک بلوک همگام JavaScript نظارت می‌شوند.

علت وجود Error Boundaryها، عدم کار کردن کد زیر با مدل‌های رندر JSX است:

<div>
  { try {
    <CoolComplicatedComponent/>
  } catch(error){
    handleChildError(error)
    }
  }
</div>

با استفاده از Error Boundaryها، می‌توانید بخش‌هایی از درخت کامپوننت‌های برنامه خود را از باقی خطاها در بخش‌های دیگر جدا کنید، برای مثال، به موزیک پلیر خود اجازه دهید وقتی که در بخش Commentها خطایی پیش می‌آید، همچنان به طور نرمی پخش آهنگ را ادامه دهد. این یک پیشرفت بزرگ برای ثبات برنامه شما است.

استفاده از Error Boundaryها را شروع کنید

Error Boundaryها ابزاری هستند که هر برنامه بزرگ React باید استفاده کند، تا انعطاف پذیری خود در مقابل خطا را پیشرفت دهد. رویه کار API در Error Boundaryها ساده است: هر کامپوننت React وقتی که یک متد جدید را پیاده‌سازی می‌کند، تبدیل به یک Error Boundary، یعنی componentDidCatch(error, info) می‌شود. این تابع زمانی که یک خطای دریافت نشده از متدهای زیرشاخه کامپوننت، یا متدهای رندر بروز می‌دهد، فراخوانی می‌شود.

<div>
  <ExampleBoundary>
    <h2>Sidebar</h2>
    <Widget/>
  </ExampleBoundary>
  <p> This content won't unmount when Widget throws. </p>
</div>

وقتی که خطایی را میگیرید،چه کاری باید انجام دهید؟

هر کاری که مناسب می‌دانید را می‌توانید در componentDidCatch انجام دهید. اما پس از این که فعال شد،‌ نمی‌توانید this.props.children را رندر کنید، و باید آن را با نوعی رابط کاربری پشتیبان جایگزین کنید. این پشتیبان ممکن است یک View خطایابی، یک فرم پشتیبان، لینکی به پشتیبانی، یا یک GIF خنده دار باشد. البته همچنین می‌تواند به معنای رندر کردن هیچ چیز باشد؛ همه چیز به نیازهای برنامه شما بستگی درد. فقط لطفی در حق خود بکنید، و مطمئن شوید چیزی نباشد که خودش خطاهای خودش را داشته باشد و بروز دهد.

Error Boundaryها باید به کجا بروند؟

Error Boundaryها یک ابزار انتها باز هستند، ‌و جامعه هنوز در حال تعریف روش‌هایی برای آنان است. پیشنهاد ما این است که کامپوننت‌ها را به صورت دانه دانه، و در جایی که می‌توانند به طور مستقل کاربردی باشند، جمع‌ آوری کنید. یعنی این که وقتی یکی از آن‌ها با خطایی مواجه می‌شود، دیگری بتواند کار خود را انجام دهد. جمع‌آوری کامپوننت‌های صفحه شما، از کامپوننت Header و نوار کناری شما محافظت می‌کند تا راه ساده‌ای برای برگشتن به بخش‌های برنامه شما که کار می‌کنند، به کاربر بدهد. در غیر این صورت، انجام این کار می‌تواند به یک رابط کاربری که هنوز پاسخگو است،‌ ولی کار خود را نمی‌تواند انجام دهد، منتهی شود.

نکته: به طور پیشفرض، Raven.js و Sentry’s JavaScript SDK، به طور امنی متدهای داخلی شما را مجبور می‌کند تا کد شما را در یک بلوک Try/Catch جمع‌آوری می‌کند. این بلوک این کار را انجام می‌دهد، تا پیام‌های خطا و Stack Traceهای تمام اسکریپت‌های شما را، بدون توجه به منشا آن‌ها دریافت کند.

خطاها را به Sentry بفرستید

از آنجایی که Error Boundaryها مشابه Try/Catch هستند، Exceptionهای آن‌ها به طور خودکار به Sentry منتقل نمی‌شوند. مطمئن شوید که تابع Raven.captureException(error) را در componentDidCatch پیاده‌سازی کنید و نحوه بر طرف کردن آن خطاها را ببینید.

در زیر، مثالی از ارسال یک گزارش به Sentry را می‌بینید:

class ExampleBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  componentDidCatch(error, errorInfo) {
    this.setState({ error });
    Raven.captureException(error, { extra: errorInfo });
  }
  render() {
    if (this.state.error) {
      return (
        <div
          className="snap"
          onClick={() => Raven.lastEventId() && Raven.showReportDialog()}
        >
          <img src={oops} />
          <p>We're sorry — something's gone wrong.</p>
          <p>Our team has been notified, but click here fill out a report.</p>
        </div>
      );
    } else {
      //when there's not an error, render children untouched
      return this.props.children;
    }
  }
}

جنبه جالب دیگر componentDidCatch در آرگومان دوم، errorInfo است. در حال حاضر، errorInfo تنها یک ویژگی دارد، و آن componentStack، یعنی یک کامپوننت Stack Trace است. این کاپوننت، مسیر شما برای درخت کامپوننت‌های شما، از ریشه برنامه تا مدل‌ها و CodeBaseها است. از آنجایی که این اطلاعات برای حل خطاها خوب است، پیشنهاد می‌کنیم آن را با خطاهای Sentry به عنوان داده‌های اضافی بفرستید.

منبع

مقالات پیشنهادی

چگونه از Bootstrap به همراه React استفاده کنیم؟

با محبوبیت روز افزون برنامه‌های تک صفحه‌ای در سال‌های اخیر، تعداد زیادی فریم‌وورک جاوااسکریپت frontend مانند Angular، React، VueJS و Ember پدید آمده‌ا...

با استفاده از React، یک دکمه Toggle سفارشی بسازید

ساخت وب‌اپلیکیشن‌ها معمولا شامل ساخت مقرراتی برای تعاملات کاربری است. یکی از راه‌های اصلی برای ساخت این مقررات برای تعاملات کاربری، از طریق فرم‌ها است...

استفاده از Create React App نسخه 2 و TypeScript

حال که Create React App نسخه ۲ منتشر شده است، پشتیبانی TypeScript رسمی هم به همراه آن می‌آید. این یک مسئله هیجان انگیز برای کاربران JavaScript است که...

استفاده از Sass در Create React App نسخه ۲

با انتشار نسخه اخیر Create React App، تعداد زیادی ابزار جدید برای بازی کردن با آن‌ها پیدا کردیم. Sass ابزاری است که من درباره‌اش هیجان زده‌ام؛ زیرا قب...