TypeScript: حراس الأنواع وتضييق النطاق في TypeScript
آخر تحديث: 2026-08-26
يُعد تضييق النوع إحدى الميزات الأكثر فائدة في TypeScript — فهو يضيق نطاق الأنواع الواسعة ليصبح أكثر دقة، مما يتيح لك الوصول بأمان إلى الخصائص والطرق الخاصة بأنواع محددة.
1. نظرة عامة على تضييق النوع
لا يمكن للمتغيرات من نوع الاتحاد الوصول إلا إلى الخصائص المشتركة بين جميع العناصر. ويتيح لنا تضييق النوع «تضييق» النوع في فرع معين من الكود للوصول إلى العناصر الخاصة بذلك الفرع حصريًّا:
function process(value: string | number) {
// Before narrowing——value Only shared methods can be used
console.log(value.toString()); // ✅ string and number All of them
// After narrowing——value You can use a specific method
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✅ string Unique Approach
} else {
console.log(value.toFixed(2)); // ✅ number Unique Approach
}
}
يدعم TypeScript أنواع التضييق التالية:
| طريقة التضييق | السيناريوهات القابلة للتطبيق |
|---|---|
typeof |
التحقق من النوع الأساسي |
instanceof |
التحقق من مثيل الفئة |
in عامل |
هل هذه الخاصية موجودة؟ |
| فحص التكافؤ | مقارنة ===/!== |
| حراس الأنواع المخصصة | فحوصات الأنواع المعقدة |
| قابل للتضييق | التضييق تلقائيًا أثناء التخصيص |
2. تضييق نطاق نوع (typeof)
typeof تُستخدم في الغالب للتمييز بين الأنواع الأساسية:
(1) الاستخدام الأساسي
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
return " ".repeat(padding) + value; // padding narrowed to number
}
return padding + value; // padding narrowed to string
}
console.log(padLeft("hello", 4)); // " hello"
console.log(padLeft("hello", ">>>")); // ">>>hello"
(2) قيمة الإرجاع لـ typeof
typeof "hello" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 100n // "bigint"
typeof {} // "object" ⚠️ Including null, Array, Date, etc.
typeof function(){} // "function"
typeof null === "object" هو خطأ قديم في لغة جافا سكريبت. لا يمكنك استخدام typeof للتمييز بين null والكائنات الأخرى — بل عليك استخدام === null للتحقق من ذلك.
(3) استخدام typeof مع switch
function describe(value: string | number | boolean | undefined) {
switch (typeof value) {
case "string":
return `String:${value.toUpperCase()}`;
case "number":
return `Numbers:${value.toFixed(2)}`;
case "boolean":
return `Boolean:${value}`;
case "undefined":
return "Undefined";
}
}
3. تضييق نطاق instanceof
instanceof التحقق مما إذا كان الكائن مثيلًا لفئة معينة — مناسب لتضييق نطاق أنواع المراجع:
(1) الاستخدام الأساسي
function formatValue(value: Date | string | Error): string {
if (value instanceof Date) {
return value.toISOString(); // Date Methods
} else if (value instanceof Error) {
return value.message; // Error Properties
} else {
return value.toUpperCase(); // string Methods
}
}
console.log(formatValue(new Date())); // "2024-..."
console.log(formatValue(new Error("Error"))); // "Error"
console.log(formatValue("hello")); // "HELLO"
(2) قيود instanceof
instanceof لا يمكن استخدامها إلا مع مثيلات الفئات — وليس مع الواجهات أو الأسماء المستعارة للأنواع (التي لا توجد في وقت التشغيل):
interface Dog { bark(): void; }
interface Cat { meow(): void; }
// ❌ instanceof Cannot be used for interfaces
// if (pet instanceof Dog) { ... }
// ✅ Do you need to use a custom type guard or in Operator
(3) الفئات المخصصة وinstanceof
class NetworkError extends Error {
constructor(public statusCode: number) {
super(`Network Error:${statusCode}`);
}
}
class ValidationError extends Error {
constructor(public field: string) {
super(`Validation Error:${field}`);
}
}
function handleError(error: NetworkError | ValidationError): string {
if (error instanceof NetworkError) {
return `HTTP ${error.statusCode} Error`;
} else {
return `Field ${error.field} Invalid`;
}
}
4. تضييق نطاق عامل in
in يتحقق مما إذا كان الكائن يحتوي على خاصية معينة — وهو أمر مفيد للتمييز بين الواجهات وأسماء الأنواع البديلة:
(1) الاستخدام الأساسي
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // ✅ Fish Type
} else {
animal.fly(); // ✅ Bird Type
}
}
(2) السمات المميزة
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // ✅ Circle 's radius
} else {
return shape.sideLength ** 2; // ✅ Square 's sideLength
}
}
▶ مثال: معالجة الأحداث الآمنة من حيث النوع
interface ClickEvent {
type: "click";
x: number;
y: number;
}
interface KeyEvent {
type: "keydown" | "keyup";
key: string;
ctrlKey: boolean;
}
interface ScrollEvent {
type: "scroll";
scrollTop: number;
scrollLeft: number;
}
type UIEvent = ClickEvent | KeyEvent | ScrollEvent;
function handleEvent(event: UIEvent): string {
switch (event.type) {
case "click":
return `Click here:(${event.x}, ${event.y})`;
case "keydown":
case "keyup":
return `Button:${event.key},Ctrl:${event.ctrlKey}`;
case "scroll":
return `Scroll:top=${event.scrollTop}, left=${event.scrollLeft}`;
}
}
console.log(handleEvent({ type: "click", x: 100, y: 200 }));
console.log(handleEvent({ type: "keydown", key: "Enter", ctrlKey: false }));
console.log(handleEvent({ type: "scroll", scrollTop: 50, scrollLeft: 0 }));
الناتج:
Click here:(100, 200)
Button:Enter,Ctrl:false
Scroll:top=50, left=0
5. حراس الأنواع المخصصة
عندما لا تكون طرق التضييق المدمجة كافية، يمكنك كتابة دوال حماية أنواع مخصصة:
(1) المسند النوعي
interface Dog {
bark(): void;
breed: string;
}
interface Cat {
meow(): void;
color: string;
}
// Type Predicates:The return value is "Parameter Name is Type"
function isDog(animal: Dog | Cat): animal is Dog {
return "bark" in animal;
}
function interact(animal: Dog | Cat) {
if (isDog(animal)) {
animal.bark(); // ✅ narrowed to Dog
console.log(animal.breed);
} else {
animal.meow(); // ✅ narrowed to Cat
console.log(animal.color);
}
}
(2) دالة التأكيد
تُطلق دوال التأكيد استثناءً عندما لا تتحقق الشرط — لتُبلغ TypeScript بأن «إذا تم تنفيذ هذا السطر، فيجب أن يكون الشرط صحيحًا»:
function assertDefined<T>(value: T | undefined | null, message?: string): asserts value is NonNullable<T> {
if (value == null) {
throw new Error(message ?? "The value cannot be null or undefined");
}
}
function processUser(user: User | undefined) {
assertDefined(user, "User does not exist");
// After that user The type has been narrowed down to User(Excludes undefined)
console.log(user.name.toUpperCase()); // ✅ Safety
}
(3) التأكيدات مقابل المسندات النوعية
| الخاصية | مسند النوع x is T |
دالة التأكيد asserts x is T |
|---|---|---|
| قيمة الإرجاع | منطقية | فارغة (تُطلق استثناءً في حالة عدم استيفاء الشرط) |
| الاستخدام | if (isType(x)) |
assertIsType(x) |
| تضييق النطاق | التضييق في فرع if | التضييق التلقائي بعد الاستدعاء |
| السيناريوهات المناسبة | معالجة الفروع بعد الفحص | فحوصات الشروط المسبقة |
6. تضييق نطاق المهمة
تقوم عمليات التعيين أيضًا بتضييق النوع:
let value: string | number;
value = "hello";
console.log(value.toUpperCase()); // ✅ After assignment, it narrows to string
value = 42;
console.log(value.toFixed(2)); // ✅ After assignment, it narrows to number
(1) تحليل تدفق التحكم
تتتبع TypeScript التغييرات التي تطرأ على نوع المتغير طوال مسار التدفق:
function example(x: string | number | boolean) {
// x: string | number | boolean
if (typeof x === "string") {
// x: string
console.log(x.toUpperCase());
} else {
// x: number | boolean
if (typeof x === "number") {
// x: number
console.log(x.toFixed(2));
} else {
// x: boolean
console.log(x);
}
}
}
(2) التضييق وإعادة التخصيص
let value: string | number;
value = "hello";
console.log(value.length); // ✅ string
value = 42;
// console.log(value.length); // ❌ number None length
value = true; // ❌ boolean Not in a composite type
7. الفحص الشامل
تأكد من أن عبارة switch/if تشمل جميع الأنواع المحتملة — استخدم النوع never لضمان الاكتمال:
type Shape = "circle" | "square" | "triangle";
function getIcon(shape: Shape): string {
switch (shape) {
case "circle": return "○";
case "square": return "□";
case "triangle": return "△";
default: {
// If all case It's all taken care of.,shape Here is never
const _exhaustive: never = shape;
return _exhaustive;
}
}
}
// If in the future Shape Added "hexagon" But I didn't add it case
// default Branched _exhaustive Report Type Error
// This is a reminder to fill in any missing information. case
(1) فحص شامل أكثر إيجازًا
function assertNever(value: never): never {
throw new Error(`Unprocessed values:${value}`);
}
type Action = "create" | "update" | "delete";
function handleAction(action: Action) {
switch (action) {
case "create": /* ... */ break;
case "update": /* ... */ break;
case "delete": /* ... */ break;
default:
assertNever(action); // If omitted case,A type error will be reported here.
}
}
▶ مثال: استخدام typeof وinstanceof لتضييق النطاق
function format(value: string | number | Date): string {
if (typeof value === "string") {
return value.trim().toUpperCase();
} else if (typeof value === "number") {
return value.toFixed(2);
} else if (value instanceof Date) {
return value.toISOString();
}
return String(value);
}
console.log(format(" hello ")); // "HELLO"
console.log(format(3.14159)); // "3.14"
console.log(format(new Date("2024-01-01"))); // "2024-01-01T00:00:00.000Z"
الناتج:
HELLO
3.14
2024-01-01T00:00:00.000Z
▶ مثال: حارس نوع مخصص مع الاتحادات المميزة
interface Circle { kind: "circle"; radius: number; }
interface Rectangle { kind: "rectangle"; width: number; height: number; }
type Shape = Circle | Rectangle;
function isCircle(shape: Shape): shape is Circle {
return shape.kind === "circle";
}
function area(shape: Shape): number {
if (isCircle(shape)) {
return Math.PI * shape.radius ** 2; // Circle
}
return shape.width * shape.height; // Rectangle
}
console.log(area({ kind: "circle", radius: 5 })); // 78.54
console.log(area({ kind: "rectangle", width: 4, height: 6 })); // 24
الناتج:
78.53981633974483
24
❓ أسئلة شائعة
typeof وinstanceof؟typeof بالتحقق من «النوع البدائي» للقيمة (سلسلة/عدد/قيمة منطقية/غير محدد/كائن/دالة) وتُرجع سلسلة؛ وهي مناسبة للتحقق من الأنواع البدائية. أما instanceof فتتحقق مما إذا كانت القيمة مثيلًا لفئة معينة؛ وهي مناسبة للتحقق من الأنواع المرجعية (مثل Date وError والفئات المخصصة). وهما متكاملتان ولا تستبعد إحداهما الأخرى.if عادية وعمليات فحص للخصائص. ولا توجد المسندات النوعية (x is T) ودوال التأكيد (asserts x is T) في مرحلة التشغيل، لذا لا توجد أي أعباء إضافية.in بتضييق النوع؟"swim" in animal صحيحة، يجب أن يُنفذ animal واجهة تتضمن swim. وهذا استنتاج منطقي لا يتطلب معلومات عن النوع في وقت التشغيل.📖 ملخص
- تضييق النطاق — وهي عملية تضييق نطاق نوع واسع ليقتصر على نطاق محدد — هو أمر أساسي للاستخدام الآمن لأنواع الاتحاد.
typeofيتحقق من وجود نوع أساسي، وinstanceofيتحقق من وجود مثيل لفئة، وinيتحقق من وجود خاصية- تعالج حراس الأنواع المخصصة (مسلّمات الأنواع
x is T) الأنواع المعقدة التي لا يمكن تمييزها باستخدام الطرق المدمجة - تقوم دالة التأكيد (
asserts x is T) بإجراء فحص مسبق وترمي استثناءً إذا لم يتم استيفاء الشرط. - يقوم تحليل تدفق التحكم في TypeScript بتتبع التغييرات في أنواع المتغيرات داخل الفروع تلقائيًا
- استخدم النوع
neverلإجراء فحوصات شاملة للتأكد من أن عباراتswitchوifتغطي جميع الاحتمالات
📝 تمارين
- المسألة الأساسية (الصعوبة ⭐): اكتب دالة
doubleOrRepeat(value: string | number)— إذا كانvalueعددًا، فاضربه في 2؛ وإذا كان سلسلة أحرف، فقم بضمها إلى نفسها مرة واحدة (على سبيل المثال، "hi" → "hihi"). استخدمtypeofلتقييد المدخلات. - مشكلة متقدمة (درجة الصعوبة ⭐⭐): عرّف واجهتين،
Admin(التي تحتوي على الطريقةhasPermission) وGuest(التي تحتوي على الطريقةrequestAccess)، وقم بتحديدهما باستخدام السمة المميزةrole. اكتب دالة تستدعي طرقًا مختلفة بناءً على الدور. - التحدي (الصعوبة: ⭐⭐⭐): اكتب حارس نوع مخصص
isNonNull<T>(value: T | null | undefined): value is NonNullable<T>، ثم استخدمه في الدالةfilterNonNull<T>(arr: (T | null | undefined)[]): T[]لتصفية القيم الفارغة (فارغ) وغير المُعرَّفة (غير معرّف) وإرجاع مصفوفة غير فارغة.