Паттерн Singleton (Синглтон), теория

Паттерн Singleton классически реализуется в JS с помощью классов. Согласно этому паттерну у класса может быть только один инстанс, то есть объект создаётся только в одном экземпляре. Обычно это реализуется внутри класса так: новый инстанс создаётся только в случае, если ранее ещё ни одного инстанса создано не было. Если же инстанс уже существует, то возвращается ссылка на него.

Синглтоны отличаются от статических классов тем, что их инициализацию можно отложить до тех пор, пока, например, не получены нужные данные или пока не произошло какое-то пользовательское действие.

Такой подход может быть полезен, когда нужен один «глобальный» объект на всю систему. Это как преимущество (не нужно прокидывать данные внутрь каждого модуля, а просто везде доступна «глобальная переменная»), так и недостаток (в коде увеличивается связность, появляются неявные зависимости, размываются зоны ответственности).

Также порой непонятно является ли определённый класс Синглтоном или обычным классом, что может привести к его неверному использованию (попытка создания нескольких инстансов класса).

С помощью ES-модулей можно легко реализовать Singleton благодаря сокрытию имплементации внутри модуля:

singleton.js
let instance;
class Singleton {
constructor() {
// Если инстанс уже был создан ранее, возвращаем ссылку на него
if (instance) {
return instance;
}
// «Кешируем» инстанс в переменную
instance = this;
return instance;
}
}
export default Singleton;
app.js
import Singleton from "./singleton";
const inst1 = new Singleton();
const inst2 = new Singleton();
// inst1 === inst2 -> true

Альтернативный вариант: когда нет надобности инициализировать Синглтон с определёнными параметрами, можно создавать инстанс прямо внутри модуля с Синглтоном и экспортировать уже его вместо класса. А чтобы защитить экспортируемый объект от модификации (добавления или изменения свойств), можно воспользоваться механизмом «фриза»:

singleton.js
let instance;
class Singleton {
constructor() {
if (instance) {
return instance;
}
instance = this;
return instance;
}
method() {}
}
// «Замораживаем» свежесозданный инстанс
const singletonInstance = Object.freeze(new Singleton());
export default singletonInstance;
app.js
import MySingleton from "./singleton";
MySingleton.method();

Ещё один вариант реализации Singleton в JS — создание обычного объекта (ведь классы в JS — это «обёртка» над обычными объектами, подробнее об этом в части про паттерн Prototype).

То есть вместо создания одного инстанса класса можно просто создать один объект. А из-за того, что объекты импортируются в JS по ссылке на изначальный объект, этот объект будет существовать в единственном экземпляре:

const data = {
add() {},
remove() {},
};
Object.freeze(data);
export { data };

В классическом JS паттерн Singleton более применим, чем во фреймворках, так как, например, в React для распространения «глобальных» данных существует механизм Context API (изменение данных в Context будет триггерить перерисовку компонентов, а изменение обычного объекта — по умолчанию нет).

Поэтому в рамках React паттерн Singleton имеет смысл использовать для рапространения:

В случае с динамическими «глобальными» данными в React для их распространения лучше подходят Context API или менеджеры состояния.