Error Boundaries
Tags: react, tutorial, javascript
Published: 2022-05-16 01:59:14

Bài viết dịch từ:
https://reactjs.org/docs/error-boundaries.html

Trước đây, các lỗi JavaScript bên trong các component thường làm hỏng trạng thái bên trong của React và khiến nó phát ra các lỗi khó hiểu trong lần hiển thị tiếp theo. Những lỗi này gây ra do lỗi trước đó trong code gây ra, nhưng React không cung cấp cách xử lý chúng một cách linh hoạt trong các component và không thể khôi phục chúng.

###Giới thiệu Error Boundaries

Lỗi JavaScript trong một phần của giao diện người dùng không nên làm hỏng toàn bộ ứng dụng. Để giải quyết vấn đề này cho người dùng React, React 16 giới thiệu một khái niệm mới về “Error Boundaries”.

Error Boundaries là các component React bắt lỗi JavaScript ở bất kỳ đâu trong cây component con của chúng, ghi lại các lỗi đó và hiển thị giao diện người dùng dự phòng thay vì cây component bị lỗi. Error Boundaries bắt lỗi trong quá trình rendering, trong các phương thức vòng đời và trong các hàm tạo của toàn bộ cây bên dưới chúng.

> Error Boundaries không bắt lỗi đối với:
- Event handlers (learn more)
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary itself (rather than its children)

Class component sẽ trở thành error boundary nếu nó định nghĩa một trong hai (hoặc cả hai) phương thức vòng đời `static getDerivedStateFromError ()` hoặc `componentDidCatch ()`. Sử dụng `static getDerivedStateFromError ()` để hiển thị một giao diện người dùng dự phòng sau khi một lỗi được đưa ra. Sử dụng `componentDidCatch ()` để ghi thông tin lỗi.

```
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
```
Sau đó, bạn có thể sử dụng nó như một component thông thường:

```
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>
```

Các error boundaries hoạt động giống như JavaScript catch{}, nhưng đối với component. Chỉ các class component mới có thể là error boundaries. Trong thực tế, hầu hết thời gian bạn sẽ muốn khai báo ErrorBoundary một lần và sử dụng nó trong toàn bộ ứng dụng của mình.

Lưu ý rằng **Error Boundaries** chỉ bắt lỗi trong các component bên dưới chúng trong cây. **Error Boundary** không thể tự bắt lỗi. Nếu một **Error Boundary** không thể hiển thị thông báo lỗi, lỗi sẽ lan truyền đến **Error Boundary** gần nhất phía trên nó. Điều này cũng tương tự như cách khối catch {} hoạt động trong JavaScript.

### Demo

[Example](https://codepen.io/gaearon/pen/wqvxGa?editors=0010)

###Nơi đặt Error Boundaries

Mức độ chi tiết của Error Boundary là tùy thuộc vào bạn. Bạn có thể bao bọc các component tuyến cấp cao nhất để hiển thị thông báo "Đã xảy ra sự cố" cho người dùng, giống như cách phía máy chủ thường xử lý các sự cố. Bạn cũng có thể bao bọc các widget riêng lẻ trong một Error Boundary để bảo vệ chúng khỏi làm hỏng phần còn lại của ứng dụng.

>Điều này là tùy vào người viết, có thể đặt ở tầng trên cùng của ứng dụng để bắt toàn bộ lỗi, cũng có thể đặt sâu hơn để bắt lỗi cho cụ thể.

###Hành vi mới cho các lỗi chưa xảy ra

Sự thay đổi này có một hàm ý quan trọng. Kể từ React 16, các lỗi không nằm trong bất kỳ Error Boundaries nào sẽ dẫn đến việc ngắt kết nối toàn bộ cây component React.

Chúng tôi đã tranh luận về quyết định này, nhưng theo kinh nghiệm của chúng tôi, việc để lại giao diện người dùng bị hỏng tại chỗ còn tệ hơn là xóa bỏ hoàn toàn. Ví dụ: trong một sản phẩm như Messenger, việc hiển thị giao diện người dùng bị hỏng có thể dẫn đến việc ai đó gửi tin nhắn đến nhầm người. Tương tự, ứng dụng thanh toán hiển thị sai số tiền còn tệ hơn là không hiển thị gì.

Thay đổi này có nghĩa là khi bạn chuyển sang React 16, bạn có thể sẽ phát hiện ra các lỗi hiện có trong ứng dụng của mình mà trước đây chưa được chú ý. Thêm Error Boundaries cho phép bạn cung cấp trải nghiệm người dùng tốt hơn khi có sự cố.

Ví dụ: Facebook Messenger bọc nội dung của sidebar, bảng thông tin, nhật ký cuộc trò chuyện và đầu vào tin nhắn thành các Error Boundaries riêng biệt. Nếu một số thành phần trong một trong các khu vực giao diện người dùng này gặp sự cố, phần còn lại của chúng vẫn tương tác.

Chúng tôi cũng khuyến khích bạn sử dụng các dịch vụ báo cáo lỗi JS (hoặc xây dựng của riêng bạn) để bạn có thể tìm hiểu về các trường hợp ngoại lệ không được khắc phục khi chúng xảy ra trong quá trình sản xuất và khắc phục chúng.

###Component Stack Traces

React 16 in tất cả các lỗi xảy ra trong quá trình hiển thị cho console ở môi trường phát triển. Ngoài thông báo lỗi và JavaScript stack, nó cũng cung cấp component stack traces. Bây giờ bạn có thể thấy vị trí chính xác lỗi trong cây component:


![Image eb](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rplw70in9clp1krx3cc4.png)

Bạn cũng có thể xem tên tệp và số dòng trong component stack trace. Điều này hoạt động theo mặc định trong Create React App:


![Image eb-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdhiqkol8b997o83qt8o.png)

Nếu không sử dụng Create React App, bạn có thể thêm [plugin](https://www.npmjs.com/package/@babel/plugin-transform-react-jsx-source) này theo cách thủ công vào cấu hình Babel của mình. Lưu ý rằng nó chỉ dành cho mục đích development và phải bị vô hiệu hóa trong production.

### Thế còn try/catch

`try / catch` dùng cho `imperative code`

```
try {
  showButton();
} catch (error) {
  // ...
}
```

Tuy nhiên, các component React là **declarative** và chỉ định những gì sẽ được hiển thị:

```
<Button />
```

> Tham khảo: [declarative  vs imperative](https://viblo.asia/p/so-sanh-imperative-programming-va-declarative-programming-YWOZro3ElQ0)

### Thế còn Event Handlers

**Error Boundary** không bắt lỗi bên trong **Event Handlers**.

React không cần **Error Boundary** để khôi phục từ lỗi trong **Event Handlers**. Không giống như **phương thức render và phương thức lifecycle**, **Event Handlers** không xảy ra trong quá trình rendering. Vì vậy, nếu **event handler** ném ra lỗi, React vẫn biết những gì sẽ hiển thị trên màn hình.

Nếu bạn cần bắt lỗi bên trong **Event Handlers**, hãy sử dụng câu lệnh `try / catch` JavaScript thông thường:

```
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    try {
      // Do something that could throw
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <button onClick={this.handleClick}>Click Me</button>
  }
}
```

Lưu ý rằng ví dụ trên thể hiện hành vi JavaScript thông thường và không sử dụng các Error Boundary.

> Dự án để học Error Boundary: [Github](https://github.com/rowyio/rowy/blob/main/src/components/ErrorBoundary.tsx)