Шаблон зовнішнього модуля TypeScript містить велику потужність і зручність використання. Тут ми обговорюємо його потужність і деякі моделі, необхідні для відображення використання в реальному світі.
Спочатку нам потрібно прояснити (жахливу) невідповідність систем модулів. Я просто дам вам свою поточну рекомендацію та видалю шум, тобто не покажу всі інші способи роботи.
З того самого TypeScript ви можете генерувати різний JavaScript залежно від параметра module
. Ось речі, які ви можете ігнорувати (мені не цікаво пояснювати мертві технології):
- AMD: не використовувати. Був лише браузер.
- SystemJS: це був хороший експеримент. Замінено модулями ES.
- Модулі ES: ще не готові.
Тепер це лише варіанти для генерування JavaScript. Замість цих параметрів використовуйте module:commonjs
Те, як ви пишете модулі TypeScript, також є дещо безладним. Ось як цього не робити сьогодні:
import foo = require('foo')
. тобтоimport/require
. Натомість використовуйте синтаксис модуля ES.
Круто, покінчивши з цим, давайте розглянемо синтаксис модуля ES.
Резюме: Використовуйте
module:commonjs
і використовуйте синтаксис модуля ES для імпорту/експорту/авторських модулів.
- Експортувати змінну (або тип) так само просто, як додати префікс ключового слова
export
.
// file `foo.ts`
export let someVar = 123;
export type SomeType = {
foo: string;
};
- Експортування змінної або типу в спеціальному операторі
export
.
// file `foo.ts`
let someVar = 123;
type SomeType = {
foo: string;
};
export {
someVar,
SomeType
};
- Експортування змінної або типу в спеціальному операторі
export
з перейменуванням.
// file `foo.ts`
let someVar = 123;
export { someVar as aDifferentName };
- Імпорт змінну або тип за допомогою
import
.
// file `bar.ts`
import { someVar, SomeType } from './foo';
- Імпортуйте змінну або тип за допомогою
import
з перейменуванням.
// file `bar.ts`
import { someVar as aDifferentName } from './foo';
- Імпортуйте все з модуля в назву за допомогою
import * as
, наприклад.
// file `bar.ts`
import * as foo from './foo';
// you can use `foo.someVar` and `foo.SomeType` and anything else that foo might export.
- Імпортуйте файл тільки для його побічного ефекту за допомогою одного оператора імпорту:
import 'core-js'; // a common polyfill library
- Повторний експорт усіх елементів з іншого модуля
export * from './foo';
- Повторний експорт лише деяких елементів з іншого модуля
export { someVar } from './foo';
- Повторний експорт лише деяких елементів з іншого модуля * з перейменуванням *
export { someVar as aDifferentName } from './foo';
Як ви дізнаєтесь пізніше, я не прихильник експорту за замовчуванням. Тим не менш, тут є синтаксис для експорту та використання стандартних експортів
- Експортуйте за допомогою
export default
- перед змінною (не потрібно
let / const / var
) - перед функцією
- перед уроком
- перед змінною (не потрібно
// some var
export default someVar = 123;
// OR Some function
export default function someFunction() { }
// OR Some class
export default class SomeClass { }
- Імпортуйте за допомогою синтаксису
import someName from "someModule"
(ви можете назвати імпорт як завгодно).
import someLocalNameForThisFile from "../foo";
Я просто припускаю
moduleResolution: "Node"
. Це параметр, який ви повинні мати у своїй конфігурації TypeScript. Цей параметр автоматично передбачаєтьсяmodule:commonjs
.
Є два різних типи модулів. Розрізнення визначається розділом шляху оператора імпорту (наприклад, import foo from 'THIS IS THE PATH SECTION'
).
- Модулі відносного шляху (де шлях починається з
.
, наприклад./someFile
або../../someFolder/someFile
тощо) - Інші модулі динамічного пошуку (наприклад,
'core-js'
або'typestyle'
або'react'
або навіть'react/core'
тощо)
Основна відмінність полягає в тому, як модуль вирішується у файловій системі.
Я буду використовувати концептуальний термін місце, який я поясню після згадки шаблону пошуку.
Легко, просто дотримуйтеся відносного шляху :)
якщо файл bar.ts
виконує імпорт * як foo з './foo';
, тоді місце foo
має існувати в тій же папці.
- якщо файл
bar.ts
виконуєімпорт * як foo з '../foo';
, тоді місцеfoo
має існувати в папці вище. - якщо файл
bar.ts
виконуєімпорт * як foo з '../someFolder/foo';
, тоді на одну папку вище, має бути папкаsomeFolder
з місцемfoo
Або будь-який інший відносний шлях, який ви можете придумати :)
Якщо шлях імпорту не відносний, пошук керується роздільністю стилю вузла. Тут я наведу лише простий приклад:
-
У вас є
імпорт * як foo з 'foo'
, нижче наведено місця, які перевіряються по порядку./node_modules/foo
../node_modules/foo
../../node_modules/foo
- До кореня файлової системи
-
У вас є
імпорт * як foo з 'something/foo'
, нижче наведено місця, які перевіряються по порядку./node_modules/something/foo
../node_modules/something/foo
../../node_modules/something/foo
- До кореня файлової системи
Коли я кажу місця, які перевіряються, я маю на увазі, що в цьому місці перевіряються такі речі. Наприклад. для місця foo
:
- Якщо місцем є файл, напр.
foo.ts
, ура! - інакше, якщо місцем є папка і є файл
foo/index.ts
, ура! - інакше, якщо місцем є папка і існує
foo/package.json
і файл, указаний у ключіtypes
у package.json, тоді ура! - інакше, якщо місцем є папка, і існує
package.json
і файл, указаний у ключіmain
у package.json, який існує, тоді ура!
Під файлом я насправді маю на увазі .ts
/ .d.ts
і .js
.
І це все. Тепер ви експерт із пошуку модулів (це не маленький подвиг!).
Ви можете оголосити модуль глобально для свого проекту за допомогою declare module 'somePath'
, а потім імпорт буде чарівним чином вирішувати цей шлях
e.g.
// global.d.ts
declare module 'foo' {
// Some variable declarations
export var bar: number; /*sample*/
}
and then:
// anyOtherTsFileInYourProject.ts
import * as foo from 'foo';
// TypeScript assumes (without doing any lookup) that
// foo is {bar:number}
Наступна заява:
import foo = require('foo');
насправді робить дві речі:
- Імпортує інформацію про тип модуля foo.
- Визначає залежність часу виконання від модуля foo.
Ви можете вибирати так, щоб завантажувалася лише інформація про тип і не виникала залежність під час виконання. Перш ніж продовжити, можливо, ви захочете підсумувати розділ просторів для оголошень у книзі.
Якщо ви не використовуєте імпортоване ім’я в просторі оголошення змінних, імпорт буде повністю видалено зі згенерованого JavaScript. Це найкраще пояснити на прикладах. Як тільки ви це зрозумієте, ми представимо вам випадки використання.
import foo = require('foo');
створить JavaScript:
Це вірно. Порожній файл як foo не використовується.
import foo = require('foo');
var bar: foo;
створить JavaScript:
var bar;
Це тому, що foo
(або будь-яка з його властивостей, наприклад foo.bas
) ніколи не використовується як змінна.
import foo = require('foo');
var bar = foo;
створить JavaScript (припускаючи, що commonjs):
var foo = require('foo');
var bar = foo;
Це тому, що foo
використовується як змінна.
Висновок типу потрібно зробити заздалегідь. Це означає, що якщо ви хочете використовувати певний тип із файлу foo
у файлі bar
, вам доведеться зробити:
import foo = require('foo');
var bar: foo.SomeType;
Однак ви можете завантажувати файл foo
лише під час виконання за певних умов. У таких випадках вам слід використовувати імпортовану назву лише в анотаціях типу, а не як змінну. Це видаляє будь-який попередній код залежності часу виконання, який впроваджується TypeScript. Потім вручну імпортуйте фактичний модуль, використовуючи код, який відповідає вашому завантажувачу модулів.
Як приклад, розглянемо наступний код на основі commonjs
, де ми завантажуємо модуль `'foo`` лише під час певного виклику функції:
import foo = require('foo');
export function loadFoo() {
// This is lazy loading `foo` and using the original module *only* as a type annotation
var _foo: typeof foo = require('foo');
// Now use `_foo` as a variable instead of `foo`.
}
Схожий приклад на amd
(using requirejs) буде:
import foo = require('foo');
export function loadFoo() {
// This is lazy loading `foo` and using the original module *only* as a type annotation
require(['foo'], (_foo: typeof foo) => {
// Now use `_foo` as a variable instead of `foo`.
});
}
Цей шаблон зазвичай використовується:
- у веб-додатках, де ви завантажуєте певний JavaScript за певними маршрутами,
- у вузлових програмах, де ви завантажуєте лише певні модулі, якщо це необхідно для прискорення завантаження програми.
Подібно до випадку використання відкладеного завантаження, деякі завантажувачі модулів (commonjs/node і amd/requirejs) погано працюють із циклічними залежностями. У таких випадках корисно мати ліниве завантаження коду в одному напрямку та завантаження модулів наперед в іншому напрямку.
Іноді ви хочете завантажити файл лише для побічного ефекту (наприклад, модуль може зареєструватися в якійсь бібліотеці, наприклад CodeMirror addons тощо). Однак, якщо ви просто виконуєте команду import/require
, транспільований JavaScript не міститиме залежності від модуля, і ваш завантажувач модуля (наприклад, веб-пакет) може повністю ігнорувати імпорт. У таких випадках ви можете використовувати змінну ensureImport
, щоб переконатися, що скомпільований JavaScript приймає залежність від модуля, наприклад:
import foo = require('./foo');
import bar = require('./bar');
import bas = require('./bas');
const ensureImport: any =
foo
&& bar
&& bas;