TypeScript에는 클래스 멤버를 위한 접근 제한자가 세 가지 있습니다. public(기본값), protected, private입니다. 멤버를 어디서 쓸 수 있는지 제어하고, 잘못된 곳에서 접근하면 컴파일러가 보고합니다.
마지막 줄은 컴파일 오류지만, 그래도 실행했을 때 무엇이 출력되었는지 보세요: 123-45-6789. 이 페이지에서 가장 중요한 사실이며, private 절에서 설명합니다.
각 제한자가 허용하는 것
| 제한자 | 클래스 안 | 하위 클래스 | 밖에서 | 런타임 강제 |
|---|---|---|---|---|
public(기본값) | 예 | 예 | 예 | 강제할 것 없음 |
protected | 예 | 예 | 아니요 | 아니요 |
private | 예 | 아니요 | 아니요 | 아니요 |
#name(JavaScript) | 예 | 아니요 | 아니요 | 예 |
readonly | 읽기, 생성자에서 쓰기 | 읽기 | 읽기 | 아니요 |
제한자는 필드, 메서드, getter와 setter, 생성자, 매개변수 속성(constructor(private id: string))에 쓸 수 있습니다. public은 쓰지 않아도 되며, 많은 코드베이스가 생략합니다.
private는 컴파일 시점 검사다
TypeScript는 나머지 타입과 함께 private도 지웁니다. 컴파일된 클래스에는 평범한 속성이 있으므로, 타입 검사기를 거치지 않는 것은 모두 그것을 봅니다. 평범한 JavaScript 호출자, JSON.stringify, Object.keys, 그리고 의도된 비상구로 허용되는 TypeScript 자체의 대괄호 표기까지요.
private의 목적, 즉 멤버가 구현 세부 사항이라고 다른 개발자(와 에디터)에게 알리는 데는 그것으로 충분합니다. 보안 경계는 아니며, 직접 제거하지 않으면 private 필드도 로그와 JSON 응답에 들어갑니다.
#private 필드: 런타임에 강제된다
JavaScript에는 #로 쓰는 자체 private 필드가 있습니다. 엔진이 이를 강제합니다. 클래스 밖에서 obj.#field는 올바른 문법조차 아니며, 필드는 Object.keys, JSON.stringify, console.log에 나타나지 않습니다.
클래스 밖에서 s.#token이라고 쓰면 오류 TS18013(Property '#token' is not accessible outside class 'Session' because it has a private identifier)이고, private의 경우와 달리 런타임에도 우회할 방법이 없습니다. 런타임 규칙은 JavaScript의 private 필드를 보세요.
private와 #private 중 무엇을 쓸까
private x | #x | |
|---|---|---|
| 검사 주체 | 컴파일러 | JavaScript 엔진 |
Object.keys / JSON.stringify에 보임 | 예 | 아니요 |
대괄호 접근 obj["x"] | 허용 | 불가능 |
하위 클래스가 자체 x를 선언 | 아니요, 충돌함 | 예, 클래스마다 자체 #x |
펼치기 { ...obj }로 복사됨 | 예 | 아니요 |
| 메서드에서의 문법 | private helper() | #helper() |
데이터가 런타임에도 비공개여야 할 때(토큰, 라이브러리 사용자가 건드리면 안 되는 내부 상태)나 JSON.stringify가 빠뜨리기를 원할 때는 #private를 쓰세요. 목표가 깔끔한 공개 API뿐일 때, 프레임워크가 리플렉션으로 필드를 읽어야 할 때, 또는 코드베이스의 기존 스타일에 맞출 때는 private를 쓰세요. 둘을 함께 쓰지는 마세요. private #x는 오류 TS18010(An accessibility modifier cannot be used with a private identifier)입니다.
protected와 하위 클래스
protected 멤버는 클래스 안과 그것을 확장하는 모든 클래스 안에서 쓸 수 있지만, 밖에서 인스턴스를 통해서는 쓸 수 없습니다.
사람들을 놀라게 하는 규칙이 하나 있습니다. Polygon 안에서 this나 다른 Polygon의 sides는 읽을 수 있지만, 매개변수로 들어온 평범한 Shape의 것은 읽을 수 없습니다. other: Shape일 때 other.sides는 오류 TS2446(Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.)입니다. 하위 클래스는 계층에서 자신의 가지에 속한 객체의 protected 멤버에만 접근할 수 있습니다.
하위 클래스는 protected 멤버를 다시 선언해서 public으로 만들 수 있지만, public 멤버를 protected나 private로 만들 수는 없습니다. 초기값과 함께(public override sides = 6) 또는 타입으로만(declare public sides: number) 다시 선언하세요. 그냥 public sides: number;라고 쓰면 TS2564와 TS2612로 거부됩니다. 최신 클래스 필드에서는 super()가 반환된 뒤 상속받은 값이 undefined로 초기화될 것이기 때문입니다.
readonly
readonly는 생성 이후의 재대입을 막습니다. 필드는 선언이나 생성자에서 설정할 수 있고, 그 뒤의 대입은 모두 오류 TS2540입니다.
여기서 두 가지 한계가 드러납니다. readonly는 얕습니다. 배열을 교체할 수는 없지만 내용은 바뀔 수 있습니다(push까지 막으려면 readonly string[]로 지정하세요). 그리고 private처럼 런타임에 사라지므로, 컴파일러가 거부한 대입도 그대로 실행되었습니다. 런타임에 바뀌면 안 되는 값에는 Object.freeze나 setter 없는 getter를 쓰세요. Readonly<T>와 readonly 배열은 readonly 페이지에서 다룹니다.
readonly는 접근 제한자와 조합됩니다. private readonly cache = new Map<string, number>()는 내부적이고 재대입되지 않는 필드에 흔히 쓰는 패턴입니다.
흔한 실수
private를 보안으로 취급하기. 런타임에는 사라집니다. 숨겨야 할 데이터에는#field를 쓰고, 비밀이 담긴 객체를JSON.stringify에 곧바로 넘기지 마세요.- 모든 곳에
public쓰기. 기본값이므로 붙여도 달라지는 것이 없습니다. - "내부용"이면 모두
protected쓰기. 필요로 하는 하위 클래스가 없다면private가 노출 면을 더 작게 유지합니다. readonly가 중첩 데이터를 동결할 거라 기대하기. 속성 자체의 재대입만 막습니다.
자주 묻는 질문
TypeScript의 접근 제한자에는 무엇이 있나요?
public(기본값: 어디서나 접근 가능), protected(클래스와 하위 클래스 안에서), private(클래스 안에서만)입니다. readonly는 생성자 이후의 재대입을 막는 별도 제한자이며, 셋 중 어느 것과도 조합할 수 있습니다.
TypeScript에서 private와 #private의 차이는 무엇인가요?
private는 컴파일러만 검사합니다. 출력된 JavaScript에는 평범한 속성이 있으므로 obj["secret"], JSON.stringify, Object.keys가 여전히 봅니다. #secret은 JavaScript의 private 필드로, 런타임이 강제하며 클래스 밖의 코드는 아예 읽을 수 없습니다.
TypeScript의 private는 정말로 private인가요?
컴파일 시점에만 그렇습니다. 컴파일 후에 필드는 어떤 JavaScript 코드든 읽을 수 있는 평범한 속성입니다. TypeScript는 비상구로 private 멤버에 대한 대괄호 접근(obj["field"])까지 허용합니다. 런타임에도 비공개가 유지되어야 한다면 #field를 쓰세요.
TypeScript에서 protected와 private의 차이는 무엇인가요?
private 멤버는 그것을 선언한 클래스 안에서만 보입니다. protected 멤버는 하위 클래스 안에서도 보입니다. 둘 다 클래스 계층 밖에서 인스턴스를 통해 접근할 수 없습니다.
TypeScript에서 readonly 속성을 바꿀 수 있나요?
readonly 속성은 선언이나 생성자에서만 대입할 수 있고, 그 밖에서는 안 됩니다(그렇지 않으면 TS2540). 얕게 동작합니다. readonly tags: string[]는 교체할 수 없지만 tags.push()는 여전히 동작합니다. 그것까지 막으려면 readonly string[]를 쓰세요.