Xử lý ranh giới lỗi trong phát triển frontend

Xử lý ranh giới lỗi một cách chuyên nghiệp

Tại sao cần có ranh giới lỗi?

Trong phát triển frontend, lỗi ở một thành phần có thể khiến toàn bộ ứng dụng sập. Hãy tưởng tượng: người dùng đang điền một biểu mẫu phức tạp, đột nhiên ứng dụng trắng màn hình chỉ vì lỗi nhỏ ở thành phần con - trải nghiệm người dùng lúc này thật tệ!

Ranh giới lỗi (Error Boundary) ra đời để giải quyết vấn đề này. Nó giúp bắt lỗi JavaScript trong cây thành phần con, ghi lại thông tin lỗi và hiển thị giao diện thay thế thay vì để ứng dụng sập hoàn toàn.

Triển khai ranh giới lỗi trong React

Cách cài đặt cơ bản

Chú ý quan trọng: Tính đến React 18, ranh giới lỗi chỉ có thể triển khai qua class component do giới hạn của framework. Tuy nhiên có thể kết hợp với Hooks để sử dụng!

import React from 'react'; // Ranh giới lỗi phải dùng class component
class ErrorBoundary extends React.Component {
  state = { 
    coLoi: false, 
    thongTinLoi: null, 
    chiTietLoi: null 
  };

  // Phương thức tĩnh để cập nhật trạng thái khi có lỗi
  static getDerivedStateFromError(loi) {
    return { 
      coLoi: true, 
      thongTinLoi: loi 
    };
  }

  // Bắt lỗi và ghi log
  componentDidCatch(loi, chiTiet) {
    console.error('Ranh giới lỗi bắt được lỗi:', loi, chiTiet);
    this.setState({ chiTietLoi: chiTiet });
    // Có thể gửi log lỗi lên server
  }

  // Hàm thiết lập lại trạng thái
  thietLapLai = () => {
    this.setState({ 
      coLoi: false, 
      thongTinLoi: null, 
      chiTietLoi: null 
    });
    if (this.props.onReset) {
      this.props.onReset();
    }
  };

  render() {
    if (this.state.coLoi) {
      // Hỗ trợ UI lỗi tùy chỉnh
      return this.props.fallbackUI || (
        <div className="gioi-han-loi">
          <h2>Xin lỗi, đã có lỗi xảy ra 😢</h2>
          <div className="chi-tiet-loi">
            <p>{this.state.thongTinLoi?.toString()}</p>
            <details>
              <summary>Chi tiết lỗi</summary>
              {this.state.chiTietLoi?.componentStack}
            </details>
          </div>
          <button onClick={this.thietLapLai} className="nut-thu-lai">
            Thử lại
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}
export default ErrorBoundary;
Kết hợp Hooks với ranh giới lỗi

Dù ranh giới lỗi cần class component, vẫn có thể sử dụng dễ dàng trong các component Hooks:

import React, { useState } from 'react';
import ErrorBoundary from './ErrorBoundary';

// Component có thể gặp lỗi
const ComponentCoLoi = () => {
  const [soDem, setSoDem] = useState(0);
  if (soDem > 3) {
    // Lỗi giả lập
    throw new Error('Vượt quá giới hạn đếm!');
  }
  return (
    <div>
      <h3>Số hiện tại: {soDem}</h3>
      <button onClick={() => setSoDem(soDem + 1)}>Tăng số</button>
    </div>
  );
};

// Sử dụng ranh giới lỗi
const App = () => {
  return (
    <div className="ung-dung">
      <h1>Ví dụ ranh giới lỗi React</h1>
      <ErrorBoundary
        // UI lỗi tùy chỉnh
        fallbackUI={
          <div className="loi-tu-dinh-dang">
            <h3>😱 Thành phần có lỗi!</h3>
            <p>Thành phần đếm bị lỗi, vui lòng thử lại</p>
          </div>
        }
      >
        <ComponentCoLoi />
      </ErrorBoundary>
    </div>
  );
};
export default App;

Tối ưu nâng cao

1. Báo cáo lỗi lên server

Kết hợp cú pháp JS hiện đại và Fetch API:

componentDidCatch(loi, chiTiet) {
  // Gửi lỗi lên server
  fetch('/api/bao-cao-loi', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      loi: loi.toString(),
      stack: chiTiet.componentStack,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: new Date().toISOString()
    })
  }).catch(baoCaoLoi => {
    console.error('Gửi lỗi thất bại:', baoCaoLoi);
  });
}
2. Hỗ trợ hàm thiết lập lại

