Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Модифікатори доступу в TypeScript: public, private, protected, readonly». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Модифікатори доступу в TypeScript** (`public`, `private`, `protected`, `readonly`) визначають видимість членів класу. `public` дозволяє доступ звідусіль. `private` обмежує доступ лише самим класом. `protected` відкриває також підкласам. `readonly` блокує перезапис після конструктора, але читання дозволено скрізь. Всі ці перевірки лише на етапі компіляції: скомпільований JavaScript їх не бачить. ```typescript class User { public name: string; // звідусіль private age: number; // тільки клас protected role: string; // підкласи також readonly id: string; // без перезапису після ініт. } ``` **Ключове:** `private` не захищає дані під час виконання. Для реального захисту використовуй `#` поля JavaScript.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Модифікатори доступу в TypeScript** (`public`, `private`, `protected`, `readonly`) контролюють, яким частинам класу можна звертатись ззовні, з підкласів або лише зсередини самого класу. ## Теорія ### TL;DR - Усі члени класу за замовчуванням `public`, тому модифікатори здебільшого *обмежують* доступ, а не надають його - `private` замикає член лише в межах класу; `protected` відкриває також підкласам; `readonly` дозволяє читати будь-де, але блокує перезапис після ініціалізації - Ці перевірки існують тільки на етапі компіляції. У скомпільованому JavaScript від них не лишається сліду - Практичне правило: починай з `private`, послаблюй до `protected` лише коли потрібна спадковість, і став `readonly` на все, що не повинно змінюватись після створення об'єкта ### Швидкий приклад ```typescript class Animal { public name: string; // Доступно звідусіль private secret: string = "hidden"; // Тільки цей клас protected species: string = "mammal"; // Цей клас і підкласи readonly id: number; // Читати скрізь, записати один раз constructor(name: string, id: number) { this.name = name; this.id = id; } } const cat = new Animal("Whiskers", 1); console.log(cat.name); // "Whiskers" - public, все добре console.log(cat.id); // 1 - readonly, все добре // cat.secret // Помилка: private // cat.id = 2; // Помилка: readonly ``` `public` і `readonly` доступні ззовні класу. `private` і `protected` - ні. ### Головна різниця `private`, `protected` і `readonly` обмежують різні речі. `private` обмежує *хто* може читати або записувати член класу (лише сам клас, і більше ніхто). `protected` розширює це правило до підкласів. `readonly` взагалі не обмежує читання. Він обмежує *коли* можна писати: тільки під час ініціалізації. Ці дві ідеї незалежні: можна мати `private readonly` властивість, яка закрита для класу і заморожена після конструктора. ### Коли що використовувати - Внутрішній стан, до якого ніхто ззовні не повинен звертатись: `private` - Допоміжний метод або властивість, яку підклас має успадкувати: `protected` - ID, URL конфігурації, будь-що що не може змінюватись після створення: `readonly` - Все, що є частиною публічного API: `public` (або просто без модифікатора, бо за замовчуванням `public`) ### Таблиця порівняння | Модифікатор | Всередині класу | Підклас | Ззовні | Перезапис після ініт. | |---|---|---|---|---| | `public` | так | так | так | так | | `protected` | так | так | ні | так | | `private` | так | ні | ні | так | | `readonly` | один раз | тільки читання | тільки читання | ні | ### Як компілятор це обробляє TypeScript перевіряє ці обмеження під час `tsc` компіляції. Компілятор сканує оголошення класів і позначає будь-який доступ, що порушує вказаний модифікатор. Після цього у скомпільованому `.js` файлі від них не лишається нічого. V8 і Node.js бачать звичайні поля об'єктів. Тобто `private balance = 1000` стає просто `balance = 1000` у вихідному коді. TypeScript Language Server у VS Code застосовує ті самі правила в реальному часі через `.d.ts` метадані, саме тому редактор підкреслює порушення ще до запуску. Це відрізняється від Java чи C#, де контроль доступу перевіряється під час виконання на рівні VM. У TypeScript модифікатори це інструмент для зручної розробки. Якщо хтось зробить `as any`, перевірка просто зникне. Більшість розробників усвідомлюють це вперше, коли логують нібито приватну властивість і бачать її прямо у консолі. ### Типові помилки **Помилка 1: вважати, що `private` захищає дані під час виконання** ```typescript class Safe { private pin = 1234; } const s = new Safe(); (s as any).pin = 0000; // Працює. Жодної помилки. console.log((s as any).pin); // 0 ``` `private` зникає при компіляції. Для реального приховування під час виконання використовуй синтаксис `#` JavaScript (ES2022): ```typescript class Safe { #pin = 1234; // V8 сам це контролює - доступ ззовні заблокований навіть через casting } ``` **Помилка 2: використовувати `protected` замість "майже private"** ```typescript class Parent { protected secret = "internal"; } class Child extends Parent { expose() { return this.secret; } } const c = new Child(); c.expose(); // "internal" - дані потрапили назовні ``` Якщо жодному підкласу не потрібен прямий доступ, використовуй `private`. `protected` це конкретно для хуків (hooks) спадковості, а не для "трохи менш приватного". **Помилка 3: вважати, що `readonly` блокує глибоку мутацію** ```typescript class Config { readonly options = { debug: false }; } const cfg = new Config(); cfg.options = { debug: true }; // Помилка - перезапис посилання заблокований cfg.options.debug = true; // Ок - мутація самого об'єкта дозволена ``` `readonly` блокує перезапис посилання, але не мутацію самого об'єкта. Для глибокої незмінності комбінуй з `Object.freeze()` або `DeepReadonly`. **Помилка 4: забути синтаксис властивостей параметрів** ```typescript // Ці два класи поводяться однаково: class User { private name: string; constructor(name: string) { this.name = name; } } class User { constructor(private name: string) {} // Оголошує і присвоює водночас } ``` Якщо забути додати модифікатор у сигнатуру конструктора, властивість просто не буде оголошена на класі. ### Де зустрічається - React: `readonly` на типах пропсів для відловлювання спроб мутації всередині компонента - Express/Passport.js: `private` для обробників токенів і внутрішнього стану middleware - NestJS: `protected` методи в базових контролерах, які розширюються підкласами - Redux Toolkit: `readonly` на зрізах стану для відловлювання випадкових мутацій - Prisma: `readonly` на згенерованих ID моделей ### Питання на співбесіді **Q:** Що TypeScript `private` компілює в JavaScript? **A:** Звичайне поле класу без жодних обмежень. `class X { private y = 1 }` стає `class X { y = 1 }` у вихідному коді. Будь-який доступ під час виконання працює нормально. **Q:** Чи може підклас звертатись до `private` члена? **A:** Ні. TypeScript дасть помилку компіляції. Використовуй `protected`, якщо підклас потребує доступу до цього члена. **Q:** Яка різниця між `private` і `readonly`? **A:** Вони контролюють різні речі. `private` визначає, хто може звертатись. `readonly` визначає, коли можна записувати. `private readonly` властивість поєднує обидва: доступ лише в межах класу і незмінність після конструктора. **Q:** Яка користь від `private`, якщо він не діє під час виконання? **A:** Зручність під час розробки. IDE підсвічує порушення ще до запуску коду, `tsc` блокує невірні білди, і це передає наміри команді, яка читає код. **Q:** Бібліотека постачається JS-споживачам. Як реально обмежити доступ під час виконання? **A:** Комбінуй підходи. TypeScript `private` для підтримки IDE і типів. JavaScript `#` поля для реального обмеження під час виконання: V8 блокує доступ незалежно від casting. Для додаткової ізоляції використовуй фабричні функції, що повертають типізований інтерфейс, щоб споживачі взагалі не тримали прямого посилання на екземпляр класу. ## Приклади ### Усі чотири модифікатори в одному класі ```typescript class BankAccount { public owner: string; // Доступно всім private balance: number; // Тільки всередині protected accountType: string; // Підкласи бачать readonly id: string; // Один раз встановлено, не змінюється constructor(owner: string, initial: number, id: string) { this.owner = owner; this.balance = initial; this.accountType = "standard"; this.id = id; } public deposit(amount: number): void { this.balance += amount; // OK всередині класу } public getBalance(): number { return this.balance; } } const acc = new BankAccount("Alice", 500, "ACC-001"); acc.owner; // "Alice" acc.getBalance(); // 500 // acc.balance; // Помилка: private // acc.id = "X"; // Помилка: readonly ``` `balance` прихований, але доступний через `getBalance()`. Це стандартний патерн інкапсуляції для класів. ### Спадковість з protected ```typescript class Vehicle { protected speed: number = 0; protected accelerate(amount: number): void { this.speed += amount; } } class Car extends Vehicle { public drive(): string { this.accelerate(60); // OK - protected, підклас може викликати return `Speed: ${this.speed}km/h`; } } const car = new Car(); car.drive(); // "Speed: 60km/h" // car.speed; // Помилка: protected, ззовні ієрархії недоступно ``` `protected` тут правильний вибір: `Car` потребує внутрішніх деталей `Vehicle`, але споживачі `Car` не повинні їх бачити. ### TypeScript private vs JavaScript #private ```typescript class Secure { private tsField = "typescript only"; #jsField = "runtime enforced"; // ES2022 reveal() { console.log(this.tsField); // OK console.log(this.#jsField); // OK } } const s = new Secure(); s.reveal(); (s as any).tsField; // Працює - TypeScript private зникає під час виконання // s.#jsField; // SyntaxError - V8 сам це блокує ``` Синтаксис `#` контролює сам V8. TypeScript `private` не дає жодного захисту після запуску коду. Для чутливих даних правильний вибір - `#`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.