Jak opravit ošklivé kruhové závislosti problémů jednou a pro všechny v JavaScript A TypeScript
-
index.js
vyžadujeAbstractNode.js
- modul zavaděč začne načítání
AbstractNode.js
a spuštění modulu kódu. Věc, na kterou se poprvé setká, je příkaz require (import) doLeaf
- , takže zavaděč modulů začne načítat soubor
Leaf.js
. Což zase začíná vyžadovánímAbstractnode.js
. -
AbstractNode.js
je již načten a je okamžitě vrácen z mezipaměti modulu. Protože však tento modul ještě neběhl za první řádek (požadavekLeaf
), příkazy zavádějící tříduAbstractNode
ještě nebyly provedeny! - třída
Leaf
se tedy snaží rozšířit z hodnotyundefined
, spíše než z platné třídy. Který hodí výjimku runtime uvedenou výše. Bum!
opravit pokus 1
takže se ukazuje, že naše kruhová závislost způsobuje nepříjemný problém. Pokud se však podíváme pozorně, je docela snadné určit, jaká by měla být objednávka nakládky:
- načtěte třídu
AbstractNode
nejprve - načtěte třídu
Node
aLeaf
poté.
jinými slovy, definujme nejprve třídu AbstractNode
a pak ji nechte vyžadovat Leaf
a Node
. To by mělo fungovat, protože Leaf
a Node
ještě nemusí být známy při definování třídy AbstractNode
. Pokud jsou definovány před AbstractNode.from
, měli bychom být v pořádku. Takže pojďme zkusit následující změny:
Ukázalo se, že existuje několik problémů s tímto řešením:
Za prvé, je to ošklivé a nemá měřítko. Ve Velké kódové základně to povede k náhodnému přesunu importů, dokud věci prostě nebudou fungovat. Což je často jen dočasné, protože malé refaktorování nebo změna v importních prohlášeních v budoucnu může jemně upravit pořadí načítání modulů a problém znovu zavést.
za druhé, zda to funguje, je velmi závislé na modulu bundler. Například v codesandboxu při sdružování naší aplikace s balíkem (nebo Webpack nebo kumulativní) toto řešení nefunguje. Nicméně, při spuštění to lokálně s uzlem.moduly js a commonJS toto řešení může fungovat dobře.
vyhnout se problému
takže zřejmě tento problém nelze snadno vyřešit. Dalo se tomu zabránit? Odpověď zní ano, existuje několik způsobů, jak se tomuto problému vyhnout. Za prvé, mohli jsme mít kód v jednom souboru. Jak je ukázáno v našem počátečním příkladu, tímto způsobem můžeme problém vyřešit, protože poskytuje plnou kontrolu nad pořadím, ve kterém běží Inicializační kód modulu.
za druhé, někteří lidé použijí výše uvedený problém jako argument, aby učinili prohlášení jako “člověk by neměl používat třídy “nebo” nepoužívat dědičnost”. To je ale přehnané zjednodušení problému. I když souhlasím s tím, že programátoři se často uchylují k dědictví příliš rychle, pro některé problémy, to je prostě perfektní, a může přinést velké výhody, pokud jde o strukturu kódu opětovné použití nebo výkon. Ale co je nejdůležitější, tento problém není omezen na dědičnost třídy. Přesně stejný problém může být zaveden, když mají kruhové závislosti mezi proměnnými modulu a funkcemi, které běží během inicializace modulu!
mohli bychom znovu uspořádat náš kód tak, že rozdělíme třídu AbstractNode
na menší kousky, takže AbstractNode
nemá žádné závislosti na Node
nebo Leaf
. V této karanténě byla metoda from
vytažena z třídy AbstractNode
a vložena do samostatného souboru. To problém vyřeší, ale nyní je náš projekt a API strukturovány odlišně. Ve velkých projektech může být velmi těžké určit, jak tento trik vytáhnout, nebo dokonce nemožné! Představte si například, co by se stalo, kdyby print
metoda závisí na Node
nebo Leaf
v další iteraci naší aplikaci…
Bonus: další ošklivý trik, který jsem použil dříve: návrat základní třídy z funkce a pákový funkce zvedání, aby se věci naložené ve správném pořadí. Ani si nejsem jistý, jak to správně vysvětlit.
vnitřní modul vzor na záchranu!
s tímto problémem jsem bojoval několikrát v mnoha projektech několik příkladů zahrnuje mou práci v Mendix, MobX, MobX-state-tree a několik osobních projektů. V určitém okamžiku jsem před několika lety dokonce napsal skript, který zřetězí všechny zdrojové soubory a vymaže všechny příkazy importu. Chudák-Mans modul bundler jen proto, aby si přilnavost na modulu loading pořadí.
po vyřešení tohoto problému se však několikrát objevil vzor. Ten, který dává plnou kontrolu nad objednávkou načítání modulů, aniž byste museli restrukturalizovat projekt nebo tahat podivné hacky! Tento vzor funguje perfektně se všemi řetězci nástrojů, které jsem vyzkoušel (kumulativní, Webpack,Parcel, Node).
jádrem tohoto vzoru je zavedení souboru index.js
a internal.js
. Pravidla hry jsou následující:
-
internal.js
modul dovozu a vývozu vše, co od každého místního modulu v projektu - Každý další modul v projektu pouze dovoz z
internal.js
soubor, a nikdy přímo z jiných souborů v projektu. - soubor
index.js
je hlavním vstupním bodem a importuje a exportuje vše odinternal.js
, které chcete vystavit vnějšímu světu. Tento krok je relevantní pouze v případě, že publikujete knihovnu, kterou spotřebovávají ostatní. Takže jsme tento krok v našem příkladu přeskočili.
Všimněte si, že výše uvedená pravidla platí pouze pro naše lokální závislosti. Import externích modulů je ponechán tak, jak je. Nejsou přece zapojeni do našich problémů s cirkulární závislostí. Pokud použijeme tuto strategii na naši demo aplikaci, náš kód bude vypadat takto:
Když použijete tento vzor poprvé, to může cítit velmi nepřirozený. Ale má několik velmi důležitých výhod!
- Nejprve jsme vyřešili náš problém! Jak je ukázáno zde naše aplikace je šťastně běží znovu.
- důvod, proč to řeší náš problém, je: nyní máme plnou kontrolu nad objednávkou načítání modulů. Bez ohledu na pořadí importu v
internal.js
bude naše objednávka načítání modulů. (Možná budete chtít, podívejte se na obrázek níže, nebo re-číst modulu cílem vysvětlení výše vidět, proč je to tento případ) - nemusíme použít refaktorování nechceme. Ani jsme nuceni používat ošklivé triky, jako je přesunutí příkazů do dolní části souboru. Nemusíme kompromitovat architekturu, API nebo sémantickou strukturu naší kódové základny.
- Bonus: příkazy importu budou mnohem menší, protože budeme importovat věci z méně souborů. Například
AbstractNode.js
má nyní pouze příkaz import, kde měl předtím dva. - Bonus: s
index.js
máme jediný zdroj pravdy, který dává jemnozrnnou kontrolu nad tím, co vystavujeme vnějšímu světu.