ควบคุม TypeScript error boundaries เพื่อสร้างแอปพลิเคชันที่ยืดหยุ่น สำรวจรูปแบบชนิดข้อมูลการจัดการข้อผิดพลาด แนวทางปฏิบัติ และตัวอย่างจริง
TypeScript Error Boundaries: รูปแบบชนิดข้อมูลการจัดการข้อผิดพลาดสำหรับแอปพลิเคชันที่ทนทาน
ในโลกของการพัฒนาซอฟต์แวร์ ข้อผิดพลาดที่ไม่คาดคิดเป็นสิ่งที่หลีกเลี่ยงไม่ได้ ตั้งแต่ปัญหาเครือข่ายไปจนถึงรูปแบบข้อมูลที่ไม่คาดคิด แอปพลิเคชันต้องพร้อมรับมือกับสถานการณ์เหล่านี้อย่างสง่างาม TypeScript ด้วยระบบชนิดข้อมูลที่ทรงพลัง มอบเฟรมเวิร์กที่ทนทานสำหรับการสร้างแอปพลิเคชันที่ยืดหยุ่น บทความนี้เจาะลึกถึงแนวคิดของ TypeScript error boundaries สำรวจรูปแบบชนิดข้อมูลการจัดการข้อผิดพลาด แนวทางปฏิบัติที่ดีที่สุด และตัวอย่างจริง เพื่อให้คุณมีความรู้ในการสร้างโค้ดที่เสถียรและบำรุงรักษาได้มากขึ้น
ทำความเข้าใจความสำคัญของการจัดการข้อผิดพลาด
การจัดการข้อผิดพลาดที่มีประสิทธิภาพเป็นสิ่งสำคัญสำหรับประสบการณ์ผู้ใช้ที่ดีต่อสุขภาพโดยรวมของแอปพลิเคชัน เมื่อข้อผิดพลาดไม่ได้รับการจัดการ พวกมันอาจนำไปสู่:
- การขัดข้องและพฤติกรรมที่คาดเดาไม่ได้: ข้อยกเว้นที่ไม่ได้ถูกจับสามารถหยุดการทำงานของโค้ดของคุณ ทำให้เกิดการขัดข้องหรือผลลัพธ์ที่คาดเดาไม่ได้
- การสูญเสียและการเสียหายของข้อมูล: ข้อผิดพลาดระหว่างการประมวลผลหรือจัดเก็บข้อมูลอาจส่งผลให้ข้อมูลสูญหายหรือเสียหาย ส่งผลกระทบต่อผู้ใช้และความสมบูรณ์ของระบบ
- ช่องโหว่ด้านความปลอดภัย: การจัดการข้อผิดพลาดที่ไม่ดีอาจเปิดเผยข้อมูลที่ละเอียดอ่อนหรือสร้างโอกาสสำหรับการโจมตีของผู้ไม่หวังดี
- ประสบการณ์ผู้ใช้เชิงลบ: ผู้ใช้ที่พบข้อความข้อผิดพลาดที่เข้าใจยากหรือแอปพลิเคชันล้มเหลว มีแนวโน้มที่จะมีประสบการณ์ที่น่าหงุดหงิด ส่งผลให้ความไว้วางใจและการยอมรับลดลง
- ลดประสิทธิภาพการทำงาน: นักพัฒนาใช้เวลาในการดีบั๊กและแก้ไขข้อผิดพลาดที่ไม่ได้ถูกจัดการ ทำให้ประสิทธิภาพการทำงานโดยรวมของนักพัฒนาลดลงและทำให้รอบการปล่อยผลิตภัณฑ์ช้าลง
การจัดการข้อผิดพลาดที่ดี ในทางกลับกัน จะให้:
- การเสื่อมสภาพอย่างสง่างาม: แอปพลิเคชันยังคงทำงานได้ แม้ว่าบางส่วนประสบปัญหาข้อผิดพลาด
- การตอบสนองที่ให้ข้อมูล: ผู้ใช้ได้รับข้อความข้อผิดพลาดที่ชัดเจนและกระชับ ช่วยให้พวกเขาเข้าใจและแก้ไขปัญหาได้
- ความสมบูรณ์ของข้อมูล: การดำเนินการที่สำคัญได้รับการจัดการในลักษณะธุรกรรม ปกป้องข้อมูลผู้ใช้ที่สำคัญ
- ความเสถียรที่เพิ่มขึ้น: แอปพลิเคชันมีความยืดหยุ่นต่อเหตุการณ์ที่ไม่คาดคิดมากขึ้น
- การบำรุงรักษาที่เพิ่มขึ้น: ระบุ วินิจฉัย และแก้ไขปัญหาได้ง่ายขึ้นเมื่อเกิดขึ้น
Error Boundaries ใน TypeScript คืออะไร
Error boundaries เป็นรูปแบบการออกแบบที่ใช้ในการจับข้อผิดพลาด JavaScript ภายในส่วนเฉพาะของต้นไม้ส่วนประกอบ (component tree) และแสดง UI สำรอง (fallback UI) อย่างสง่างาม แทนที่จะทำให้แอปพลิเคชันทั้งหมดล่ม แม้ว่า TypeScript เองจะไม่มีฟีเจอร์ "error boundary" ที่เฉพาะเจาะจง แต่หลักการและเทคนิคในการสร้างขอบเขตดังกล่าวสามารถนำไปใช้ได้อย่างง่ายดายและได้รับการปรับปรุงโดยความปลอดภัยของชนิดข้อมูลของ TypeScript
แนวคิดหลักคือการแยกโค้ดที่อาจเกิดข้อผิดพลาดภายในส่วนประกอบหรือโมดูลเฉพาะ ส่วนประกอบนี้ทำหน้าที่เป็นตัวห่อหุ้ม ตรวจสอบโค้ดภายใน หากเกิดข้อผิดพลาด ขอบเขตข้อผิดพลาดจะ "จับ" ข้อผิดพลาด ป้องกันไม่ให้แพร่กระจายขึ้นไปในต้นไม้ส่วนประกอบและอาจทำให้แอปพลิเคชันล่มได้ แต่ขอบเขตข้อผิดพลาดสามารถแสดง UI สำรอง บันทึกข้อผิดพลาด หรือพยายามกู้คืนจากปัญหาได้
ประโยชน์ของการใช้ error boundaries คือ:
- การแยกส่วน: ป้องกันไม่ให้ข้อผิดพลาดในส่วนหนึ่งของแอปพลิเคชันของคุณส่งผลกระทบต่อส่วนอื่น
- UI สำรอง: มอบประสบการณ์ที่ใช้งานง่ายกว่าแอปพลิเคชันที่ทำงานผิดพลาดโดยสิ้นเชิง
- การบันทึกข้อผิดพลาด: อำนวยความสะดวกในการรวบรวมข้อมูลข้อผิดพลาดเพื่อการดีบั๊กและการตรวจสอบ
- การบำรุงรักษาที่ดีขึ้น: ทำให้ตรรกะการจัดการข้อผิดพลาดง่ายขึ้นและง่ายต่อการอัปเดตและบำรุงรักษาโค้ด
รูปแบบชนิดข้อมูลการจัดการข้อผิดพลาดใน TypeScript
ระบบชนิดข้อมูลของ TypeScript มีประสิทธิภาพสูงเมื่อใช้ร่วมกับรูปแบบการจัดการข้อผิดพลาดที่เหมาะสม นี่คือรูปแบบทั่วไปและมีประสิทธิภาพสำหรับการจัดการข้อผิดพลาดในแอปพลิเคชัน TypeScript ของคุณ:
1. บล็อก Try-Catch
ส่วนประกอบพื้นฐานของการจัดการข้อผิดพลาดใน JavaScript และ TypeScript คือบล็อก `try-catch` ช่วยให้คุณดำเนินการโค้ดภายในบล็อก `try` และจับข้อยกเว้นใด ๆ ที่ถูกโยนออกไป นี่เป็นการดำเนินการแบบซิงโครนัส เหมาะสำหรับการจัดการข้อผิดพลาดโดยตรงภายในฟังก์ชัน
function fetchData(url: string): Promise<any> {
try {
return fetch(url).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
} catch (error) {
console.error("An error occurred while fetching data:", error);
// Handle the error (e.g., display an error message to the user)
return Promise.reject(error);
}
}
ในตัวอย่างนี้ ฟังก์ชัน `fetchData` พยายามดึงข้อมูลจาก URL ที่กำหนด หากการเรียก `fetch` ล้มเหลว (เช่น ข้อผิดพลาดเครือข่าย URL ไม่ถูกต้อง) หรือหากสถานะการตอบกลับไม่ถูกต้อง จะมีการโยนข้อผิดพลาด บล็อก `catch` จะจัดการข้อผิดพลาด สังเกตการใช้ `Promise.reject(error)` เพื่อส่งต่อข้อผิดพลาด เพื่อให้โค้ดที่เรียกสามารถจัดการได้เช่นกัน นี่เป็นเรื่องปกติสำหรับการดำเนินการแบบอะซิงโครนัส
2. Promises และการจัดการข้อผิดพลาดแบบอะซิงโครนัส
การดำเนินการแบบอะซิงโครนัสเป็นเรื่องปกติใน JavaScript โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับ API การโต้ตอบฐานข้อมูล และ I/O ของไฟล์ Promises มอบกลไกที่ทรงพลังสำหรับการจัดการข้อผิดพลาดในสถานการณ์เหล่านี้ บล็อก `try-catch` มีประโยชน์ แต่ในหลายกรณี คุณจะจัดการข้อผิดพลาดภายในเมธอด `.then()` และ `.catch()` ของ Promise
function fetchData(url: string): Promise<any> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.catch(error => {
console.error("An error occurred while fetching data:", error);
// Handle the error (e.g., display an error message to the user)
return Promise.reject(error);
});
}
fetchData('https://api.example.com/data')
.then(data => {
console.log("Data fetched successfully:", data);
})
.catch(error => {
console.error("Failed to fetch data:", error);
// Display a user-friendly error message
});
ในตัวอย่างนี้ ฟังก์ชัน `fetchData` ใช้ Promise เพื่อจัดการการดำเนินการ `fetch` แบบอะซิงโครนัส ข้อผิดพลาดจะถูกจับในบล็อก `.catch()` ทำให้คุณสามารถจัดการกับมันโดยเฉพาะสำหรับการดำเนินการแบบอะซิงโครนัส
3. คลาสข้อผิดพลาดและชนิดข้อผิดพลาดที่กำหนดเอง
TypeScript อนุญาตให้คุณกำหนดคลาสข้อผิดพลาดที่กำหนดเอง ซึ่งให้การจัดการข้อผิดพลาดที่มีโครงสร้างและให้ข้อมูลมากขึ้น นี่เป็นแนวทางปฏิบัติที่ดีสำหรับการสร้างตรรกะการจัดการข้อผิดพลาดที่นำกลับมาใช้ใหม่ได้และปลอดภัยสำหรับชนิดข้อมูล โดยการสร้างคลาสข้อผิดพลาดที่กำหนดเอง คุณสามารถ:
- เพิ่มรหัสข้อผิดพลาดเฉพาะ: แยกความแตกต่างระหว่างประเภทข้อผิดพลาดต่างๆ
- ให้บริบท: จัดเก็บข้อมูลเพิ่มเติมที่เกี่ยวข้องกับข้อผิดพลาด
- ปรับปรุงความสามารถในการอ่านและบำรุงรักษา: ทำให้โค้ดการจัดการข้อผิดพลาดของคุณเข้าใจง่ายขึ้น
class ApiError extends Error {
statusCode: number;
code: string;
constructor(message: string, statusCode: number, code: string) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
this.code = code;
// Assign the prototype explicitly
Object.setPrototypeOf(this, ApiError.prototype);
}
}
async function getUserData(userId: number): Promise<any> {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
let errorMessage = 'Failed to fetch user data';
if (response.status === 404) {
errorMessage = 'User not found';
}
throw new ApiError(errorMessage, response.status, 'USER_NOT_FOUND');
}
return await response.json();
} catch (error: any) {
if (error instanceof ApiError) {
console.error("API Error:", error.message, error.statusCode, error.code);
// Handle specific API error based on the code
if (error.code === 'USER_NOT_FOUND') {
// Show a 'user not found' message
}
} else {
console.error("An unexpected error occurred:", error);
// Handle other errors
}
throw error; // Re-throw or handle the error
}
}
getUserData(123)
.then(userData => console.log("User data:", userData))
.catch(error => console.error("Error retrieving user data:", error));
ตัวอย่างนี้กำหนดคลาส `ApiError` ที่สืบทอดมาจากคลาส `Error` ในตัว มีคุณสมบัติ `statusCode` และ `code` เพื่อให้บริบทเพิ่มเติม ฟังก์ชัน `getUserData` ใช้คลาสข้อผิดพลาดที่กำหนดเองนี้ จับและจัดการประเภทข้อผิดพลาดเฉพาะ การใช้ตัวดำเนินการ `instanceof` ช่วยให้สามารถตรวจสอบที่ปลอดภัยสำหรับชนิดข้อมูลและการจัดการข้อผิดพลาดเฉพาะตามชนิดของข้อผิดพลาด
4. ประเภท `Result` (การจัดการข้อผิดพลาดเชิงฟังก์ชัน)
การเขียนโปรแกรมเชิงฟังก์ชันมักใช้ประเภท `Result` (หรือที่เรียกว่าประเภท `Either`) เพื่อแสดงผลลัพธ์ที่สำเร็จหรือข้อผิดพลาด รูปแบบนี้มอบวิธีที่สะอาดและปลอดภัยสำหรับชนิดข้อมูลในการจัดการข้อผิดพลาด ประเภท `Result` โดยทั่วไปมีสองรูปแบบ: `Ok` (สำหรับความสำเร็จ) และ `Err` (สำหรับความล้มเหลว)
// Define a generic Result type
interface Ok<T> {
type: 'ok';
value: T;
}
interface Err<E> {
type: 'err';
error: E;
}
type Result<T, E> = Ok<T> | Err<E>
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return { type: 'err', error: 'Division by zero' };
}
return { type: 'ok', value: a / b };
}
const result1 = divide(10, 2);
const result2 = divide(10, 0);
if (result1.type === 'ok') {
console.log('Result:', result1.value);
} else {
console.error('Error:', result1.error);
}
if (result2.type === 'ok') {
console.log('Result:', result2.value);
} else {
console.error('Error:', result2.error);
}
ฟังก์ชัน `divide` ส่งคืน `Result` ของประเภท `Ok` ที่มีผลลัพธ์ของการหาร หรือ `Result` ของประเภท `Err` ที่มีข้อความแสดงข้อผิดพลาด รูปแบบนี้รับประกันว่าผู้เรียกจะต้องจัดการทั้งสถานการณ์ที่สำเร็จและล้มเหลวโดยชัดแจ้ง ป้องกันข้อผิดพลาดที่ไม่ได้ถูกจัดการ
5. Decorators (สำหรับการจัดการข้อผิดพลาดขั้นสูง - ไม่ค่อยได้ใช้โดยตรงสำหรับการนำไปใช้ขอบเขต)
แม้ว่าจะไม่ใช่รูปแบบโดยตรงสำหรับ error boundaries แต่ decorators สามารถใช้เพื่อใช้ตรรกะการจัดการข้อผิดพลาดในลักษณะเชิงประกาศ วิธีนี้สามารถลดโค้ดที่ซ้ำซ้อนในโค้ดของคุณได้ อย่างไรก็ตาม การใช้งานนี้ไม่ค่อยพบเห็นบ่อยเท่ารูปแบบอื่น ๆ ด้านบนสำหรับการนำไปใช้ error boundary หลัก
function handleError(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
try {
const result = await originalMethod.apply(this, args);
return result;
} catch (error: any) {
console.error(`Error in ${propertyKey}:`, error);
// Handle the error here (e.g., log, display a default value, etc.)
return null; // Or throw a more specific error
}
};
return descriptor;
}
class MyService {
@handleError
async fetchData(url: string): Promise<any> {
// Simulate an error
if (Math.random() < 0.5) {
throw new Error('Simulated network error');
}
const response = await fetch(url);
return await response.json();
}
}
ตัวอย่างนี้กำหนด decorator `@handleError` decorator ห่อหุ้มเมธอดเดิม จับข้อผิดพลาดใด ๆ และบันทึกไว้ สิ่งนี้ช่วยให้การจัดการข้อผิดพลาดโดยไม่ต้องแก้ไขโค้ดของเมธอดเดิมโดยตรง
การนำ Error Boundaries ไปใช้ใน Frontend Frameworks (ตัวอย่าง React)
แม้ว่าแนวคิดหลักจะยังคงคล้ายคลึงกัน แต่รายละเอียดการนำไปใช้ของ error boundaries จะแตกต่างกันเล็กน้อย ขึ้นอยู่กับเฟรมเวิร์ก frontend ที่คุณใช้อยู่ เรามาเน้นที่ React ซึ่งเป็นเฟรมเวิร์กที่ได้รับความนิยมมากที่สุดในการสร้างส่วนต่อประสานผู้ใช้แบบโต้ตอบ
React Error Boundaries
React มีกลไกเฉพาะสำหรับการสร้าง error boundaries Error boundary เป็นส่วนประกอบ React ที่จับข้อผิดพลาด JavaScript ได้ทุกที่ในต้นไม้ส่วนประกอบลูก บันทึกข้อผิดพลาดเหล่านั้น และแสดง UI สำรอง แทนที่จะทำให้แอปพลิเคชันทั้งหมดล่ม Error boundaries จับข้อผิดพลาดระหว่างการเรนเดอร์ เมธอดวงจรชีวิต และคอนสตรัคเตอร์ของส่วนประกอบลูกทั้งหมด
เมธอดสำคัญสำหรับการสร้าง error boundary ใน React:
- `static getDerivedStateFromError(error)`: เมธอดแบบคงที่นี้จะถูกเรียกหลังจากส่วนประกอบลูกโยนข้อผิดพลาด มันรับข้อผิดพลาดเป็นพารามิเตอร์และควรส่งคืนออบเจกต์เพื่ออัปเดตสถานะ มันถูกใช้เพื่ออัปเดตสถานะ เช่น การตั้งค่าแฟล็ก `error` เป็น `true` เพื่อเรียก UI สำรอง
- `componentDidCatch(error, info)`: เมธอดนี้จะถูกเรียกหลังจากข้อผิดพลาดถูกโยนโดยส่วนประกอบลูก มันจะรับข้อผิดพลาดและออบเจกต์ที่มีข้อมูลเกี่ยวกับส่วนประกอบที่โยนข้อผิดพลาด โดยทั่วไปจะใช้สำหรับการบันทึกข้อผิดพลาด เมธอดนี้จะถูกเรียกสำหรับข้อผิดพลาดที่เกิดขึ้นระหว่างการเรนเดอร์ของส่วนประกอบลูกเท่านั้น
import React from 'react';
interface Props {
children: React.ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error: error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// You can also log the error to an error reporting service
console.error('Uncaught error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<p>We're working on fixing it!</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.stack}
</details>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
ส่วนประกอบ `ErrorBoundary` นี้ห่อหุ้มส่วนประกอบลูก หากมีข้อผิดพลาดถูกโยนภายในส่วนประกอบที่ห่อหุ้ม เมธอด `getDerivedStateFromError` จะถูกเรียกเพื่ออัปเดตสถานะ ทำให้ส่วนประกอบเรนเดอร์ใหม่ด้วย UI สำรอง เมธอด `componentDidCatch` ใช้สำหรับการบันทึกข้อผิดพลาด หากต้องการใช้ ErrorBoundary คุณเพียงแค่ห่อหุ้มส่วนต่างๆ ของแอปพลิเคชันของคุณไว้ภายใน:
import ErrorBoundary from './ErrorBoundary';
function App() {
return (
<div>
<ErrorBoundary>
<MyComponentThatMightError />
</ErrorBoundary>
<AnotherComponent />
</div>
);
}
โดยการวางส่วนประกอบ `ErrorBoundary` รอบส่วนประกอบที่อาจเกิดปัญหา คุณจะแยกส่วนประกอบเหล่านั้นและจัดเตรียม UI สำรองในกรณีที่เกิดข้อผิดพลาด ป้องกันไม่ให้แอปพลิเคชันทั้งหมดล่ม
Error Boundaries ในเฟรมเวิร์กอื่น ๆ (เชิงแนวคิด)
แม้ว่ารายละเอียดการนำไปใช้จะแตกต่างกันไป แต่แนวคิดหลักของ error boundaries สามารถนำไปใช้กับเฟรมเวิร์ก frontend อื่น ๆ เช่น Angular และ Vue.js ได้ คุณมักจะบรรลุสิ่งนี้ได้โดยใช้กลยุทธ์ที่คล้ายคลึงกัน:
- Angular: การใช้การจัดการข้อผิดพลาดส่วนประกอบ ตัวจัดการข้อผิดพลาดที่กำหนดเอง และ interceptors พิจารณาใช้คลาส `ErrorHandler` ของ Angular และห่อหุ้มส่วนประกอบที่อาจเกิดปัญหารอบ ๆ ด้วยตรรกะการจัดการข้อผิดพลาด
- Vue.js: การใช้บล็อก `try...catch` ภายในส่วนประกอบหรือใช้ตัวจัดการข้อผิดพลาดทั่วโลกที่ลงทะเบียนผ่าน `Vue.config.errorHandler` Vue ยังมีฟีเจอร์สำหรับการจัดการข้อผิดพลาดระดับส่วนประกอบที่คล้ายกับ error boundaries ของ React
แนวทางปฏิบัติที่ดีที่สุดสำหรับ Error Boundaries และการจัดการข้อผิดพลาด
เพื่อใช้ประโยชน์จาก error boundaries และรูปแบบชนิดข้อมูลการจัดการข้อผิดพลาดอย่างมีประสิทธิภาพ ให้พิจารณาแนวทางปฏิบัติที่ดีที่สุดเหล่านี้:
- แยกโค้ดที่อาจเกิดข้อผิดพลาด: ห่อหุ้มส่วนประกอบหรือส่วนของโค้ดที่มีแนวโน้มที่จะโยนข้อผิดพลาดไว้ภายใน error boundaries หรือโครงสร้างการจัดการข้อผิดพลาดที่เหมาะสม
- ให้ข้อความข้อผิดพลาดที่ชัดเจน: ออกแบบข้อความข้อผิดพลาดที่เป็นมิตรต่อผู้ใช้ซึ่งให้บริบทและคำแนะนำสำหรับผู้ใช้ หลีกเลี่ยงศัพท์ทางเทคนิคหรือที่เข้าใจยาก
- บันทึกข้อผิดพลาดอย่างมีประสิทธิภาพ: ใช้ระบบการบันทึกข้อผิดพลาดที่แข็งแกร่งเพื่อติดตามข้อผิดพลาด รวบรวมข้อมูลที่เกี่ยวข้อง (stack traces บริบทผู้ใช้ ฯลฯ) และอำนวยความสะดวกในการดีบั๊ก ใช้บริการเช่น Sentry, Bugsnag หรือ Rollbar สำหรับสภาพแวดล้อมการผลิต
- ใช้ UI สำรอง: จัดเตรียม UI สำรองที่มีความหมายซึ่งจัดการข้อผิดพลาดอย่างสง่างามและป้องกันไม่ให้แอปพลิเคชันทั้งหมดล่ม UI สำรองควรแจ้งผู้ใช้เกี่ยวกับสิ่งที่เกิดขึ้น และถ้าเหมาะสม ให้แนะนำการดำเนินการที่พวกเขาสามารถทำได้
- ใช้คลาสข้อผิดพลาดที่กำหนดเอง: สร้างคลาสข้อผิดพลาดที่กำหนดเองเพื่อแสดงประเภทข้อผิดพลาดที่แตกต่างกันและเพิ่มบริบทและข้อมูลเพิ่มเติมสำหรับการจัดการข้อผิดพลาดที่มีประสิทธิภาพมากขึ้น
- พิจารณาขอบเขตของ Error Boundaries: อย่าห่อหุ้มทั้งแอปพลิเคชันด้วย error boundary เดียว เพราะอาจซ่อนปัญหาที่ซ่อนอยู่ได้ แต่ให้วาง error boundaries อย่างมีกลยุทธ์รอบ ๆ ส่วนประกอบหรือส่วนของแอปพลิเคชัน
- ทดสอบการจัดการข้อผิดพลาด: เขียน unit tests และ integration tests เพื่อให้แน่ใจว่าตรรกะการจัดการข้อผิดพลาดของคุณทำงานตามที่คาดหวังและ UI สำรองจะแสดงผลอย่างถูกต้อง ทดสอบสถานการณ์ที่อาจเกิดข้อผิดพลาด
- ตรวจสอบและวิเคราะห์ข้อผิดพลาด: ตรวจสอบบันทึกข้อผิดพลาดของแอปพลิเคชันของคุณเป็นประจำเพื่อระบุปัญหาที่เกิดซ้ำ ติดตามแนวโน้มข้อผิดพลาด และระบุส่วนที่ต้องปรับปรุง
- มุ่งมั่นเพื่อการตรวจสอบข้อมูล: ตรวจสอบข้อมูลที่ได้รับจากแหล่งภายนอกเพื่อป้องกันข้อผิดพลาดที่ไม่คาดคิดที่เกิดจากรูปแบบข้อมูลที่ไม่ถูกต้อง
- จัดการ Promises และการดำเนินการแบบอะซิงโครนัสอย่างระมัดระวัง: ตรวจสอบให้แน่ใจว่าคุณจัดการข้อผิดพลาดที่อาจเกิดขึ้นในการดำเนินการแบบอะซิงโครนัสโดยใช้บล็อก `.catch()` หรือกลไกการจัดการข้อผิดพลาดที่เหมาะสม
ตัวอย่างจริงและข้อควรพิจารณาด้านนานาชาติ
มาสำรวจตัวอย่างที่เป็นประโยชน์ของวิธีที่ error boundaries และรูปแบบชนิดข้อมูลการจัดการข้อผิดพลาดสามารถนำไปใช้ได้ในสถานการณ์จริง โดยพิจารณาถึงการทำให้เป็นสากล:
ตัวอย่าง: แอปพลิเคชัน E-commerce (การดึงข้อมูล)
ลองนึกภาพแอปพลิเคชัน E-commerce ที่แสดงรายการสินค้า แอปพลิเคชันจะดึงข้อมูลผลิตภัณฑ์จาก API ฝั่งเซิร์ฟเวอร์ Error boundary ถูกใช้เพื่อจัดการปัญหาที่อาจเกิดขึ้นกับการเรียก API
interface Product {
id: number;
name: string;
price: number;
currency: string;
// ... other product details
}
class ProductList extends React.Component<{}, { products: Product[] | null; loading: boolean; error: Error | null }> {
state = { products: null, loading: true, error: null };
async componentDidMount() {
try {
const products = await this.fetchProducts();
this.setState({ products, loading: false });
} catch (error: any) {
this.setState({ error, loading: false });
}
}
async fetchProducts(): Promise<Product[]> {
const response = await fetch('/api/products'); // API endpoint
if (!response.ok) {
throw new Error(`Failed to fetch products: ${response.status}`);
}
return await response.json();
}
render() {
const { products, loading, error } = this.state;
if (loading) {
return <div>Loading products...</div>;
}
if (error) {
return (
<div className="error-message">
<p>Sorry, we're having trouble loading the products.</p>
<p>Please try again later.</p>
<p>Error details: {error.message}</p> {/* Log the error message for debugging */}
</div>
);
}
return (
<ul>
{products && products.map(product => (
<li key={product.id}>{product.name} - {product.price} {product.currency}</li>
))}
</ul>
);
}
}
// Error Boundary (React Component)
class ProductListErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean, error: Error | null}> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render will show the fallback UI.
return { hasError: true, error: error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// You can also log the error to an error reporting service
console.error('Product List Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
// Render a fallback UI (e.g., error message, retry button)
return (
<div className="product-list-error">
<h2>Oops, something went wrong!</h2>
<p>We are unable to load product information at this time.</p>
<button onClick={() => window.location.reload()} >Retry</button>
</div>
);
}
return this.props.children;
}
}
// Usage
function App() {
return (
<div>
<ProductListErrorBoundary>
<ProductList />
</ProductListErrorBoundary>
</div>
);
}
ในตัวอย่างนี้:
- `ProductList` ดึงข้อมูลผลิตภัณฑ์ มันจัดการสถานะการโหลด ข้อมูลผลิตภัณฑ์ที่สำเร็จ และสถานะข้อผิดพลาดภายในส่วนประกอบ
- `ProductListErrorBoundary` ใช้เพื่อห่อหุ้มส่วนประกอบ `ProductList` เพื่อจับข้อผิดพลาดระหว่างการเรนเดอร์และการเรียก API
- หากคำขอ API ล้มเหลว `ProductListErrorBoundary` จะแสดงข้อความข้อผิดพลาดที่เป็นมิตรต่อผู้ใช้แทนที่จะทำให้ UI ล่ม
- ข้อความข้อผิดพลาดมีตัวเลือก "ลองอีกครั้ง" ที่ให้ผู้ใช้รีเฟรช
- ฟิลด์ `currency` ในข้อมูลผลิตภัณฑ์สามารถแสดงได้อย่างถูกต้องโดยใช้ไลบรารีการทำให้เป็นสากล (เช่น Intl ใน JavaScript) ซึ่งให้การจัดรูปแบบสกุลเงินตามการตั้งค่าภาษาของผู้ใช้
ตัวอย่าง: การตรวจสอบความถูกต้องของแบบฟอร์มระหว่างประเทศ
พิจารณาแบบฟอร์มที่รวบรวมข้อมูลผู้ใช้ รวมถึงข้อมูลที่อยู่ การตรวจสอบความถูกต้องที่เหมาะสมเป็นสิ่งสำคัญ โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับผู้ใช้จากประเทศต่างๆ ที่มีรูปแบบที่อยู่แตกต่างกัน
// Assume a simplified address interface
interface Address {
street: string;
city: string;
postalCode: string;
country: string;
}
class AddressForm extends React.Component<{}, { address: Address; errors: { [key: string]: string } }> {
state = {
address: {
street: '',
city: '',
postalCode: '',
country: 'US', // Default country
},
errors: {},
};
handleChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = event.target;
this.setState((prevState) => ({
address: {
...prevState.address,
[name]: value,
},
errors: {
...prevState.errors,
[name]: '', // Clear any previous errors for this field
},
}));
};
handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const { address } = this.state;
const errors = this.validateAddress(address);
if (Object.keys(errors).length > 0) {
this.setState({ errors });
}
else {
// Submit the form (e.g., to an API)
alert('Form submitted!'); // Replace with actual submission logic
}
};
validateAddress = (address: Address) => {
const errors: { [key: string]: string } = {};
// Validation rules based on the selected country
if (!address.street) {
errors.street = 'Street address is required';
}
if (!address.city) {
errors.city = 'City is required';
}
// Example: postal code validation based on the country
switch (address.country) {
case 'US':
if (!/^[0-9]{5}(?:-[0-9]{4})?$/.test(address.postalCode)) {
errors.postalCode = 'Invalid US postal code';
}
break;
case 'CA':
if (!/^[A-Za-z][0-9][A-Za-z][ ]?[0-9][A-Za-z][0-9]$/.test(address.postalCode)) {
errors.postalCode = 'Invalid Canadian postal code';
}
break;
// Add more countries and validation rules
default:
if (!address.postalCode) {
errors.postalCode = 'Postal code is required';
}
break;
}
return errors;
};
render() {
const { address, errors } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<label htmlFor="street">Street:</label>
<input
type="text"
id="street"
name="street"
value={address.street}
onChange={this.handleChange}
/>
{errors.street && <div className="error">{errors.street}</div>}
<label htmlFor="city">City:</label>
<input
type="text"
id="city"
name="city"
value={address.city}
onChange={this.handleChange}
/>
{errors.city && <div className="error">{errors.city}</div>}
<label htmlFor="postalCode">Postal Code:</label>
<input
type="text"
id="postalCode"
name="postalCode"
value={address.postalCode}
onChange={this.handleChange}
/>
{errors.postalCode && <div className="error">{errors.postalCode}</div>}
<label htmlFor="country">Country:</label>
<select
id="country"
name="country"
value={address.country}
onChange={this.handleChange}
>
<option value="US">United States</option>
<option value="CA">Canada</option>
<!-- Add more countries -->
</select>
<button type="submit">Submit</button>
</form>
);
}
}
ในตัวอย่างนี้:
- ส่วนประกอบ `AddressForm` จัดการข้อมูลแบบฟอร์มและตรรกะการตรวจสอบความถูกต้อง
- ฟังก์ชัน `validateAddress` ทำการตรวจสอบตามประเทศที่เลือก
- ใช้กฎการตรวจสอบรหัสไปรษณีย์เฉพาะประเทศ (แสดง US และ CA)
- แอปพลิเคชันใช้ API `Intl` สำหรับการจัดรูปแบบที่รับทราบภาษา ผู้ใช้. ซึ่งจะใช้ในการจัดรูปแบบตัวเลข วันที่ และสกุลเงินตามการตั้งค่าภาษาของผู้ใช้ในปัจจุบัน
- ข้อความข้อผิดพลาดสามารถแปลเป็นภาษาต่างๆ เพื่อมอบประสบการณ์ผู้ใช้ที่ดีขึ้นทั่วโลก
- แนวทางนี้ช่วยให้ผู้ใช้กรอกแบบฟอร์มได้อย่างเป็นมิตรต่อผู้ใช้ โดยไม่คำนึงถึงสถานที่ตั้งของพวกเขา
แนวทางปฏิบัติที่ดีที่สุดในการทำให้เป็นสากล:
- ใช้ไลบรารีการแปลภาษา: ไลบรารีเช่น i18next, react-intl หรือ LinguiJS มีฟีเจอร์สำหรับการแปลข้อความ การจัดรูปแบบวันที่ ตัวเลข และสกุลเงินตามภาษาของผู้ใช้
- ให้การเลือกภาษา: อนุญาตให้ผู้ใช้เลือกภาษาและภูมิภาคที่ต้องการ สิ่งนี้สามารถทำได้ผ่านเมนูแบบเลื่อนลง การตั้งค่า หรือการตรวจจับอัตโนมัติตามการตั้งค่าเบราว์เซอร์
- จัดการรูปแบบวันที่ เวลา และตัวเลข: ใช้ API `Intl` เพื่อจัดรูปแบบวันที่ เวลา ตัวเลข และสกุลเงินให้เหมาะสมกับแต่ละภาษา
- พิจารณาการวางแนวข้อความ: ออกแบบ UI ของคุณเพื่อรองรับทั้งการวางแนวข้อความจากซ้ายไปขวา (LTR) และจากขวาไปซ้าย (RTL) มีไลบรารีที่ช่วยในการรองรับ RTL
- คำนึงถึงความแตกต่างทางวัฒนธรรม: คำนึงถึงบรรทัดฐานทางวัฒนธรรมเมื่อออกแบบ UI และข้อความข้อผิดพลาดของคุณ หลีกเลี่ยงการใช้ภาษาหรือภาพที่อาจไม่เหมาะสมหรือก่อให้เกิดการล่วงละเมิดในบางวัฒนธรรม
- ทดสอบในภาษาต่างๆ: ทดสอบแอปพลิเคชันของคุณอย่างละเอียดในภาษาต่างๆ เพื่อให้แน่ใจว่าการแปลและการจัดรูปแบบทำงานได้อย่างถูกต้องและ UI จะแสดงผลอย่างถูกต้อง
บทสรุป
TypeScript error boundaries และรูปแบบชนิดข้อมูลการจัดการข้อผิดพลาดที่มีประสิทธิภาพเป็นส่วนประกอบที่จำเป็นในการสร้างแอปพลิเคชันที่เชื่อถือได้และใช้งานง่าย การนำแนวทางปฏิบัติเหล่านี้ไปใช้ คุณสามารถป้องกันการขัดข้องที่ไม่คาดคิด ปรับปรุงประสบการณ์ผู้ใช้ และปรับปรุงกระบวนการดีบั๊กและการบำรุงรักษา ตั้งแต่บล็อก `try-catch` พื้นฐานไปจนถึงประเภท `Result` ที่ซับซ้อนกว่าและคลาสข้อผิดพลาดที่กำหนดเอง รูปแบบเหล่านี้ช่วยให้คุณสามารถสร้างแอปพลิเคชันที่ทนทานซึ่งสามารถทนทานต่อความท้าทายของโลกแห่งความเป็นจริงได้ ด้วยการยอมรับเทคนิคเหล่านี้ คุณจะเขียนโค้ด TypeScript ได้ดีขึ้น และมอบประสบการณ์ที่ดีขึ้นให้กับผู้ใช้ทั่วโลก
จำไว้ว่าให้เลือกรูปแบบการจัดการข้อผิดพลาดที่เหมาะสมกับความต้องการของโครงการและความซับซ้อนของแอปพลิเคชันของคุณ ให้ความสำคัญกับการให้ข้อความข้อผิดพลาดที่ชัดเจน ให้ข้อมูล และ UI สำรองที่แนะนำผู้ใช้ผ่านปัญหาที่อาจเกิดขึ้นเสมอ ด้วยการปฏิบัติตามแนวทางเหล่านี้ คุณสามารถสร้างแอปพลิเคชันที่ยืดหยุ่น บำรุงรักษาได้ และประสบความสำเร็จในตลาดโลก
พิจารณาการทดลองกับรูปแบบและเทคนิคเหล่านี้ในโครงการของคุณ และปรับให้เข้ากับข้อกำหนดเฉพาะของแอปพลิเคชันของคุณ แนวทางนี้จะส่งผลให้คุณภาพโค้ดดีขึ้นและประสบการณ์ที่ดีขึ้นสำหรับผู้ใช้ทุกคน