الدوال في TypeScript
الدوال في TypeScript
الدوال هي اللبنات الأساسية لأي تطبيق TypeScript. تعمل TypeScript على توسيع دوال JavaScript بتعليقات الأنواع القوية للمعاملات وقيم الإرجاع وتوقيعات الدوال. هذا يسمح لك باكتشاف الأخطاء مبكراً وكتابة كود أكثر قابلية للصيانة.
تعليقات أنواع الدوال
الطريقة الأساسية الأكثر إضافة الأنواع إلى الدوال هي من خلال إضافة تعليقات المعاملات وأنواع الإرجاع:
// دالة مع تعليقات المعامل ونوع الإرجاع
function add(a: number, b: number): number {
return a + b;
}
// دالة السهم مع تعليقات الأنواع
const multiply = (a: number, b: number): number => {
return a * b;
};
// دالة السهم المختصرة
const subtract = (a: number, b: number): number => a - b;
// دالة بدون قيمة إرجاع (void)
function logMessage(message: string): void {
console.log(message);
}
// الاستخدام
const sum = add(5, 3); // sum: number = 8
const product = multiply(4, 7); // product: number = 28
logMessage('Hello, TypeScript!'); // ترجع void
المعاملات الاختيارية
المعاملات الاختيارية تسمح لك باستدعاء دالة دون تقديم جميع الوسائط. قم بتحديد معامل كاختياري عن طريق إضافة علامة استفهام (?) بعد اسم المعامل.
// دالة مع معامل اختياري
function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}
// الاستخدام
console.log(greet('Alice')); // "Hello, Alice!"
console.log(greet('Bob', 'Good morning')); // "Good morning, Bob!"
// معاملات اختيارية متعددة
function createUser(
username: string,
email?: string,
age?: number
): object {
return {
username,
email: email || 'N/A',
age: age || 0
};
}
const user1 = createUser('john_doe');
const user2 = createUser('jane_smith', 'jane@example.com');
const user3 = createUser('bob_jones', 'bob@example.com', 30);
المعاملات الافتراضية
المعاملات الافتراضية توفر قيمة احتياطية إذا لم يتم تمرير وسيط. على عكس المعاملات الاختيارية، لا تحتاج المعاملات الافتراضية إلى علامة ?.
// دالة مع معامل افتراضي
function calculatePrice(
price: number,
taxRate: number = 0.1,
discount: number = 0
): number {
const taxAmount = price * taxRate;
const discountAmount = price * discount;
return price + taxAmount - discountAmount;
}
// الاستخدام
console.log(calculatePrice(100)); // يستخدم الافتراضيات: 110
console.log(calculatePrice(100, 0.15)); // ضريبة مخصصة: 115
console.log(calculatePrice(100, 0.1, 0.2)); // ضريبة وخصم مخصصان: 90
// المعاملات الافتراضية يمكن أن تشير إلى المعاملات السابقة
function buildUrl(
protocol: string = 'https',
domain: string,
path: string = '/'
): string {
return `${protocol}://${domain}${path}`;
}
console.log(buildUrl('http', 'example.com'));
// "http://example.com/"
console.log(buildUrl(undefined, 'example.com', '/about'));
// "https://example.com/about"
معاملات الباقي
معاملات الباقي تسمح لدالة بقبول عدد غير محدد من الوسائط كمصفوفة. استخدم عامل الانتشار (...) مع تعليق نوع للمصفوفة.
// دالة مع معاملات الباقي
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
// الاستخدام
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
console.log(sum()); // 0
// معاملات الباقي مع معاملات أخرى
function createTeam(
teamName: string,
captain: string,
...members: string[]
): object {
return {
name: teamName,
captain,
members,
totalMembers: members.length + 1 // +1 للقائد
};
}
const team = createTeam(
'Alpha Team',
'John',
'Alice',
'Bob',
'Charlie'
);
console.log(team);
// {
// name: 'Alpha Team',
// captain: 'John',
// members: ['Alice', 'Bob', 'Charlie'],
// totalMembers: 4
// }
// معاملات الباقي مع أنواع مختلفة
function logValues(prefix: string, ...values: (string | number)[]): void {
values.forEach(value => {
console.log(`${prefix}: ${value}`);
});
}
logValues('Item', 'apple', 42, 'banana', 100);
تعبيرات نوع الدالة
يمكنك تعريف نوع الدالة بشكل منفصل واستخدامه لتحديد نوع دوال متعددة:
// تعريف نوع الدالة
type MathOperation = (a: number, b: number) => number;
// دوال تطابق النوع
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;
const divide: MathOperation = (a, b) => b !== 0 ? a / b : 0;
// دالة تقبل دالة أخرى
function calculate(
a: number,
b: number,
operation: MathOperation
): number {
return operation(a, b);
}
// الاستخدام
console.log(calculate(10, 5, add)); // 15
console.log(calculate(10, 5, subtract)); // 5
console.log(calculate(10, 5, multiply)); // 50
console.log(calculate(10, 5, divide)); // 2
// نوع دالة أكثر تعقيداً
type Validator = (input: string) => {
isValid: boolean;
errors: string[];
};
const emailValidator: Validator = (input) => {
const errors: string[] = [];
if (!input.includes('@')) {
errors.push('Email must contain @');
}
if (input.length < 5) {
errors.push('Email too short');
}
return {
isValid: errors.length === 0,
errors
};
};
console.log(emailValidator('test@example.com')); // { isValid: true, errors: [] }
console.log(emailValidator('bad')); // { isValid: false, errors: [...] }
تحميلات الدالة الزائدة
تحميلات الدالة الزائدة تسمح لك بتعريف توقيعات دالة متعددة لنفس الدالة. هذا مفيد عندما يمكن استدعاء دالة بأنواع معاملات مختلفة أو أعداد معاملات مختلفة.
// توقيعات التحميل الزائد
function formatDate(date: Date): string;
function formatDate(timestamp: number): string;
function formatDate(year: number, month: number, day: number): string;
// توقيع التنفيذ (يجب أن يكون متوافقاً مع جميع التحميلات الزائدة)
function formatDate(
dateOrYear: Date | number,
month?: number,
day?: number
): string {
if (dateOrYear instanceof Date) {
// تم الاستدعاء بكائن Date
return dateOrYear.toISOString().split('T')[0];
} else if (month !== undefined && day !== undefined) {
// تم الاستدعاء بالسنة والشهر واليوم
return `${dateOrYear}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
} else {
// تم الاستدعاء بالطابع الزمني
return new Date(dateOrYear).toISOString().split('T')[0];
}
}
// الاستخدام - TypeScript تفرض توقيعات التحميل الزائد
const date1 = formatDate(new Date()); // كائن Date
const date2 = formatDate(1707926400000); // الطابع الزمني
const date3 = formatDate(2024, 2, 14); // السنة والشهر واليوم
console.log(date1); // "2024-02-14"
console.log(date2); // "2024-02-14"
console.log(date3); // "2024-02-14"
// مثال آخر: دالة البحث
function search(query: string): string[];
function search(query: string, limit: number): string[];
function search(query: string, limit: number, offset: number): string[];
function search(
query: string,
limit?: number,
offset?: number
): string[] {
// محاكاة البحث في قاعدة البيانات
const allResults = [
`Result 1 for "${query}"`,
`Result 2 for "${query}"`,
`Result 3 for "${query}"`,
`Result 4 for "${query}"`,
`Result 5 for "${query}"`
];
const start = offset || 0;
const end = limit ? start + limit : allResults.length;
return allResults.slice(start, end);
}
console.log(search('typescript')); // جميع النتائج
console.log(search('typescript', 2)); // أول نتيجتين
console.log(search('typescript', 2, 2)); // نتيجتان تبدآن من الفهرس 2
الدوال العامة
الدوال العامة تسمح لك بكتابة دوال تعمل مع أنواع متعددة مع الحفاظ على سلامة الأنواع:
// دالة عامة مع معامل النوع
function identity<T>(value: T): T {
return value;
}
// الاستخدام - يتم استنتاج النوع
const num = identity(42); // T هو number
const str = identity('hello'); // T هو string
const arr = identity([1, 2, 3]); // T هو number[]
// دالة عامة مع معاملات نوع متعددة
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const pair1 = pair('age', 30); // [string, number]
const pair2 = pair(true, 'success'); // [boolean, string]
// دالة عامة مع قيود
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = {
name: 'Alice',
age: 30,
email: 'alice@example.com'
};
const name = getProperty(person, 'name'); // string
const age = getProperty(person, 'age'); // number
// const invalid = getProperty(person, 'invalid'); // خطأ: مفتاح غير صالح
// عمليات المصفوفة العامة
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
const numbers = [1, 2, 3, 4, 5];
const firstNum = first(numbers); // number | undefined
const lastNum = last(numbers); // number | undefined
const words = ['hello', 'world'];
const firstWord = first(words); // string | undefined
const lastWord = last(words); // string | undefined
نوع معامل this
تسمح لك TypeScript بإعلان نوع this في دالة:
interface User {
name: string;
age: number;
greet(this: User): void;
}
const user: User = {
name: 'Alice',
age: 30,
greet() {
// TypeScript تعرف أن 'this' هي User
console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old`);
}
};
user.greet(); // "Hello, I'm Alice and I'm 30 years old"
// منع ربط this غير صحيح
const greetFunction = user.greet;
// greetFunction(); // خطأ: سياق 'this' غير قابل للتعيين
// استخدام this في دوال مستقلة
interface Database {
host: string;
port: number;
}
function connect(this: Database): string {
return `Connecting to ${this.host}:${this.port}`;
}
const db: Database = {
host: 'localhost',
port: 5432
};
// الاستدعاء بالسياق الصحيح
console.log(connect.call(db)); // "Connecting to localhost:5432"
دوال الاستدعاء الراجعة
اكتب دوال الاستدعاء الراجعة بشكل صحيح لضمان سلامة الأنواع:
// دالة تقبل استدعاء راجع
function processArray(
items: number[],
callback: (item: number, index: number) => void
): void {
items.forEach((item, index) => {
callback(item, index);
});
}
// الاستخدام
processArray([1, 2, 3, 4, 5], (num, idx) => {
console.log(`Item ${idx}: ${num * 2}`);
});
// استدعاء راجع مع قيمة إرجاع
function filterArray<T>(
items: T[],
predicate: (item: T) => boolean
): T[] {
return items.filter(predicate);
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = filterArray(numbers, num => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8, 10]
// استدعاء راجع غير متزامن
function fetchData(
url: string,
onSuccess: (data: unknown) => void,
onError: (error: Error) => void
): void {
fetch(url)
.then(response => response.json())
.then(data => onSuccess(data))
.catch(error => onError(error));
}
fetchData(
'https://api.example.com/data',
(data) => console.log('Success:', data),
(error) => console.error('Error:', error.message)
);
الدوال غير المتزامنة
الدوال غير المتزامنة في TypeScript ترجع نوع Promise:
// دالة غير متزامنة مع نوع إرجاع صريح
async function fetchUser(id: string): Promise<{
id: string;
name: string;
email: string;
}> {
// محاكاة استدعاء API
await new Promise(resolve => setTimeout(resolve, 1000));
return {
id,
name: 'John Doe',
email: 'john@example.com'
};
}
// استخدام الدالة غير المتزامنة
async function main() {
try {
const user = await fetchUser('123');
console.log(user.name); // TypeScript تعرف شكل user
} catch (error) {
console.error('Failed to fetch user:', error);
}
}
// دالة غير متزامنة عامة
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as T;
}
// الاستخدام مع معامل النوع
interface Post {
id: number;
title: string;
body: string;
}
async function loadPost(id: number): Promise<Post> {
return await fetchData<Post>(`https://api.example.com/posts/${id}`);
}
- أنشئ دالة
calculateDiscountالتي تأخذprice: number، اختياريةdiscountPercent: number(افتراضي 10)، واختياريةmemberDiscount: number. أرجع السعر النهائي بعد تطبيق الخصومات. - أنشئ دالة عامة
groupByالتي تأخذ مصفوفة من العناصر ودالة محدد المفتاح، وترجع كائناً يجمع العناصر حسب المفتاح المحدد. - أنشئ تحميلات زائدة للدالة
getValueالتي تقبل إما مفتاح سلسلة أو فهرس رقمي لاسترداد القيم من مصفوفة أو كائن. - أنشئ دالة غير متزامنة
fetchUserPostsالتي تأخذ معرف المستخدم وترجع Promise لمصفوفة من المشاركات. - اختبر جميع دوالك مع بيانات نموذجية.
الملخص
- أنواع المعاملات وأنواع الإرجاع تجعل الدوال آمنة من حيث الأنواع وموثقة ذاتياً
- المعاملات الاختيارية تستخدم
?ويجب أن تأتي بعد المعاملات المطلوبة - المعاملات الافتراضية توفر قيم احتياطية وتكون اختيارية تلقائياً
- معاملات الباقي تستخدم
...لقبول عدد غير محدد من الوسائط كمصفوفة - تعبيرات نوع الدالة تسمح لك بتعريف أنواع دوال قابلة لإعادة الاستخدام
- تحميلات الدالة الزائدة تعرف توقيعات استدعاء متعددة لنفس الدالة
- الدوال العامة تعمل مع أنواع متعددة مع الحفاظ على سلامة الأنواع
- نوع معامل this يضمن ربط السياق الصحيح في الدوال
- الدوال غير المتزامنة ترجع أنواع
Promise<T>تلقائياً