أتقن حدود الأخطاء في TypeScript لبناء تطبيقات مرنة. استكشف أنماط أنواع معالجة الأخطاء المختلفة، وأفضل الممارسات، وأمثلة من العالم الحقيقي.
حدود الأخطاء في TypeScript: أنماط أنواع معالجة الأخطاء للتطبيقات القوية
في عالم تطوير البرمجيات، الأخطاء غير المتوقعة أمر لا مفر منه. من مشاكل الشبكة إلى تنسيقات البيانات غير المتوقعة، يجب أن تكون التطبيقات مستعدة للتعامل مع هذه المواقف بسلاسة. تقدم TypeScript، بنظام أنواعها القوي، إطار عمل متينًا لبناء تطبيقات مرنة. تتعمق هذه المقالة في مفهوم حدود الأخطاء في TypeScript، وتستكشف أنماط أنواع معالجة الأخطاء المختلفة، وأفضل الممارسات، والأمثلة الواقعية لتزويدك بالمعرفة اللازمة لإنشاء كود أكثر استقرارًا وقابلية للصيانة.
فهم أهمية معالجة الأخطاء
تعتبر معالجة الأخطاء الفعالة أمرًا بالغ الأهمية لتجربة المستخدم الإيجابية والصحة العامة للتطبيق. عندما تترك الأخطاء دون معالجة، يمكن أن تؤدي إلى:
- الانهيارات والسلوك غير المتوقع: يمكن أن توقف الاستثناءات غير المعالجة تنفيذ الكود الخاص بك، مما يؤدي إلى انهيارات أو نتائج غير متوقعة.
- فقدان البيانات وتلفها: يمكن أن تؤدي الأخطاء أثناء معالجة البيانات أو تخزينها إلى فقدان البيانات أو تلفها، مما يؤثر على المستخدمين وسلامة النظام.
- الثغرات الأمنية: يمكن أن تكشف معالجة الأخطاء السيئة عن معلومات حساسة أو تخلق فرصًا لهجمات خبيثة.
- تجربة مستخدم سلبية: من المرجح أن يواجه المستخدمون الذين يصادفون رسائل خطأ غامضة أو فشلًا في التطبيق تجربة محبطة، مما يؤدي إلى فقدان الثقة والإقبال على الاستخدام.
- انخفاض الإنتاجية: يقضي المطورون وقتًا في تصحيح الأخطاء غير المعالجة وحلها، مما يعيق إنتاجية التطوير الإجمالية ويبطئ دورات الإصدار.
من ناحية أخرى، توفر معالجة الأخطاء الجيدة ما يلي:
- التدهور السلس: يستمر التطبيق في العمل، حتى لو واجه جزء معين خطأ.
- ملاحظات إعلامية: يتلقى المستخدمون رسائل خطأ واضحة وموجزة، مما يساعدهم على فهم المشكلة وحلها.
- سلامة البيانات: تتم إدارة العمليات المهمة بطريقة تعاملية، مما يحمي معلومات المستخدم الهامة.
- تحسين الاستقرار: يصبح التطبيق أكثر مرونة في مواجهة الأحداث غير المتوقعة.
- تعزيز قابلية الصيانة: يسهل تحديد المشكلات وتشخيصها وإصلاحها عند ظهورها.
ما هي حدود الأخطاء في TypeScript؟
حدود الأخطاء هي نمط تصميمي يستخدم لالتقاط أخطاء JavaScript داخل جزء معين من شجرة المكونات وعرض واجهة مستخدم بديلة بسلاسة بدلاً من انهيار التطبيق بأكمله. في حين أن TypeScript نفسها لا تحتوي على ميزة "حدود الأخطاء" المحددة، فإن المبادئ والتقنيات لإنشاء مثل هذه الحدود يتم تطبيقها وتعزيزها بسهولة من خلال أمان الأنواع في TypeScript.
الفكرة الأساسية هي عزل الكود الذي يحتمل أن يكون عرضة للأخطاء داخل مكون أو وحدة مخصصة. يعمل هذا المكون كغلاف، حيث يراقب الكود الموجود بداخله. إذا حدث خطأ، فإن مكون حدود الأخطاء "يلتقط" الخطأ، ويمنعه من الانتشار لأعلى في شجرة المكونات واحتمال انهيار التطبيق. بدلاً من ذلك، يمكن لحدود الأخطاء عرض واجهة مستخدم بديلة، أو تسجيل الخطأ، أو محاولة التعافي من المشكلة.
فوائد استخدام حدود الأخطاء هي:
- العزل: يمنع الأخطاء في جزء واحد من تطبيقك من التأثير على الأجزاء الأخرى.
- واجهة مستخدم بديلة: يوفر تجربة أكثر سهولة للمستخدم من تطبيق معطل بالكامل.
- تسجيل الأخطاء: يسهل جمع معلومات الأخطاء لتصحيح الأخطاء والمراقبة.
- تحسين قابلية الصيانة: يبسط منطق معالجة الأخطاء ويجعل من السهل تحديث الكود وصيانته.
أنماط أنواع معالجة الأخطاء في 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، خاصة عند التعامل مع واجهات برمجة التطبيقات (APIs) وتفاعلات قاعدة البيانات وإدخال/إخراج الملفات. توفر الـ 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) (لمعالجة الأخطاء المتقدمة - نادرًا ما تستخدم مباشرة لتنفيذ حدود الأخطاء)
على الرغم من أنها ليست نمطًا مباشرًا لحدود الأخطاء، إلا أنه يمكن استخدام المزخرفات (decorators) لتطبيق منطق معالجة الأخطاء على التوابع بطريقة تصريحية. يمكن أن يقلل هذا من الكود المتكرر في الكود الخاص بك. ومع ذلك، فإن هذا الاستخدام أقل شيوعًا من الأنماط الأخرى المذكورة أعلاه لتنفيذ حدود الأخطاء الأساسية.
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();
}
}
يعرّف هذا المثال مزخرف `@handleError`. يغلف المزخرف التابع الأصلي، ويلتقط أي أخطاء ويسجلها. يسمح هذا بمعالجة الأخطاء دون تعديل كود التابع الأصلي مباشرة.
تنفيذ حدود الأخطاء في أطر عمل الواجهة الأمامية (مثال React)
بينما تظل المفاهيم الأساسية متشابهة، يختلف تنفيذ حدود الأخطاء قليلاً اعتمادًا على إطار عمل الواجهة الأمامية الذي تستخدمه. دعنا نركز على React، وهو إطار العمل الأكثر شيوعًا لبناء واجهات مستخدم تفاعلية.
حدود الأخطاء في React
يوفر React آلية محددة لإنشاء حدود الأخطاء. حدود الأخطاء هي مكون React يلتقط أخطاء JavaScript في أي مكان في شجرة المكونات الفرعية الخاصة به، ويسجل تلك الأخطاء، ويعرض واجهة مستخدم بديلة بدلاً من انهيار التطبيق بأكمله. تلتقط حدود الأخطاء الأخطاء أثناء العرض (rendering)، وتوابع دورة الحياة، ومنشئات (constructors) جميع مكوناتها الفرعية.
التوابع الرئيسية لإنشاء حدود أخطاء في React:
- `static getDerivedStateFromError(error)`: يتم استدعاء هذا التابع الثابت بعد أن يطرح مكون فرعي خطأ. يتلقى الخطأ كمعلمة ويجب أن يُرجع كائنًا لتحديث الحالة. يستخدم لتحديث الحالة، مثل تعيين علامة `error` إلى `true` لتشغيل واجهة المستخدم البديلة.
- `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` لتحديث الحالة، مما يتسبب في إعادة عرض المكون مع واجهة المستخدم البديلة. يستخدم تابع `componentDidCatch` لتسجيل الأخطاء. لاستخدام ErrorBoundary، يمكنك ببساطة تغليف أجزاء من تطبيقك بداخله:
import ErrorBoundary from './ErrorBoundary';
function App() {
return (
<div>
<ErrorBoundary>
<MyComponentThatMightError />
</ErrorBoundary>
<AnotherComponent />
</div>
);
}
من خلال وضع مكون `ErrorBoundary` حول المكونات التي يحتمل أن تكون إشكالية، فإنك تعزل تلك المكونات وتوفر واجهة مستخدم بديلة في حالة حدوث أخطاء، مما يمنع التطبيق بأكمله من الانهيار.
حدود الأخطاء في أطر العمل الأخرى (مفاهيميًا)
بينما تختلف تفاصيل التنفيذ، يمكن تطبيق المبادئ الأساسية لحدود الأخطاء على أطر عمل الواجهة الأمامية الأخرى مثل Angular و Vue.js. ستحقق هذا عادةً باستخدام استراتيجيات مماثلة:
- Angular: استخدام معالجة أخطاء المكونات، ومعالجات الأخطاء المخصصة، والمعترضات (interceptors). فكر في استخدام فئة `ErrorHandler` في Angular وتغليف المكونات التي يحتمل أن تكون إشكالية بمنطق معالجة الأخطاء.
- Vue.js: استخدام كتل `try...catch` داخل المكونات أو استخدام معالجات الأخطاء العامة المسجلة عبر `Vue.config.errorHandler`. يحتوي Vue أيضًا على ميزات لمعالجة الأخطاء على مستوى المكونات تشبه حدود الأخطاء في React.
أفضل الممارسات لحدود الأخطاء ومعالجة الأخطاء
للاستفادة الفعالة من حدود الأخطاء وأنماط أنواع معالجة الأخطاء، ضع في اعتبارك هذه الممارسات الأفضل:
- عزل الكود المعرض للأخطاء: غلف المكونات أو أقسام الكود التي من المحتمل أن تطرح أخطاءً داخل حدود الأخطاء أو بنيات معالجة الأخطاء المناسبة.
- توفير رسائل خطأ واضحة: صمم رسائل خطأ سهلة الاستخدام توفر سياقًا وإرشادات للمستخدم. تجنب المصطلحات الغامضة أو التقنية.
- تسجيل الأخطاء بفعالية: نفذ نظامًا قويًا لتسجيل الأخطاء لتتبع الأخطاء، وجمع المعلومات ذات الصلة (تتبعات المكدس، سياق المستخدم، إلخ)، وتسهيل تصحيح الأخطاء. استخدم خدمات مثل Sentry أو Bugsnag أو Rollbar لبيئات الإنتاج.
- تنفيذ واجهات مستخدم بديلة: وفر واجهات مستخدم بديلة ذات معنى تتعامل مع الأخطاء بسلاسة وتمنع التطبيق بأكمله من الانهيار. يجب أن تُعلم الواجهة البديلة المستخدم بما حدث، وإذا كان مناسبًا، تقترح الإجراءات التي يمكنه اتخاذها.
- استخدام فئات الأخطاء المخصصة: أنشئ فئات أخطاء مخصصة لتمثيل أنواع مختلفة من الأخطاء وإضافة سياق ومعلومات إضافية لمعالجة الأخطاء بشكل أكثر فعالية.
- ضع في اعتبارك نطاق حدود الأخطاء: لا تغلف التطبيق بأكمله في حدود أخطاء واحدة، فقد يخفي ذلك المشكلات الأساسية. بدلاً من ذلك، ضع حدود الأخطاء بشكل استراتيجي حول المكونات أو أجزاء من التطبيق.
- اختبار معالجة الأخطاء: اكتب اختبارات الوحدة واختبارات التكامل للتأكد من أن منطق معالجة الأخطاء يعمل كما هو متوقع وأن واجهات المستخدم البديلة يتم عرضها بشكل صحيح. اختبر السيناريوهات التي قد تحدث فيها أخطاء.
- مراقبة وتحليل الأخطاء: راقب سجلات أخطاء تطبيقك بانتظام لتحديد المشكلات المتكررة، وتتبع اتجاهات الأخطاء، وتحديد مجالات التحسين.
- السعي للتحقق من صحة البيانات: تحقق من صحة البيانات الواردة من مصادر خارجية لمنع الأخطاء غير المتوقعة الناتجة عن تنسيقات البيانات غير الصحيحة.
- تعامل مع الـ Promises والعمليات غير المتزامنة بعناية: تأكد من معالجة الأخطاء التي يمكن أن تحدث في العمليات غير المتزامنة باستخدام كتل `.catch()` أو آليات معالجة الأخطاء المناسبة.
أمثلة من العالم الحقيقي والاعتبارات الدولية
دعنا نستكشف بعض الأمثلة العملية لكيفية تطبيق حدود الأخطاء وأنماط أنواع معالجة الأخطاء في سيناريوهات العالم الحقيقي، مع مراعاة التدويل:
مثال: تطبيق التجارة الإلكترونية (جلب البيانات)
تخيل تطبيق تجارة إلكترونية يعرض قوائم المنتجات. يقوم التطبيق بجلب بيانات المنتج من واجهة برمجة تطبيقات خلفية. يتم استخدام حدود الأخطاء للتعامل مع المشكلات المحتملة في استدعاءات واجهة برمجة التطبيقات.
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` لالتقاط الأخطاء أثناء العرض واستدعاءات واجهة برمجة التطبيقات.
- إذا فشل طلب واجهة برمجة التطبيقات، سيعرض `ProductListErrorBoundary` رسالة خطأ سهلة الاستخدام بدلاً من انهيار واجهة المستخدم.
- توفر رسالة الخطأ خيار “إعادة المحاولة” مما يسمح للمستخدم بالتحديث.
- يمكن عرض حقل `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` بإجراء عمليات التحقق بناءً على البلد المحدد.
- يتم تطبيق قواعد التحقق من الرمز البريدي الخاصة بكل بلد (تظهر الولايات المتحدة وكندا).
- يستخدم التطبيق واجهة برمجة التطبيقات `Intl` للتنسيق المدرك للمنطقة. سيتم استخدام هذا لتنسيق الأرقام والتواريخ والعملات ديناميكيًا وفقًا للمنطقة المحلية للمستخدم الحالي.
- يمكن ترجمة رسائل الخطأ لتوفير تجربة مستخدم أفضل على مستوى العالم.
- يسمح هذا النهج للمستخدمين بملء النموذج بطريقة سهلة الاستخدام، بغض النظر عن موقعهم.
أفضل الممارسات للتدويل:
- استخدام مكتبة توطين: توفر مكتبات مثل i18next، أو react-intl، أو LinguiJS ميزات لترجمة النصوص، وتنسيق التواريخ، والأرقام، والعملات بناءً على المنطقة المحلية للمستخدم.
- توفير اختيار المنطقة المحلية: اسمح للمستخدمين باختيار لغتهم ومنطقتهم المفضلة. يمكن أن يكون هذا من خلال قائمة منسدلة، أو إعدادات، أو كشف تلقائي بناءً على إعدادات المتصفح.
- التعامل مع تنسيقات التاريخ والوقت والأرقام: استخدم واجهة برمجة التطبيقات `Intl` لتنسيق التواريخ، والأوقات، والأرقام، والعملات بشكل مناسب لمختلف المناطق المحلية.
- مراعاة اتجاه النص: صمم واجهة المستخدم الخاصة بك لدعم اتجاهات النص من اليسار إلى اليمين (LTR) ومن اليمين إلى اليسار (RTL). توجد مكتبات للمساعدة في دعم RTL.
- مراعاة الاختلافات الثقافية: كن على دراية بالمعايير الثقافية عند تصميم واجهة المستخدم ورسائل الخطأ. تجنب استخدام لغة أو صور قد تكون مسيئة أو غير مناسبة في ثقافات معينة.
- الاختبار في مناطق محلية مختلفة: اختبر تطبيقك جيدًا في مناطق محلية مختلفة للتأكد من أن الترجمة والتنسيق يعملان بشكل صحيح وأن واجهة المستخدم يتم عرضها بشكل صحيح.
الخلاصة
تعد حدود الأخطاء في TypeScript وأنماط أنواع معالجة الأخطاء الفعالة مكونات أساسية لبناء تطبيقات موثوقة وسهلة الاستخدام. من خلال تنفيذ هذه الممارسات، يمكنك منع الانهيارات غير المتوقعة، وتعزيز تجربة المستخدم، وتبسيط عمليات تصحيح الأخطاء والصيانة. من كتل `try-catch` الأساسية إلى نوع `Result` الأكثر تطورًا وفئات الأخطاء المخصصة، تمكّنك هذه الأنماط من إنشاء تطبيقات قوية يمكنها الصمود أمام تحديات العالم الحقيقي. من خلال تبني هذه التقنيات، ستكتب كود TypeScript أفضل، وستوفر تجربة أفضل لمستخدميك العالميين.
تذكر أن تختار أنماط معالجة الأخطاء التي تناسب احتياجات مشروعك وتعقيد تطبيقك على أفضل وجه. ركز دائمًا على توفير رسائل خطأ واضحة وغنية بالمعلومات وواجهات مستخدم بديلة توجه المستخدمين خلال أي مشكلات محتملة. باتباع هذه الإرشادات، يمكنك إنشاء تطبيقات أكثر مرونة وقابلية للصيانة، وفي النهاية، ناجحة في السوق العالمية.
فكر في تجربة هذه الأنماط والتقنيات في مشاريعك، وتكييفها لتناسب المتطلبات المحددة لتطبيقك. سيساهم هذا النهج في تحسين جودة الكود وتجربة أكثر إيجابية لجميع المستخدمين.