Typescript - ako používať súbor utilít v celom projekte s minimálnou ceremóniou?
Pracujem s Typescriptom už niekoľko mesiacov a tento aspekt ma už počas niekoľkých projektov otravuje. Povedzme, že mám nasledujúcu štruktúru projektu:
project
+- app
| +- component1
| | +- component1.ts
| +- component2
| | +- component2.ts
| +- utils
| +- utils.ts
+- tsconfig.json
Predpokladajme teraz, že súbor utils.ts obsahuje veľa pomocných tried (napr. dátové štruktúry) a možno aj globálne funkcie, a takmer každý iný súbor v projekte používa aspoň časť z neho. Ako by ste ho použili s minimálnym množstvom obslužného kódu?
Použitie príkazu import
// utils.ts
export function foo() { return 1; }
// component1.ts
import { foo, bar, baz, ...(a long list here) } from '../utils/utils'
let x = foo(); // looks fine
Výhody: importovaní členovia sú jednoduchí na používanie
Nevýhody: nutnosť udržiavať dlhý zoznam importov takmer v každom inom súbore
Ďalšou možnosťou je importovať všetko naraz:
// component1.ts
import * as utils from '../utils/utils' // I have to remember to put this line on top of every other file, but it's better than the option above
let x = utils.foo(); // unavoidable prefixes all over the code
Výhody: direktíva importu je všeobecná a môže sa kopírovať do každého súboru len so zmenou relatívnej cesty (hoci by som bol šťastnejší, keby existoval spôsob, ako to urobiť implicitne v celom projekte)
Nevýhody: predpony názvového priestoru pre jednoduché pomocné funkcie po celom kóde. Z nejakého dôvodu Typescript nedovoľuje používať import * bez explicitného názvového priestoru.
Použitie direktívy s trojitým lomítkom
// utils.ts
function foo() { return 1; } // no export keyword
// component1.ts
/// <reference path="../utils/utils.ts" />
let x = foo();
Toto vyzerá oveľa lepšie, aj keď stále musím uviesť odkaz s relatívnou cestou na začiatku každého súboru. Avšak, úplne sa to pre mňa pokazí, keď sa pokúsim balíčkovať svoju aplikáciu pomocou webpacku - utils.js sa jednoducho nezabalí spolu so zvyškom aplikácie (myslím, že toto je téma pre ďalšiu otázku na SO).
Čo by som naozaj chcel, je možnosť používať môj kód v utils.ts tak, ako používam globálne triedy knižníc - Map, Set, HTMLElement, Promise atď., bez akýchkoľvek importov alebo predpôn, existuje nejaký spôsob, ako to dosiahnuť?
P.S. Všimnite si, že som už dávno opustil dobrý programátorský princíp skutočného rozdeľovania môjho súboru utils.ts do viacerých súborov (jeden na triedu/globálnu funkciu), pretože by to výrazne zhoršilo vyššie uvedený problém. V skutočnosti už uvažujem o použití jediného súboru pre celý projekt.
Odpoveď č. 1
Ako pridať nástroje do globálneho rozsahu
Keď robíte /// <reference path="../utils/utils.ts" />, hovoríte kompilátoru Typescript: „všetko v module utils.ts je v globálnom rozsahu“. Typescript nič neurobí, aby sa to skutočne stalo, len hovoríte TS, že je to hotové, a on vám verí. (Napríklad, ak by ste načítali knižnicu pomocou <script src=".."> tagu vo vašom HTML, aby sa knižnica načítala globálne, použili by ste túto anotáciu, aby ste to oznámili Typescriptu.)
Ak chcete ísť touto cestou, budete potrebovať anotáciu, aby ste TypeScriptu povedali, že utilitky sú v globálnom priestore, ale budete tiež musieť niekde skutočne pridať logiku, aby sa to stalo. Webpack má niekoľko spôsobov, ako definovať globálne premenné, ale jednou z možností by bolo použiť global s kódom niekde na začiatku vašej aplikácie:
import * as utils from "../path/to/utils";
for(const key in utils) {
global[key] = utils[key];
}
Teraz sú všetky vaše utilitky v globálnom rozsahu, v skutočnosti, a anotácia cesty k referencii informuje Typescript o tom, takže Typescript je spokojný.
Prečo by som nepridával utils do globálneho priestoru
Ale, úprimne, nemôžem povedať, že by som odporúčal robiť to v praxi. Znečisťovanie globálneho priestoru je dosť klasický anti-pattern a vo všeobecnosti robí programy ťažko sledovateľnými. Každý, kto číta váš kód, bude vidieť iba „magické“ funkcie ako foo, ktoré sa zdajú objavovať z ničoho, bez akéhokoľvek naznačenia, odkiaľ táto funkcia pochádza. A rovnako ako všetky doplnky do globálneho priestoru, otvára to dvere kolíziám názvov.
Osobne odporúčam robiť štandardné importy: osobne inklinujem k import { foo } from "utils/utils", hoci niekedy je utils.foo ľahšie pochopiť (napr. ak dôjde k kolízii názvov s nejakým iným foo v module, do ktorého importujem).
Niekoľko odporúčaní, ako spraviť importy menej bolestivými:
Nepoužívajte relatívne cesty importu.
S troškou konfigurácie pre TS a webpack môžete písať svoje importy takto
import * as utils from 'utils/utils'
Namiesto:
import * as utils from '../../utils/utils'
Relevantné časti konfigurácie sú "include" vo vašom tsconfig a "resolve.modules" vo vašej webpack konfigurácii. (Alebo resolve.root, ak používate webpack 1.0) Vo vašom prípade by ste chceli pridať "app" do oboch týchto zoznamov, aby ste naznačili, že "app" je koreň vašich zdrojových súborov.
Ukážky kódu pre často používané importy
S konzistentnými importnými cestami, ak máte veľa často používaných importov, môžete si vytvoriť kódový útržok pre ne: kúsok kódu, ktorý môže byť rýchlo vložený zadaním konkrétnej skratkovej frázy. (V VSCode, Atom a SublimeText sa nazývajú útržky, predpokladám, že aj iné editory majú podobnú funkcionalitu)
Pre vašu hypotetickú matematicky náročnú aplikáciu (z komentárov k otázke) by ste mohli vytvoriť úryvok pre
import Vector from "path/to/Vector";
import Matrix from "path/to/Matrix";
import Tensor from "path/to/Tensor";
rýchlo naimportovať všetky tieto veci pri vytváraní nového súboru.
VSCode teraz podporuje automatické importy
Môžete zadať foo a stlačiť Enter, a automaticky sa pridá import pre foo na začiatok vášho súboru. Nechcem propagovať konkrétny editor, ale ak vás otravuje zdĺhavosť importov, toto je naozaj celkom užitočné.
Kratšie menné priestory
Ak používate import mena priestoru, možno si vyberte kratší: u.foo je len o niečo dlhšie ako samotné foo, ale je oveľa menej magické než manipulovať s globálnym rozsahom.
