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.

Súvisiace články