Circular Dependencies Problem in JavaScript
π― What You'll Know After Reading This Document
- You'll understand what circular dependencies are.
- You'll know how to solve circular dependency problems.
- You'll know how to prevent circular dependency problems.
π The Problem Situation
Circular Dependency Problem Encountered While Writing Storybook Stories
As you can see in the article above, I encountered a circular dependency problem while writing stories in Storybook.
I had heard the keyword "circular dependency problem" before, but I realized I didn't actually understand it properly.
So I decided to take this opportunity to learn what circular dependencies are and how to solve them.
π€ What is a Circular Dependency Problem?
Circular Dependencies refers to a situation where two or more modules reference each other directly or indirectly.
As in the example above, when a situation occurs where ModuleA and ModuleB reference each other, this is called a circular dependency problem.
When such a situation occurs, modules can reference each other infinitely, causing the program to fall into an infinite loop, which is why it's classified as a problem.
As you can see, it's actually a common problem in software development, and the probability of occurrence increases as module dependencies become more complex.
Let's look at it more closely through code.
π Direct Circular Reference Example
In the code above, ModuleA and ModuleB are referencing each other.
This way, when functionA is called, functionB is called, and when functionB is called, functionA is called, resulting in an infinite loop.
When such a situation occurs, the program falls into an infinite loop and doesn't function properly.
π Indirect Circular Reference Example
Circular references can occur not only when two or more modules directly reference each other, but also when they reference each other indirectly.
In the code above, ModuleA references ModuleC, ModuleC references ModuleB, and ModuleB references ModuleA.
Here, a circular reference occurs in the order of moduleA β moduleC β moduleB β moduleA.
π€ Why Are Circular References a Problem?
Let's look at more specific reasons beyond just falling into an infinite loop.
| Problem | Description |
|---|---|
| Module Initialization Problem | The JavaScript module system (especially ES modules) loads all dependencies first before executing when loading a module.When there's a circular reference, modules reference other modules before they are fully initialized, resulting in referencing values in an uninitialized state. This can cause undefined or unexpected values to be returned. |
| Memory Leak | Circular references can interfere with garbage collection.Especially circular references between objects can cause memory leaks. Garbage collection runs when the reference count of a variable becomes 0, but with circular references, it can never become 0. |
| Difficulty in Code Maintenance | Circular references make code difficult to understand and maintain. Especially when circular references occur, dependencies between modules become complex, making the code difficult to understand. This hinders code reusability and extensibility. |
| Difficulty in Testing | Circular references make testing difficult. Especially when dependencies between modules become complex, writing test code becomes difficult. This was also the problem I experienced during the Storybook writing process mentioned earlier. |
π€ Examples and Problems of Circular References
Let's look at the circular dependency problem through an example using simple React components.
References occur like moduleA β moduleB β moduleA β moduleB β moduleA β moduleB β moduleA β ...
And this can cause the following error:
This is because an infinite loop occurred and the call stack was exceeded.
π€ How to Detect Circular Dependency Problems
Actually, the best approach is to consciously check every time while progressing.
However, doing so causes productivity to drop significantly.
Considering whether each one will occur or not every time... we have too many problems to solve and things to pay attention to.
So let's learn about tools and methods to detect these problems.
π Detecting Circular References Using ESLint
You can detect circular references using ESLint.
Using the eslint-plugin-import plugin, you can detect circular references.
Then add the following configuration to the .eslintrc.js file.
With this configuration, ESLint will detect circular references and raise an error.
Actually, when I found and used ESLint on the internet, I always downloaded and used the import module... but I just learned this time that it's used for this purpose...
As always, if you don't properly understand something, you'll eventually pay the price...
π Detecting Circular References Using TypeScript
You can detect circular references using TypeScript.
TypeScript basically detects circular references and raises an error.
π Using Circular Reference Visualization Tools
Using tools like madge, you can visually display code dependencies to easily detect circular references.
Installation
Detecting Circular References
Then execute the following command to visually check the dependency graph.
src/ is the path to the source directory to analyze.
This will output files and paths that have circular references.
Visualizing the Dependency Graph
graph.svg is the name of the graph file to be generated.
src/ is the path to the source directory to analyze.
This will generate a graph.svg file to visually check the dependency graph.
π€ How to Solve Circular Dependency Problems
To solve circular references, you need to restructure dependencies between modules or introduce an intermediate layer to separate dependencies.
Below are common solutions.
| Solution | Description |
|---|---|
| Apply Dependency Inversion Principle (DIP) | The Dependency Inversion Principle (DIP) is a principle that makes high-level modules not depend on low-level modules, but depend on abstracted interfaces. This can reduce direct dependencies between modules. |
| Apply Dependency Injection (DI) | Dependency Injection (DI) is a design pattern that has dependencies injected from outside. This allows module dependencies to be injected from outside, preventing circular references. |
| Separate into Common Modules | Common functionality that two or more modules don't need to reference each other for can be separated into a separate module to remove dependencies. |
| Use Dynamic Import | If circular references are essential, you can avoid circular references at initialization time by using dynamic imports to load modules at runtime. |
| Redesign Dependency Structure | This is the fundamental solution. Redesign the dependency structure between modules so that circular references don't occur. |
π§Έ Real-World Case
As mentioned earlier, this is a problem I actually experienced while writing Storybook stories.
The resolution process can be referenced below.
Solving Circular Reference Error During Storybook Writing: Dropdown Component Case Analysis
π Summary
- Circular reference means a situation where two or more modules reference each other directly or indirectly.
- Circular references cause various problems such as module initialization problems, memory leaks, difficulty in code maintenance, and difficulty in testing.
- To detect circular references, you can use tools like
ESLint,TypeScript, andmadge. - To solve circular references, you can use methods such as Dependency Inversion Principle (DIP), Dependency Injection (DI), separating into common modules, dynamic import, and redesigning dependency structure.
We've learned about circular references.
One of the things I often heard at NAVER Boostcamp and in my major classes was the Single Responsibility Principle (SRP).
Even without going that far, the message was that it's better to keep dependencies as low as possible.
This was from the perspective that many unexpected errors can occur and maintenance becomes difficult.
While learning about circular references this time... I could feel that more diverse meanings were implied.
I thought I knew something... but I still feel like I have a lot to learn... I need to work harder...