2024-06-22.md
๐กDIL: ์ดํํฐ๋ธ ํ์ ์คํฌ๋ฆฝํธ
์คํฐ๋: ์๊ฐ CS, https://github.com/monthly-cs/2024-05-effective-typescript
์์ฑ์ผ: 2024-06-22
์์ฑ์: dusunax
์์ดํ 56: ์ ๋ณด๋ฅผ ๊ฐ์ถ๋ ๋ชฉ์ ์ผ๋ก private ์ฌ์ฉํ์ง ์๊ธฐ
- ECMAScript 2022 ์ด์ , ์๋ฐ์คํฌ๋ฆฝํธ๋ ํด๋์ค์ ๋น๊ณต๊ฐ ์์ฑ์ ๋ง๋ค ์ ์์๋ค
- ์ธ๋์ค์ฝ์ด ์ ๋์ฌ๋ฅผ ๊ด๋ก๋ก ์ฌ์ฉํ์ ๋ฟ~~
- ํ์
์คํฌ๋ฆฝํธ์ public, protected, private ์ ๊ทผ ์ ์ด์๋ฅผ ์ฌ์ฉํ๋ฉด ๊ณต๊ฐ ๊ท์น์ ๊ฐ์ ํ ์ ์๋ ๊ฒ์ผ๋ก ์คํดํ ์ ์๋ค~!
- ํ์ง๋ง ์ ๊ทผ ์ ์ด์๋? ํ์ ์คํฌ๋ฆฝํธ ํค์๋์ด๊ธฐ ๋๋ฌธ์ ์ปดํ์ผ ํ์๋ ์ ๊ฑฐ๋๋ค.
// TS
class Diary {
private secret = "cheated on my English test";
}
const diary = new Diary();
diary.secret;
// ~~~ 'secret' ์์ฑ์ private์ด๋ฉฐ 'Diary' ํด๋์ค ๋ด์์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค.
// JS ์ปดํ์ผ ํ
class Diary {
constructor() {
this.secret = "cheated on my English test"; // private ํค์๋๋ ์ฌ๋ผ์ง, secret์ ์ผ๋ฐ์ ์ธ ์์ฑ
}
}
const diary = new Diary();
diary.secret;
// TS์ ์ ๊ทผ ์ ์ด์๋ค์? ์ปดํ์ผ ์์ ์๋ง ์ค๋ฅ ํ์. ์ธ๋์ค์ฝ์ด๋ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐํ์์ ํจ๋ ฅ์ด ์๋ค.
// ๋จ์ธ๋ฌธ์ ์ฌ์ฉํ๋ฉด? TS์์๋ private์ ์ ๊ทผํ ์ ์๋ค.
(diary as any).secret; // ์ ์
์์ A:
- JS์์ ์บก์ํ์ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ
declare function hash(text: string): number;
// ํด๋ก์ ๋ฅผ ๋ง๋๋ ์์ฑ์ ์์
class PasswordChecker {
checkPassword: (password: string) => boolean;
constructor(passwordHash: number) {
// checkPassword ํจ์๊ฐ ํด๋ก์ ๋ฅผ ์ด์ฉํ์ฌ passwordHash ๋ณ์์ ์ ๊ทผํ ์ ์์
this.checkPassword = (password: string) => {
// ์ ๋ฌ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ํด์ํํ์ฌ ์ ์ฅ๋ ํด์๊ฐ๊ณผ ๋น๊ต
return hash(password) === passwordHash;
};
}
}
const checker = new PasswordChecker(hash("s3cret"));
checker.checkPassword("s3cret"); // true
Closure ์์ A: ์คํ ๊ณผ์
1. ํด์ ํจ์ ํธ์ถ
hash("s3cret")
๊ฐ123456
์ด๋ผ๋ ํด์ ๊ฐ์ ๋ฐํํ๋ค๊ณ ๊ฐ์
2. PasswordChecker ํด๋์ค์ ์ธ์คํด์ค ์์ฑ
- new PasswordChecker(123456) ํธ์ถ๋ก PasswordChecker ํด๋์ค์ ์ธ์คํด์ค๊ฐ ์์ฑ๋๋ค.
- ์์ฑ์ ํจ์๊ฐ ํธ์ถ๋๊ณ passwordHash ๋งค๊ฐ๋ณ์๋
123456
์ด๋ผ๋ ๊ฐ์ ๋ฐ๋๋ค.
3. ํด๋ก์ ์์ฑ
- ์์ฑ์ ํจ์ ๋ด๋ถ์์ this.checkPassword์ ํ ๋น๋ ํจ์๊ฐ ์์ฑ
- passwordHash ๋ณ์๋ฅผ ์บก์ฒํ์ฌ ํด๋ก์ ๋ฅผ ๋ง๋ฆ => passwordHash์ ์ ๊ทผํ ์ ์๋ค
- this.checkPassword ํจ์๋ password ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ์, ์ ๋ฌ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ํด์ํํ๊ณ ์ ์ฅ๋ passwordHash ๊ฐ๊ณผ ๋น๊ตํ๋ค.
- passwordHash ๋ณ์๋ฅผ ์บก์ฒํ์ฌ ํด๋ก์ ๋ฅผ ๋ง๋ฆ => passwordHash์ ์ ๊ทผํ ์ ์๋ค
ํด๋ก์ ์งง๋ง ์์
- ์คํ ์ปจํ
์คํธ ์ฑก์ฑก ์๊ธฐ
- ํด๋ก์ ๋ ํจ์๊ฐ ์์ฑ๋ ๋น์์ lexical enviorment(์ฌ๊ธฐ์๋ passwordHash ๋ณ์)์ ๊ธฐ์ตํ์ฌ, ๋์ค์ ํจ์๊ฐ ํธ์ถ๋ ๋์๋ ๊ทธ enviorment์ ๋ณ์ ๊ฐ์ฒด์ ์ ๊ทผ
- ์ฝ๋๊ฐ ์คํ๋๋ ๋ฐ ํ์ํ execution context๋ ์คํ์์ pop๋จ => passwordHash์ ์ ๊ทผํ๋ ค๋ฉด, ํจ์๊ฐ ์์ฑ๋์๋ ์์ ์ ๋ ์์ปฌ ํ๊ฒฝ์ ์ฐธ์กฐํด์ผํจ(์ฆ, ํด๋ก์ ๋ฅผ ํตํด ์ ๊ทผํ ์ ์์). ๋ณ์ ๊ฐ์ฒด๋ ํจ์๊ฐ ์ฐธ์กฐํ์ง ์์ผ๋ฉด ์ ๊ทผํ ์ ์๋ค. ๋ํ ๋ณ์๋ ํจ์์ ์ํด ์ฐธ์กฐ๋๊ณ ์์ผ๋ฏ๋ก ๊ฐ๋น์ง ์ปฌ๋ํ ๋์ง ์์.
์์ A์ ๋จ์
- ๋ฉ์๋ ์ ์ ์์น๊ฐ ์ ํ์ : ์์ฑ์ ์ธ๋ถ์์ passwordHash์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์, passwordHash์ ์ ๊ทผํ๋ ๋ฉ์๋๋ ์์ฑ์ ๋ด๋ถ์ ์ ์๋์ด์ผ ํ๋ค.
- ๋ฉ๋ชจ๋ฆฌ: ์ธ์คํด์ค๋ฅผ ์์ฑํ ๋๋ง๋ค ๋ฉ์๋์ ๋ณต์ฌ๋ณธ์ด ์์ฑ๋๋ค.
- ๋์ผ ํด๋์ค์์ ์์ฑ๋ ์ธ์คํด์ค: ์๋ก์ ๋น๊ณต๊ฐ ๋ฐ์ดํฐ์ ์ ๊ทผํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๋ค.
- ํด๋์ค ๋จ์ ๋น๊ณต๊ฐ? ์ผ๋ฐ์ ์ธ ๊ฐ์ฒด์งํฅ ์ธ์ด์์๋ ๋์ผ ํด๋์ค์ ๊ฐ๋ณ ์ธ์คํด์ค๋ผ๋ฆฌ private ์์ฑ์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
์์ B: Private Class Fields
- ECMAScript 2022 ํ์ค, ํด๋์ค ์ธ์คํด์ค ์ธ๋ถ์์๋ ์ง์ ์ ๊ทผํ ์ ์๋ค.
- ์ด์ ๋ฒ์ ์ผ๋ก ์ปดํ์ผ ์ WeapMap์ผ๋ก ๋์ฒด๋จ
class PasswordChecker {
#passwordHash: number;
constructor(passwordHash: number) {
this.#passwordHash = passwordHash;
}
checkPassword(password: string) {
return hash(password) === this.#passwordHash;
}
}
const checker = new PasswordChecker(hash("s3cret"));
checker.checkPassword("secret"); // false
checker.checkPassword("s3cret"); // true
WeapMap์ด๋?
- ๊ฐ์ฒด๋ง ํ์ฉ
- WeakMap์ ํค๋ก ๊ฐ์ฒด๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. ์์ ์๋ฃํ(์: ์ซ์, ๋ฌธ์์ด ๋ฑ)์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ์ฝํ ์ฐธ์กฐ(Weak References)
- WeakMap์ ํค๋ก ์ฌ์ฉ๋ ๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ๊ฐ ์ฝํ๊ฒ ์ ์ง๋ฉ๋๋ค. ์ฝํ ์ฐธ์กฐ๋ ๋ค๋ฅธ ๊ณณ์์ ํด๋น ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ์ง ์์ผ๋ฉด, ํด๋น ๊ฐ์ฒด๊ฐ ๊ฐ๋น์ง ์ปฌ๋ ์ ์ ๋์์ด ๋ ์ ์์์ ์๋ฏธํฉ๋๋ค. ์ฆ, WeakMap์ ํค๋ก ์ฌ์ฉ๋ ๊ฐ์ฒด๊ฐ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋ ์ ์์ต๋๋ค.
// WeakMap์ ์ฌ์ฉํ์ฌ ํ๋ผ์ด๋น ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐ ์์
const privateData = new WeakMap();
// ํด๋์ค ์ ์
class Person {
constructor(name, age) {
// WeakMap์ ํ์ฌ ์ธ์คํด์ค(this)๋ฅผ ํค๋ก ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ์ ์ฅ
privateData.set(this, { name: name, age: age });
}
// ํ๋ผ์ด๋น ๋ฉ์๋ ์์: ์ธ๋ถ์์ ์ ๊ทผํ ์ ์์
#privateMethod() {
console.log(`This is a private method for ${privateData.get(this).name}`);
}
// ๊ณต๊ฐ ๋ฉ์๋์์ ํ๋ผ์ด๋น ๋ฐ์ดํฐ ์ฌ์ฉ ์์
introduce() {
// privateData๋ฅผ ํตํด ํ๋ผ์ด๋น ๋ฐ์ดํฐ ์ ๊ทผ
const { name, age } = privateData.get(this);
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
// ํ๋ผ์ด๋น ๋ฉ์๋ ํธ์ถ
this.#privateMethod();
}
}
// Person ํด๋์ค ์ธ์คํด์ค ์์ฑ
const person1 = new Person("Alice", 30);
// introduce ๋ฉ์๋ ํธ์ถ
person1.introduce(); // ์ถ๋ ฅ: Hello, my name is Alice and I am 30 years old.
// This is a private method for Alice
// ์ธ๋ถ์์ privateData์ ์ง์ ์ ๊ทผํ ์ ์์
console.log(privateData.get(person1)); // undefined
์์ฝ
- public, protected, private๊ฐ์ access modifiers๋ ํ์
์์คํ
์๋ง ๊ฐ์ ๋จ.
- ๋ฐํ์์๋ ์์ฉ์ด ์์ผ๋ฉฐ, ๋จ์ธ๋ฌธ์ผ๋ก ์ฐํํ ์ ์๋ค.
- ํด๋ก์ & ํ๋ผ์ด๋น ํ๋ ์ฌ์ฉํ๊ธฐ
์์ดํ 57: ์์ค๋งต์ ์ฌ์ฉํ์ฌ ํ์ ์คํฌ๋ฆฝํธ ๋๋ฒ๊น ํ๊ธฐ
- ํ์
์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ์คํํ๋ค: ํ์
์คํฌ๋ฆฝํธ ์ปดํ์ผ๋ฌ๊ฐ ์์ฑํ JS ์ฝ๋๋ฅผ ์คํํ๋ค.
- ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ค๋ฅธ ํํ๋ก ๋ณํํ๋ ๋๊ตฌ๋ค ์์) ์์ถ๊ธฐ minifier๋ ์ ์ฒ๋ฆฌ๊ธฐ preprocessor
- ๋๋ฒ๊ฑฐ๋ ๋ฐํ์์ ๋์ => ๋๋ฒ๊น
์ ํ๋ฉด ๋ณด๊ฒ๋๋ ์ฝ๋๋ ์ ์ฒ๋ฆฌ๊ธฐ, ์ปดํ์ผ๋ฌ, ์์ถ๊ธฐ๋ฅผ ๊ฑฐ์น ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋์ด๋ค.
- ๋ณํ๋ JS๋ ๋ณต์กํ๋ค.
์์ค๋งต source map
- ๋ธ๋ผ์ฐ์ ์ ์กฐ์ฌ๊ฐ ๋ด๋์ ํด๊ฒฐ์ฑ : ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์ ๋ง์ IDE๊ฐ ์ง์
- ๋ณํ๋ ์ฝ๋์ ์์น์ ์ฌ๋ฒ๋ค์ ์๋ณธ ์ฝ๋์ ์๋ ์์น์ ์ฌ๋ฒ๋ก ๋งคํ
๋ณํ๋ JS ์์
-
async, await์ ์ง์ํ๊ธฐ ์ํด ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ํ ๋จธ์ (state machine)์ผ๋ก ์ฌ์์ฑ
- ์ฝ๋์ ๋์์ ๋์ผํ์ง๋ง, ํํ๊ฐ ๋งค์ฐ ๋ค๋ฅด๊ฒ ๋๋ค.
- tsconfig.json์์ sourceMap ์ค์ => .ts ํ์ผ์ ๋์ํ๋ .js์ .js.map ํ์ผ์ ์์ฑ
{ "compilerOptions": { "sourceMap": true } }
-
์์ค๋งต์ด js์ ํจ๊ป ์์ผ๋ฉด? ๋ธ๋ผ์ฐ์ ์ ๋๋ฒ๊ฑฐ์์ ์๋ก์ด index.ts ํ์ผ์ด ๋ํ๋จ => ๋ธ๋ ์ดํฌํฌ์ธํธ ์ค์ & ๋ณ์ ์กฐ์ฌ ๊ฐ๋ฅ
- ๋๋ฒ๊ฑฐ ์ข์ธก์ ํ์ผ ๋ชฉ๋ก์์ ํ์ผ์ด ๊ธฐ์ธ์ ๊ธ๊ผด์ด๋ค? => ์น ํ์ด์ง์ ์ค์ ๋ก ์กด์ฌํ๋ ํ์ผ์ด ์๋๋ค!
- index.js.map ํ์ผ์ด ํ์ผ ๋ด์ฉ์ ํฌํจํ๊ฑฐ๋(์ธ๋ผ์ธ), ๋ณ๋์ index.ts ํ์ผ์ ๊ฐ์ง๋๋ก ์ค์ ํ ์ ์์(์ฐธ์กฐ ํฌํจ, ๋ธ๋ผ์ฐ์ ๊ฐ ๋คํธ์ํฌ๋ฅผ ํตํด ๋ก๋)
-
์์๋ ์
- TS๊ฐ bundler๋ minifier๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด? ๋ฒ๋ค๋ฌ๋ ์์ถ๊ธฐ๊ฐ ๊ฐ์ ์์ค๋งต์ ์์ฑํ๋ค.
- ์ด์์ ์ธ ๋๋ฒ๊น ํ๊ฒฝ์ด ๋๋ ค๋ฉด ์์ฑ๋ ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์๋ ์๋ณธ ํ์ ์คํฌ๋ฆฝํธ ์์ค๋ก ๋งคํ๋๋๋ก ํ ๊ฒ
- ๋ฒ๋ค๋ฌ๊ฐ TS ์ง์์ด๋ผ๋ฉด ๋ณ๋ ์ค์ ์์ด ๊ด์ฐฎ์ง๋ง, ์๋๋ผ๋ฉด ์ถ๊ฐ ์ค์ ํ์
- ์์ฉ ํ๊ฒฝ์ ์์ค๋งต์ด ์ ์ถ๋๊ณ ์๋์ง ํ์ธ
- ๋๋ฒ๊ฑฐ๋ฅผ ์ด์ง ์๋ ์ด์ ์์ค๋งต์ด ๋ก๋๋์ง ์์ผ๋ฏ๋ก ์ฌ์ฉ์์๊ฒ ์ฑ๋ฅ ์ ํ๋ ์์
- ์๋ณธ ์ฝ๋์ ๋ณต์ฌ๋ณธ์ ์ฃผ์, ๋ฒ๊ทธ ์ถ์ ์ ์ํ url๋ฑ ๊ณต๊ฐํ ํ์๊ฐ ์๋ ๋ด์ฉ์ด ์์ ๊ฒ
- NodeJS ๋๋ฒ๊น ์๋ ์์ค๋งต ์ฌ์ฉํ ์ ์๋ค.
- ํ์ ์ฒด์ปค๊ฐ ์ฝ๋๋ฅผ ์คํํ๊ธฐ ์ ์ค๋ฅ๋ฅผ ์ก์ ์ ์์ง๋ง, ๋๋ฒ๊ฑฐ๋ฅผ ๋์ฒดํ ์๋ ์๋ค. ์์ค๋งต์ ์ฌ์ฉํ๋ฉด ํ์ ์คํฌ๋ฆฝํธ ๋๋ฒ๊น ํ๊ฒฝ์ ๊ตฌ์ถํ ์ ์๋ค.
- TS๊ฐ bundler๋ minifier๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด? ๋ฒ๋ค๋ฌ๋ ์์ถ๊ธฐ๊ฐ ๊ฐ์ ์์ค๋งต์ ์์ฑํ๋ค.
Things to Remember
- Don't debug generated JavaScript. Use source maps to debug your TypeScript code at runtime.
- ์๋ณธ ์ฝ๋๊ฐ ์๋ ๋ณํ๋ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ๋๋ฒ๊น ํ์ง ๋ง๊ณ , ์์ค๋งต์ ์ฌ์ฉํด์ ๋ฐํ์์ ํ์ ์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ์~!
- Make sure that your source maps are mapped all the way through to the code that you run.
- ์์ค๋งต์ด ์์ ํ ๋งคํ๋์๋์ง ํ์ธํ ๊ฒ
- Know how to debug Node.js code written in TypeScript.
- Depending on your settings, your source maps might contain an inline copy of your original code. Don't publish them unless you know what you're doing!
- ์์ค๋งต์ ์๋ณธ ์ฝ๋๊ฐ ๊ทธ๋๋ก ํฌํจ๋๋๋ก ์ค์ ๋์ด ์์ ์๋ ์๋ค. ๊ณต๊ฐํ์ง ์๋๋ก ํ๊ธฐ