٢٥ مارس ٢٠٢١

التصدير والاستيراد

لمُوجِّهات (تعليمات) الاستيراد والتصدير أكثر من صياغة برمجية واحدة.

رأينا في الفصل السابق استعمالًا بسيطًا له، فهيًا نرى بقية الاستعمالات.

التصدير قبل التصريح

يمكننا أن نقول لأيّ تصريح بأنّه مُصدّر بوضع عبارة export قبله، كان التصريح عن متغيّر أو عن دالة أو عن صنف.

فمثلًا، التصديرات هنا كلّها صحيحة:

// تصدير مصفوفة
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// تصدير ثابت
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// تصدير صنف
export class User {
  constructor(name) {
    this.name = name;
  }
}

ملاحظة: لا يوجد فواصل منقوطة بعد تعليمة التصدير للأصناف أو الدوالّ لاحظ أن تعليمة export قبل الصنف أو الدالة لا يجعلها [تعابير الدوالّ](https://academy.hsoub.com/programming/javascript/تعابير-الدوال-والدوال-السهمية-في-جافا سكريبت-r782/). ولو أنه يصُدرها، لكنه لا يزال تعريفًا للدالّة أو الصنف.

لا توصي معظم الأدلة التعليمية بوضع فاصلة منقوطة بعد تعريف الدوال والأصناف.

لهذا السبب لا داعي للفاصلة المنقوطة في نهاية التعليمة export class والتعليمة export function:

export function sayHi(user) {
  alert(`Hello, ${user}!`);
} // لاحظ لا يوجد فاصلة منقوطة في نهاية التعريف

التصدير بعيدًا عن التصريح

كما يمكننا وضع عبارة export لوحدها.

هنا نصرّح أولًا عن الدالتين وبعدها نُصدّرهما:

// 📁 say.js
function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // a list of exported variables

أو… يمكننا تقنيًا وضع export أعلى الدوال أيضًا.

عبارة استيراد كلّ شيء

عادةً نضع قائمة بما نريد استيراده في أقواس معقوفة import {...}‎، هكذا:

// 📁 main.js
import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

ولكن لو أردنا استيراد وحدات كثيرة، فيمكننا استيراد كلّ شيء كائنًا واحدًا باستعمال import * as <obj>‎ هكذا:

// 📁 main.js
import * as say from './say.js';

say.sayHi('John');
say.sayBye('John');

يقول المرء من النظرة الأولى ”استيراد كلّ شيء فكرة جميلة جدًا، وكتابة الشيفرة سيكون أسرع. أساسًا لمَ نقول جهارةً ما نريد استيراده؟“

ذلك… لأسباب وجيهة.

  1. أدوات البناء الحديثة (مثل: webpack وغيرها)

    لنقل مثلًا بأنّا أضفنا مكتبة خارجية اسمها say.js إلى مشروعنا، وفيها دوالّ عديدة:

    // 📁 say.js
    export function sayHi() { ... }
    export function sayBye() { ... }
    export function becomeSilent() { ... }

    هكذا نستعمل واحدة فقط من دوالّ say.js في مشروعنا:

    // 📁 main.js
    import {sayHi} from './say.js';

    …حينها تأتي أداة التحسين وترى ذلك، فتُزيل الدوال الأخرى من الشيفرة … بذلك يصغُر حجم الملف المبني. هذا ما نسميه هز الشجر (لتَسقطَ الأوراق اليابسة).

  2. لو وضّحنا بالضبط ما نريد استيراده فيمكننا كتابته باسم أقصر: sayHi()‎ بدل say.sayHi()‎.

  3. بكتابة قائمة الاستيراد جهارةً نستطيع أن نفهم بنية الشيفرة دون الخوض في التفاصيل (أي نعرف ما نستعمل من وحدات، وأين نستعملها). هذا يسهّل دعم الشيفرة وإعادة كتابتها لو تطلّب الأمر.

استيراد كذا بالاسم كذا as

يمكننا كذلك استعمال as لاستيراد ما نريد بأسماء مختلفة.

فمثلًا يمكننا استيراد الدالة sayHi في المتغير المحلي hi لنختصر الكلام، واستيراد sayBye على أنّها bye:

// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!

تصدير كذا بالاسم كذا as

نفس صياغة الاستيراد موجودة أيضًا للتصدير export.

فلنصدّر الدوال على أنّها hi وbye:

// 📁 say.js
...
export {sayHi as hi, sayBye as bye};

الآن صارت hi وbye هي الأسماء ”الرسمية“ للشيفرات الخارجية وستُستعمل عند الاستيراد:

// 📁 main.js
import * as say from './say.js';

// لاحِظ الفرق
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!

التصدير المبدئي

في الواقع العملي، ثمّة نوعين رئيسين من الوحدات.

  1. تلك التي تحتوي مكتبة (أي مجموعة من الدوال) مثل وحدة say.js أعلاه.
  2. وتلك التي تصرّح عن كيانٍ واحد مثل وحدة user.js التي تُصدّر class User فقط.

عادةً ما يُحبّذ استعمال الطريقة الثانية كي يكون لكلّ ”شيء“ وحدةً خاصة به.

ولكن هذا بطبيعة الحال يطلب ملفات كثيرة إذ يطلب كلّ شيء وحدةً تخصّه باسمه، ولكنّ هذه ليست بمشكلة، أبدًا. بل على العكس هكذا يصير التنقل في الشيفرة أسهل (لو كانت تسمية الملفات مرضية ومرتّبة في مجلدات).

توفر الوِحدات طريقة لصياغة عبارة export default (التصدير المبدئي) لجعل “سطر تصدير واحد لكلّ وِحدة” تبدو أفضل.

ضَع export default قبل أيّ كيان لتصديره:

// 📁 user.js
export default class User { // ‫نُضيف ”default“ فقط
  constructor(name) {
    this.name = name;
  }
}

لكلّ ملف سطر تصدير export default واحد لا أكثر.

وبعدها… نستورد الكيان بدون الأقواس المعقوفة:

// 📁 main.js
import User from './user.js'; // ‫لا نضع {User}، بل User

new User('John');

أسطر الاستيراد التي لا تحتوي الأقواس المعقوفة أجمل من تلك التي تحتويها. يشيع خطأ نسيان تلك الأقواس حين يبدأ المطورون باستعمال الوِحدات. لذا تذكّر دائمًا، يطلب سطر الاستيراد import أقواس معقوفة للكيانات المُصدّرة والتي لها أسماء، ولا يطلبها لتلك المبدئية.

التصدير الذي له اسم التصدير المبدئي
export class User {...} export default class User {...}‎
import {User} from ... import User from ...

يمكننا نظريًا وضع النوعين من التصدير معًا في نفس الوحدة (الذي له اسم والمبدئي)، ولكن عمليًا لا يخلط الناس عادةً بينها، بل للوِحدة إمّا تصديرات لها أسماء، أو التصدير المبدئي.

ولأنّه لا يمكن أن يكون لكلّ ملف إلا تصديرًا مبدئيًا واحدًا، فيمكن للكيان الذي صُدّر ألّا يحمل أيّ اسم.

فمثلًا التصديرات أسفله كلّها صحيحة مئة في المئة:

export default class { // لا اسم للصنف
  constructor() { ... }
}
export default function(user) { // لا اسم للدالة
  alert(`Hello, ${user}!`);
}
// نُصدّر قيمةً واحدة دون صنع متغيّر
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

لا مشكلة بتاتًا بعدم كتابة الاسم إذ لا نرى export default إلّا مرّة في الملف، بهذا تعرف تمامًا أسطر import (بدون استعمال الأقواس المعقوفة) ما عليها استيراده.