Trong phiên bản cơ bản đã có chức năng thiết lập lại, cách sử dụng:

<ErrorBoundary 
  onReset={() => {
    // Thiết lập lại trạng thái hoặc dọn dẹp
    console.log('Đã thiết lập lại ranh giới lỗi');
  }}
>
  <ComponentCoLoi />
</ErrorBoundary>

️ Lưu ý quan trọng

Ranh giới lỗi React
  1. Chỉ bắt lỗi trong cây thành phần con, không bắt lỗi chính nó
  2. Không bắt lỗi trong mã bất đồng bộ (setTimeout, fetch callback...)
  3. Không bắt lỗi trong handler sự kiện (onClick...)
  4. Chỉ hỗ trợ từ React 16 trở lên
  5. Phải dùng class component (Giới hạn framework)
Xử lý lỗi trong Vue 3
  1. Hàm onErrorCaptured chỉ bắt lỗi thành phần con
  2. Bắt lỗi biên dịch và render template
  3. Mặc định lỗi sẽ lan truyền, trả về false để ngăn chặn
  4. Lỗi bất đồng bộ cần dùng try/catch hoặc xử lý toàn cục
  5. Vue 3 Composition API cung cấp cách xử lý linh hoạt hơn

Triển khai xử lý lỗi trong Vue 3 (Syntax sugar)

Vue 3 cung cấp Composition API hiện đại, kết hợp <script setup> để xử lý lỗi:

<template>
  <div>
    <slot v-if="!coLoi"></slot>
    <div v-else class="gioi-han-loi">
      <h2>Xin lỗi, đã có lỗi xảy ra 😢</h2>
      <div class="chi-tiet-loi" v-if="thongTinLoi">
        <p>{{ thongTinLoi.message }}</p>
        <details>
<summary>Chi tiết lỗi</summary>

{{ thongTinLoi.stack }}

        </details>
      </div>
      <button @click="thietLapLai" class="nut-thu-lai"> 
        Thử lại 
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, onErrorCaptured } from 'vue';

// Định nghĩa props
const props = defineProps({
  // UI lỗi tùy chỉnh
  fallbackUI: {
    type: Object,
    default: null
  }
});

// Định nghĩa sự kiện
const emit = defineEmits(['reset']);

// Trạng thái lỗi
const coLoi = ref(false);
const thongTinLoi = ref(null);
const chiTietLoi = ref(null);

// Hàm bắt lỗi thành phần con
onErrorCaptured((loi, thietBi, thongTin) => {
  console.error('Vue 3 bắt lỗi:', loi, thietBi, thongTin);
  coLoi.value = true;
  thongTinLoi.value = loi;
  chiTietLoi.value = thongTin;
  // Gửi log lỗi
  baoCaoLoi(loi, thongTin);
  // Trả về false để ngăn lỗi lan truyền
  return false;
});

// Hàm báo cáo lỗi
const baoCaoLoi = async (loi, thongTin) => {
  try {
    await fetch('/api/bao-cao-loi', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        loi: loi.toString(),
        stack: loi.stack,
        thongTin,
        userAgent: navigator.userAgent,
        url: window.location.href,
        timestamp: new Date().toISOString()
      })
    });
  } catch (baoCaoLoi) {
    console.error('Gửi lỗi thất bại:', baoCaoLoi);
  }
};

// Hàm thiết lập lại
const thietLapLai = () => {
  coLoi.value = false;
  thongTinLoi.value = null;
  chiTietLoi.value = null;
  emit('reset');
};
</script>
Ví dụ sử dụng


Ví dụ xử lý lỗi Vue 3



<!-- Sử dụng UI lỗi tùy chỉnh -->







import { h } from 'vue';
import ErrorBoundary from './ErrorBoundary.vue';
import ComponentCoLoi from './ComponentCoLoi.vue';
import ComponentKhacCoLoi from './ComponentKhacCoLoi.vue';

// UI lỗi tùy chỉnh
const uiLoiTuDinhDang = h('div', { class: 'loi-tu-dinh-dang' }, [
h('h3', '📊 Biểu đồ tải lỗi'),
h('p', 'Vui lòng kiểm tra kết nối mạng'),
h('button', { onClick: () => console.log('Nhấn nút thử lại') }, 'Tải lại')
]);

// Hàm xử lý thiết lập lại
const xuLyThietLapLai = () => {
console.log('Đã thiết lập lại, ứng dụng hoạt động bình thường');
};

Thẻ: react Vue.js Xử lý lỗi Lỗi lập trình xử lý ngoại lệ

Đăng vào ngày 29 tháng 8 lúc 10:11