From e4021a57341e8e05976fe8c363aeac8d932e6dd9 Mon Sep 17 00:00:00 2001 From: Minhyeok Kim Date: Tue, 5 May 2026 23:59:02 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=201=EC=A3=BC=EC=B0=A8=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\352\271\200\353\257\274\355\230\201.md" | 1110 +++++++++++++++++ 1 file changed, 1110 insertions(+) create mode 100644 "week-01/\352\271\200\353\257\274\355\230\201.md" diff --git "a/week-01/\352\271\200\353\257\274\355\230\201.md" "b/week-01/\352\271\200\353\257\274\355\230\201.md" new file mode 100644 index 0000000..c5b466d --- /dev/null +++ "b/week-01/\352\271\200\353\257\274\355\230\201.md" @@ -0,0 +1,1110 @@ +## 1. 동등 연산자(`==`)와 일치 연산자(`===`) + +### 동등 연산자란? + +동등 연산자 `==`는 두 값의 **타입이 다르더라도 암묵적 타입 변환을 한 뒤 값이 같으면 `true`를 반환**한다. + +```js +6 == "6"; // true +0 == ""; // true +false == 0; // true +null == undefined; // true +``` + +반면 일치 연산자 `===`는 **타입과 값이 모두 같아야 `true`를 반환**한다. + +```js +6 === "6"; // false +6 === 6; // true +``` + +### 쿼리스트링 값 비교에 `==`를 사용해도 될까? + +예를 들어 URL 쿼리스트링으로 받은 값은 보통 문자열이다. + +```js +const pageFromQuery = "5"; +const pageFromState = 5; + +pageFromQuery == pageFromState; // true +``` + +이 경우 `==`를 사용하면 동작은 할 수 있다. 하지만 권장되지는 않는다. +이유는 `==`가 자동 타입 변환을 수행하기 때문에, 의도하지 않은 비교 결과가 나올 수 있기 때문이다. + +```js +0 == ""; // true +"0" == false; // true +null == undefined; // true +``` + +따라서 쿼리스트링 값은 명시적으로 변환한 뒤 `===`로 비교하는 것이 더 안전한다. + +```js +const pageFromQuery = Number(searchParams.get("page")); +const currentPage = 5; + +pageFromQuery === currentPage; // true +``` + +### 정리 + +`==`는 JavaScript의 암묵적 타입 변환 규칙을 정확히 알고 있을 때만 제한적으로 사용할 수 있다. +일반적인 서비스 코드에서는 예측 가능성을 높이기 위해 `===`를 기본으로 사용하는 것이 좋다. + +--- + +## 2. 문자열은 원시 타입이며 변경 불가능하다 + +JavaScript에서 문자열은 원시 타입이다. +그리고 원시 타입의 값은 **변경 불가능한 값**, 즉 immutable value이다. + +```js +let text = "hello"; + +text[0] = "H"; + +console.log(text); // "hello" +``` + +위 코드에서 `"hello"`의 첫 번째 문자를 직접 바꾸려고 했지만, 원본 문자열은 변경되지 않는다. + +### 그럼 문자열 변경은 어떻게 이루어질까? + +문자열 자체를 바꾸는 것이 아니라, **새로운 문자열을 만들어 변수에 다시 할당**한다. + +```js +let text = "hello"; + +text = "H" + text.slice(1); + +console.log(text); // "Hello" +``` + +여기서 기존 문자열 `"hello"`가 직접 수정된 것이 아니다. +`"H" + text.slice(1)`의 결과로 `"Hello"`라는 새로운 문자열이 만들어지고, `text`가 그 값을 다시 가리키게 된다. + +### `slice`와 `substring` + +`slice`와 `substring`은 문자열의 일부를 가져오는 메서드이다. +이 메서드들도 원본 문자열을 변경하지 않고, **잘라낸 결과를 새로운 문자열로 반환**한다. + +```js +const text = "JavaScript"; + +const result1 = text.slice(0, 4); +const result2 = text.substring(4, 10); + +console.log(result1); // "Java" +console.log(result2); // "Script" +console.log(text); // "JavaScript" +``` + +### 참고 자료 + +- [MDN - String.prototype.substring](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/substring) +- [문자열에서 특정 위치의 문자를 변경하고 싶은 경우](https://velog.io/@jhsung23/JS-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90%EC%84%9C-%ED%8A%B9%EC%A0%95-%EC%9C%84%EC%B9%98%EC%9D%98-%EB%AC%B8%EC%9E%90%EB%A5%BC-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%9D%80-%EA%B2%BD%EC%9A%B0) + +--- + +## 3. `Object.is` + +`Object.is`는 두 값이 같은 값인지 비교하는 메서드이다. +일반적으로 `===`와 비슷하게 동작하지만, 몇 가지 차이가 있다. + +```js +NaN === NaN; // false +Object.is(NaN, NaN); // true + +-0 === +0; // true +Object.is(-0, +0); // false + +Object.is(1, "1"); // false +Object.is(1, 1); // true +``` + +### `Object.is`의 특징 + +`Object.is`는 `==`처럼 타입 변환을 하지 않는다. +그리고 `===`와 달리 다음 두 가지를 더 엄격하게 구분한다. + +1. `NaN`과 `NaN`을 같은 값으로 본다. +2. `-0`과 `+0`을 다른 값으로 본다. + +### 이것은 장점일까, 단점일까? + +상황에 따라 장점이 될 수도 있고 단점이 될 수도 있다. + +`NaN`을 안정적으로 비교해야 하는 상황에서는 장점이다. + +```js +const value = Number("hello"); + +Object.is(value, NaN); // true +Number.isNaN(value); // true +``` + +반면 대부분의 일반적인 숫자 비교에서는 `-0`과 `+0`을 구분할 필요가 거의 없다. +이런 상황에서는 `Object.is(-0, +0)`이 `false`라는 점이 오히려 낯설게 느껴질 수 있다. + +### 어떤 상황에서 `Object.is`를 사용할까? + +직접 서비스 코드에서 자주 사용할 일은 많지 않다. +다만 다음과 같은 상황에서는 유용한다. + +- `NaN`을 정확히 비교해야 할 때 +- `-0`과 `+0`을 구분해야 하는 수학적 계산이 필요할 때 +- React처럼 상태 변경 여부를 내부적으로 정밀하게 판단해야 할 때 + +--- + +## 4. React와 `Object.is` + +React는 상태 값이 이전 값과 같은지 비교할 때 내부적으로 `Object.is`와 유사한 비교 방식을 사용한다. + +```js +setCount(1); +setCount(1); +``` + +이미 `count`가 `1`인 상태에서 다시 `1`을 넣으면, React는 이전 값과 다음 값이 같다고 판단하여 불필요한 렌더링을 줄일 수 있다. + +### React의 얕은 비교 + +React에서 자주 나오는 개념 중 하나가 **얕은 비교(shallow comparison)**다. + +얕은 비교란 객체 내부의 모든 중첩 값을 깊게 비교하는 것이 아니라, **첫 번째 깊이에 존재하는 값만 비교하는 방식**이다. + +### React에서 `Object.is`와 얕은 비교의 관계 + +React가 `Object.is`와 얕은 비교 중 하나만 사용하는 것은 아니다. +상황에 따라 **값 하나를 비교할 때는 `Object.is`와 유사한 비교 방식을 사용하고, 객체의 props 변경 여부를 판단할 때는 `Object.is`를 기반으로 한 얕은 비교를 사용한다.** + +즉, 얕은 비교는 `Object.is`가 동작하지 않을 때 사용하는 대체 방식이 아니다. +객체의 1단계 key를 순회하면서, 각 key의 값을 `Object.is`로 비교하는 방식에 가깝다. + +```js +function shallowEqual(prevProps, nextProps) { + if (Object.is(prevProps, nextProps)) { + return true; + } + + if ( + typeof prevProps !== "object" || + prevProps === null || + typeof nextProps !== "object" || + nextProps === null + ) { + return false; + } + + const prevKeys = Object.keys(prevProps); + const nextKeys = Object.keys(nextProps); + + if (prevKeys.length !== nextKeys.length) { + return false; + } + + for (const key of prevKeys) { + if ( + !Object.prototype.hasOwnProperty.call(nextProps, key) || + !Object.is(prevProps[key], nextProps[key]) + ) { + return false; + } + } + + return true; +} +``` + +위 코드는 React 내부 코드를 그대로 옮긴 것은 아니지만, `Object.is`와 얕은 비교의 관계를 이해하기 좋은 예시다. + +React에서 `React.memo`나 `PureComponent`는 props 객체 전체를 깊게 비교하지 않는다. +대신 이전 props와 다음 props의 key를 비교하고, 각 prop 값이 같은지 `Object.is`와 유사한 방식으로 비교한다. + +```txt +Object.is +→ 값 하나와 값 하나를 비교한다. + +shallowEqual +→ 객체의 1단계 key를 순회하면서 + 각 key의 값을 Object.is로 비교한다. +``` + +```js +const prev = { + user: { + name: "Kim", + }, +}; + +const next = { + user: { + name: "Kim", + }, +}; + +prev === next; // false +prev.user === next.user; // false +``` + +두 객체의 내용은 같아 보이지만, 각각 새로 만들어진 객체이기 때문에 참조가 다르다. + +반대로 다음처럼 내부 객체를 그대로 공유하면 얕은 비교에서 같다고 판단될 수 있다. + +```js +const user = { + name: "Kim", +}; + +const prev = { + user, +}; + +const next = { + user, +}; + +prev === next; // false +prev.user === next.user; // true +``` + +### React에서 중요한 이유 + +React에서는 상태를 직접 변경하지 않고 새로운 객체를 만들어야 변경을 감지하기 쉽다. + +```js +// 좋지 않은 예시 +const nextUser = user; +nextUser.name = "Lee"; +setUser(nextUser); +``` + +위 코드는 같은 객체 참조를 다시 넣는 형태가 될 수 있다. +이 경우 React는 이전 상태와 다음 상태가 같다고 판단하여 변경을 감지하지 못할 수 있다. + +```js +// 좋은 예시 +setUser({ + ...user, + name: "Lee", +}); +``` + +이렇게 새로운 객체를 만들어 전달하면 React가 이전 상태와 다음 상태의 참조가 달라졌음을 알 수 있다. + +다만 새로운 객체를 매번 만들면 `React.memo`로 감싼 컴포넌트에서는 props가 매번 달라졌다고 판단될 수 있다. + +```jsx +const user = { + name: "Kim", +}; + +return ; +``` + +위 코드에서 부모 컴포넌트가 렌더링될 때마다 `user` 객체가 새로 만들어지면, `Profile` 입장에서는 이전 `user`와 다음 `user`의 참조가 다르다고 판단할 수 있다. +이런 경우에는 객체를 컴포넌트 밖으로 분리하거나, 필요한 상황에서 `useMemo`를 사용할 수 있다. + +```jsx +const user = useMemo(() => { + return { + name: "Kim", + }; +}, []); + +return ; +``` + +핵심은 상태 업데이트에서는 불변성을 지키기 위해 새 객체를 만들어야 하지만, memo 최적화에서는 불필요하게 새 객체를 계속 만들지 않도록 주의해야 한다는 점이다. + +### Polyfill이란? + +Polyfill은 최신 JavaScript 기능을 지원하지 않는 구형 환경에서도 해당 기능을 사용할 수 있도록 구현해주는 코드이다. + +예를 들어 구형 브라우저가 `Object.is`, `Promise`, `Map` 같은 기능을 지원하지 않는다면, Polyfill을 통해 비슷한 동작을 직접 구현할 수 있다. + +```js +if (!Object.is) { + Object.is = function (x, y) { + if (x === y) { + return x !== 0 || 1 / x === 1 / y; + } + + return x !== x && y !== y; + }; +} +``` + +--- + +## 5. 즉시 실행 함수 IIFE + +IIFE는 Immediately Invoked Function Expression의 약자이다. +함수를 정의하자마자 즉시 실행하는 함수 표현식이다. + +```js +(function () { + console.log("즉시 실행된다."); +})(); +``` + +화살표 함수로도 작성할 수 있다. + +```js +(() => { + console.log("즉시 실행된다."); +})(); +``` + +### IIFE의 특징 + +IIFE는 정의된 순간 한 번 실행된다. +일반적인 함수 선언문처럼 이름을 통해 다시 호출하는 용도로 사용하지 않는다. + +### 장점 + +IIFE의 가장 큰 장점은 **독립적인 스코프를 만들 수 있다**는 것이다. + +```js +(() => { + const message = "내부에서만 사용된다."; + console.log(message); +})(); + +console.log(message); // ReferenceError +``` + +이처럼 내부 변수는 외부에서 접근할 수 없기 때문에 글로벌 스코프 오염을 줄일 수 있다. + +또한 코드를 읽는 사람에게 “이 로직은 한 번만 실행되는 초기화 코드”라는 의도를 전달할 수 있다. + +--- + +## 6. 함수를 만들 때 기억하기 좋은 사항 + +### 6.1 함수의 부수 효과를 최대한 억제하라 + +부수 효과란 함수 내부의 실행 결과가 함수 외부에 영향을 주는 것을 의미한다. + +```js +let count = 0; + +function increase() { + count += 1; +} +``` + +위 함수는 외부 변수 `count`를 변경하므로 부수 효과가 있다. + +반대로 순수 함수는 같은 입력에 대해 항상 같은 결과를 반환하고, 외부 상태를 변경하지 않는다. + +```js +function add(a, b) { + return a + b; +} +``` + +웹 애플리케이션 개발에서 부수 효과를 완전히 없앨 수는 없다. +API 요청, localStorage 접근, DOM 조작, 로그 기록 등은 모두 부수 효과이다. + +중요한 것은 부수 효과를 없애는 것이 아니라, **부수 효과가 일어나는 위치를 명확히 하고 최소화하는 것**이다. + +React 관점에서는 `useEffect`를 남용하지 않고, 렌더링 중 계산 가능한 값은 렌더링 과정에서 계산하는 것이 좋다. + +```js +// 불필요한 useEffect 예시 +const [fullName, setFullName] = useState(""); + +useEffect(() => { + setFullName(`${firstName} ${lastName}`); +}, [firstName, lastName]); +``` + +위 코드는 굳이 상태와 Effect를 만들 필요가 없다. + +```js +// 더 단순한 방식 +const fullName = `${firstName} ${lastName}`; +``` + +### 6.2 가능한 함수를 작게 만들어라 + +함수가 길어질수록 한 함수가 너무 많은 일을 하고 있을 가능성이 높아집니다. +ESLint에는 `max-lines-per-function`이라는 규칙이 있으며, 함수의 최대 줄 수를 제한할 수 있다. + +```js +// .eslintrc 예시 +{ + "rules": { + "max-lines-per-function": ["warn", 50] + } +} +``` + +이 규칙의 목적은 단순히 줄 수를 줄이는 것이 아닙니다. +핵심은 **하나의 함수가 하나의 책임만 갖도록 만드는 것**이다. + +```js +// 좋지 않은 예시 +function handleSubmit() { + // 입력 검증 + // API 요청 + // 응답 처리 + // 토스트 표시 + // 페이지 이동 +} +``` + +위 함수는 너무 많은 일을 하고 있다. +검증, API 요청, 후처리 등을 별도 함수로 분리하면 읽기 쉽고 테스트하기 쉬워진다. + +```js +function validateForm(form) { + // 입력 검증 +} + +async function submitForm(form) { + // API 요청 +} + +function handleSuccess() { + // 성공 후 처리 +} +``` + +### 6.3 누구나 이해할 수 있는 이름을 붙여라 + +함수 이름은 함수가 무엇을 하는지 드러내야 한다. +이름이 명확하면 주석 없이도 코드의 의도를 파악하기 쉬워진다. + +--- + +## 7. 클래스 + +클래스는 아직 이해가 어려워서 일단 생략합니다.. + +--- + +## 8. 클로저 + +클로저는 **함수와 함수가 선언된 어휘적 환경의 조합**이다. + +조금 더 쉽게 말하면, 함수가 생성될 당시의 외부 변수를 기억하고, 함수 실행 이후에도 그 변수에 접근할 수 있는 성질이다. + +```js +function createCounter() { + let count = 0; + + return function increase() { + count += 1; + return count; + }; +} + +const counter = createCounter(); + +console.log(counter()); // 1 +console.log(counter()); // 2 +console.log(counter()); // 3 +``` + +`createCounter` 함수의 실행은 끝났지만, 반환된 `increase` 함수는 여전히 `count`를 기억하고 있다. +이것이 클로저이다. + +### React에서의 클로저 예시 + +React에서 대표적으로 클로저를 느낄 수 있는 예시는 이벤트 핸들러이다. + +```jsx +function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + setCount(count + 1); + } + + return ; +} +``` + +`handleClick` 함수는 자신이 만들어질 당시의 `count` 값을 기억한다. + +이 특성 때문에 비동기 코드에서는 오래된 값을 참조하는 문제가 생길 수 있다. + +```jsx +function Counter() { + const [count, setCount] = useState(0); + + function handleClick() { + setTimeout(() => { + console.log(count); + }, 1000); + } + + return ( + <> + + + + ); +} +``` + +`setTimeout` 안의 콜백은 클릭 당시의 `count`를 기억한다. +이후 상태가 바뀌더라도 해당 콜백이 기억하는 값은 그대로일 수 있다. + +이럴 때는 함수형 업데이트를 사용할 수 있다. + +```jsx +setCount((prev) => prev + 1); +``` + +### 클로저와 메모리 + +클로저는 외부 변수를 기억하기 때문에 메모리를 사용한다. +필요한 값만 클로저에 남기고, 더 이상 필요 없는 큰 객체나 DOM 참조를 오래 붙잡지 않도록 주의해야 한다. + +--- + +## 9. 이벤트 루프와 비동기 통신의 이해 + +JavaScript는 기본적으로 싱글 스레드에서 실행된다. +즉, 한 번에 하나의 작업만 호출 스택에서 처리할 수 있다. + +하지만 브라우저 환경에서는 `setTimeout`, 이벤트 처리, 네트워크 요청, Promise 같은 비동기 작업이 자연스럽게 동작한다. +이것을 이해하려면 이벤트 루프, 호출 스택, 태스크 큐, 마이크로태스크 큐를 알아야 한다. + +### 프로세스와 스레드 + +프로세스는 실행 중인 프로그램의 단위이다. +스레드는 프로세스 안에서 실제 작업을 수행하는 실행 흐름이다. + +하나의 프로세스 안에는 여러 스레드가 존재할 수 있다. +브라우저는 여러 기능을 처리하기 위해 다양한 스레드를 사용하지만, JavaScript 코드를 실행하는 메인 스레드는 기본적으로 한 번에 하나의 작업을 처리한다. + +### 호출 스택 + +호출 스택은 현재 실행 중인 함수들이 쌓이는 공간이다. + +```js +function bar() { + console.log("bar"); +} + +function baz() { + console.log("baz"); +} + +function foo() { + console.log("foo"); + baz(); + bar(); +} + +foo(); +``` + +실행 결과는 다음과 같다. + +```txt +foo +baz +bar +``` + +함수가 호출되면 호출 스택에 쌓이고, 실행이 끝나면 스택에서 제거된다. + +### `setTimeout` 예시 + +```js +console.log(1); + +setTimeout(() => { + console.log(2); +}, 0); + +setTimeout(() => { + console.log(3); +}, 100); + +console.log(4); +``` + +실행 결과는 다음과 같다. + +```txt +1 +4 +2 +3 +``` + +`setTimeout(..., 0)`이라고 해서 즉시 실행되는 것은 아니다. +콜백 함수가 태스크 큐에 들어가고, 호출 스택이 비었을 때 이벤트 루프에 의해 실행된다. + +따라서 `console.log(4)`까지 먼저 실행된 후, 태스크 큐에 있던 `console.log(2)`가 실행된다. + +### 올바른 `setTimeout` 사용 예시 + +아래 코드처럼 콜백 함수를 전달해야 한다. + +```js +function bar() { + console.log("bar"); +} + +function baz() { + console.log("baz"); +} + +function foo() { + console.log("foo"); + setTimeout(bar, 0); + baz(); +} + +foo(); +``` + +실행 결과는 다음과 같다. + +```txt +foo +baz +bar +``` + +주의할 점은 `setTimeout(bar(), 0)`처럼 쓰면 `bar` 함수를 나중에 실행하는 것이 아니라, 지금 즉시 실행한 결과를 `setTimeout`에 넘기게 된다. + +```js +setTimeout(bar, 0); // 올바른 형태 +setTimeout(bar(), 0); // bar가 즉시 실행됨 +``` + +### 태스크 큐와 마이크로태스크 큐 + +태스크 큐에는 `setTimeout`, `setInterval`, 일부 이벤트 콜백 등이 들어간다. +마이크로태스크 큐에는 대표적으로 `Promise.then`, `queueMicrotask` 등이 들어간다. + +마이크로태스크 큐는 태스크 큐보다 우선순위가 높다. + +```js +console.log("start"); + +setTimeout(() => { + console.log("timeout"); +}, 0); + +Promise.resolve().then(() => { + console.log("promise"); +}); + +console.log("end"); +``` + +실행 결과는 다음과 같다. + +```txt +start +end +promise +timeout +``` + +### `setTimeout`의 지연 시간은 최소 대기 시간이다 + +`setTimeout`에 설정한 시간은 정확한 실행 시간이 아니라 최소 대기 시간이다. + +```js +setTimeout(() => { + console.log("실행"); +}, 0); +``` + +위 코드는 “0초 뒤에 반드시 실행”이 아니라, “가능한 한 빨리 실행할 수 있도록 태스크 큐에 넣는다”에 가깝다. +호출 스택이 비어야 실행될 수 있으므로, 메인 스레드가 바쁘면 실제 실행은 더 늦어질 수 있다. + +### 렌더링은 언제 실행될까? + +브라우저는 보통 하나의 태스크가 끝나고, 마이크로태스크 큐가 모두 비워진 뒤 렌더링할 기회를 얻는다. +따라서 마이크로태스크가 너무 많이 쌓이면 렌더링이 지연될 수 있다. + +```js +Promise.resolve().then(() => { + // 마이크로태스크 +}); +``` + +마이크로태스크는 태스크보다 먼저 실행되므로, 긴 마이크로태스크 체인은 화면 업데이트를 늦출 수 있다. + +### 참고 자료 + +- [이벤트 루프와 태스크 큐 정리](https://blaxsior-repository.tistory.com/169) + +--- + +## 10. 구조 분해 할당 + +### 배열 구조 분해 할당 + +배열 구조 분해 할당은 배열의 값을 순서대로 꺼내 변수에 할당하는 문법이다. + +```js +const numbers = [1, 2]; + +const [first, second] = numbers; + +console.log(first); // 1 +console.log(second); // 2 +``` + +React의 `useState`도 배열 구조 분해 할당을 자주 사용한다. + +```jsx +const [count, setCount] = useState(0); +``` + +`useState`는 2개의 값을 가진 배열을 반환한다. + +1. 현재 상태 값 +2. 상태를 변경하는 함수 + +### `useState`가 객체가 아니라 배열을 반환하는 이유 + +배열 구조 분해 할당은 사용하는 쪽에서 변수 이름을 자유롭게 정할 수 있다. + +```jsx +const [count, setCount] = useState(0); +const [name, setName] = useState(""); +const [isOpen, setIsOpen] = useState(false); +``` + +만약 객체를 반환했다면 이름을 바꾸기 위해 별칭을 사용해야 한다. + +```jsx +const { value: count, setter: setCount } = useStateLikeObject(0); +``` + +배열은 순서만 맞추면 되기 때문에, Hook처럼 정해진 개수의 값을 반환하는 경우에 사용하기 좋다. + +### 기본값 설정 + +배열 구조 분해 할당에서는 기본값을 설정할 수 있다. + +```js +const numbers = [1]; + +const [first, second = 2] = numbers; + +console.log(first); // 1 +console.log(second); // 2 +``` + +배열에 해당 위치의 값이 없거나 `undefined`라면 기본값이 사용된다. + +```js +const [a = 10] = [undefined]; + +console.log(a); // 10 +``` + +### 객체 구조 분해 할당 + +객체 구조 분해 할당은 프로퍼티 이름을 기준으로 값을 꺼낸다. + +```js +const user = { + name: "Kim", + age: 20, +}; + +const { name, age } = user; + +console.log(name); // "Kim" +console.log(age); // 20 +``` + +이름을 바꾸고 싶다면 별칭을 사용할 수 있다. + +```js +const { name: userName } = user; + +console.log(userName); // "Kim" +``` + +### Babel과 트랜스파일 + +구조 분해 할당은 비교적 최신 JavaScript 문법이다. +구형 브라우저까지 지원해야 한다면 Babel 같은 도구를 통해 오래된 JavaScript 문법으로 변환할 수 있다. + +객체 구조 분해 할당, 전개 구문 등은 트랜스파일 결과가 상대적으로 길어질 수 있다. +대부분의 현대 프론트엔드 개발에서는 크게 문제 되지 않지만, 라이브러리 개발이나 번들 크기에 민감한 환경에서는 고려할 수 있다. + +### 전개 구문에서 순서가 중요한 이유 + +객체 전개 구문에서는 뒤에 오는 값이 앞의 값을 덮어쓰인다. + +```js +const user = { + name: "Kim", + age: 20, +}; + +const updatedUser = { + ...user, + age: 21, +}; + +console.log(updatedUser); // { name: "Kim", age: 21 } +``` + +반대로 순서를 바꾸면 전개 구문의 값이 다시 덮어쓰인다. + +```js +const updatedUser = { + age: 21, + ...user, +}; + +console.log(updatedUser); // { age: 20, name: "Kim" } +``` + +따라서 기존 값을 덮어쓸 것인지, 기존 값을 유지할 것인지에 따라 순서를 신경 써야 한다. + +React 상태 업데이트에서도 자주 사용된다. + +```jsx +setUser({ + ...user, + name: "Lee", +}); +``` + +--- + +## 11. `forEach` + +`forEach`는 배열의 각 요소를 순회하면서 콜백 함수를 실행하는 메서드이다. + +```js +const numbers = [1, 2, 3]; + +numbers.forEach((number) => { + console.log(number); +}); +``` + +실행 결과는 다음과 같다. + +```txt +1 +2 +3 +``` + +### `forEach`의 반환 값 + +`forEach`의 반환 값은 항상 `undefined`이다. + +```js +const numbers = [1, 2, 3]; + +const result = numbers.forEach((number) => { + return number * 2; +}); + +console.log(result); // undefined +``` + +새로운 배열이 필요하다면 `map`을 사용해야 한다. + +```js +const doubled = numbers.map((number) => number * 2); + +console.log(doubled); // [2, 4, 6] +``` + +### `forEach`는 중간에 멈추기 어렵다 + +`forEach`는 `break`나 `return`으로 순회를 중단할 수 없다. + +```js +const numbers = [1, 2, 3, 4, 5]; + +numbers.forEach((number) => { + if (number === 3) { + return; + } + + console.log(number); +}); +``` + +위 코드의 `return`은 현재 콜백만 종료한다. +전체 `forEach` 반복이 멈추는 것은 아니다. + +중간에 멈춰야 한다면 `for...of`, `some`, `every`, `find` 등을 사용하는 것이 좋다. + +```js +for (const number of numbers) { + if (number === 3) { + break; + } + + console.log(number); +} +``` + +또는 조건에 맞는 값을 찾고 싶다면 `find`를 사용할 수 있다. + +```js +const target = numbers.find((number) => number === 3); + +console.log(target); // 3 +``` + +--- + +## 12. JSX에서 삼항 연산자 말고 조건부 렌더링하기 + +JSX에서는 삼항 연산자 외에도 여러 방식으로 조건부 렌더링을 구현할 수 있다. + +### `&&` 연산자 사용 + +```jsx +function UserMessage({ isLogin }) { + return
{isLogin &&

환영한다.

}
; +} +``` + +조건이 `true`일 때만 오른쪽 JSX가 렌더링된다. + +### 함수를 사용한 조건부 렌더링 + +```jsx +function renderMessage(status) { + if (status === "loading") { + return

로딩 중이다.

; + } + + if (status === "error") { + return

에러가 발생했습니다.

; + } + + return

완료되었습니다.

; +} + +function Page({ status }) { + return
{renderMessage(status)}
; +} +``` + +조건이 많아질수록 JSX 안에 모든 조건을 넣기보다, 별도 함수나 컴포넌트로 분리하는 것이 더 읽기 좋다. + +### 객체 매핑 사용 + +```jsx +const statusMessage = { + loading:

로딩 중이다.

, + error:

에러가 발생했습니다.

, + success:

완료되었습니다.

, +}; + +function Page({ status }) { + return
{statusMessage[status]}
; +} +``` + +가능은 하지만, 조건이 복잡해지면 오히려 가독성이 떨어질 수 있다. +간단한 조건은 삼항 연산자나 `&&`를 사용하고, 복잡한 조건은 함수나 컴포넌트로 분리하는 것이 좋다. + +--- + +## 13. TypeScript: `any` 대신 `unknown` 사용하기 + +`unknown`은 TypeScript의 top type이다. +즉, 어떤 값이든 `unknown` 타입 변수에 할당할 수 있다. + +```ts +let value: unknown; + +value = 1; +value = "hello"; +value = true; +value = {}; +``` + +하지만 `unknown` 타입의 값은 바로 사용할 수 없다. +사용하기 전에 타입을 좁혀야 한다. + +### `any`의 문제점 + +`any`는 타입 검사를 사실상 포기하는 타입이다. + +```ts +function doSomething(callback: any) { + callback(); +} + +doSomething("hello"); +``` + +TypeScript는 위 코드를 에러로 판단하지 않는다. +하지만 실행 시점에는 `"hello"`는 함수가 아니기 때문에 에러가 발생한다. + +### `unknown`을 사용하면? + +```ts +function doSomething(callback: unknown) { + callback(); + // Error: 'callback' is of type 'unknown' +} +``` + +`unknown`은 아직 어떤 타입인지 알 수 없기 때문에 바로 호출할 수 없다. +이 값을 사용하려면 타입 검사를 먼저 해야 한다. + +```ts +function doSomething(callback: unknown) { + if (typeof callback === "function") { + callback(); + return; + } + + throw new Error("callback은 함수여야 한다."); +} +``` + +이렇게 하면 예상치 못한 타입이 들어와도 안전하게 처리할 수 있다. + +### `unknown`을 사용하면 좋은 상황 + +외부에서 들어오는 값은 타입을 확신하기 어렵다. + +- API 응답 +- `JSON.parse` 결과 +- 사용자 입력 +- 외부 라이브러리에서 받은 값 +- `catch`문의 에러 값 + +이런 값은 처음에는 `unknown`으로 받아두고, 타입을 좁힌 뒤 사용하는 것이 안전한다. + +```ts +try { + throw new Error("에러 발생"); +} catch (error: unknown) { + if (error instanceof Error) { + console.log(error.message); + } +} +``` + +### 정리 + +`any`는 TypeScript의 타입 안정성을 무너뜨릴 수 있다. +반면 `unknown`은 값을 바로 사용하지 못하게 막고, 타입 검사를 강제한다. +따라서 타입을 확신할 수 없는 값에는 `any`보다 `unknown`을 사용하는 것이 좋다. + +--- From c55e0d520a34caaa6bd4b860819b74c4b349000e Mon Sep 17 00:00:00 2001 From: Minhyeok Kim Date: Wed, 6 May 2026 19:51:58 +0900 Subject: [PATCH 2/3] =?UTF-8?q?1=EC=A3=BC=EC=B0=A8:=20=EB=8F=99=EB=93=B1?= =?UTF-8?q?=20=EB=B9=84=EA=B5=90=20=EA=B4=80=EB=A0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\352\271\200\353\257\274\355\230\201.md" | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git "a/week-01/\352\271\200\353\257\274\355\230\201.md" "b/week-01/\352\271\200\353\257\274\355\230\201.md" index c5b466d..2fb0dce 100644 --- "a/week-01/\352\271\200\353\257\274\355\230\201.md" +++ "b/week-01/\352\271\200\353\257\274\355\230\201.md" @@ -2,7 +2,7 @@ ### 동등 연산자란? -동등 연산자 `==`는 두 값의 **타입이 다르더라도 암묵적 타입 변환을 한 뒤 값이 같으면 `true`를 반환**한다. +동등 연산자 `==`는 두 값의 **타입이 다르더라도 값이 같으면 `true`를 반환**한다. ```js 6 == "6"; // true @@ -29,7 +29,44 @@ const pageFromState = 5; pageFromQuery == pageFromState; // true ``` -이 경우 `==`를 사용하면 동작은 할 수 있다. 하지만 권장되지는 않는다. +코드 상에서 보통 숫자는 Number 형식으로 처리될 것이다. 하지만, URL 쿼리스트링은 문자열 형식의 숫자값이다. +이는 서로 타입은 다르지만, '=='로 비교하였을 때 같은 값으로 인식된다. + +하지만 이러한 비교는## 1. 동등 연산자(`==`)와 일치 연산자(`===`) + +### 동등 연산자란? + +동등 연산자 `==`는 두 값의 **타입이 다르더라도 값이 같으면 `true`를 반환**한다. + +```js +6 == "6"; // true +0 == ""; // true +false == 0; // true +null == undefined; // true +``` + +반면 일치 연산자 `===`는 **타입과 값이 모두 같아야 `true`를 반환**한다. + +```js +6 === "6"; // false +6 === 6; // true +``` + +### 쿼리스트링 값 비교에 `==`를 사용해도 될까? + +예를 들어 URL 쿼리스트링으로 받은 값은 보통 문자열이다. + +```js +const pageFromQuery = "5"; +const pageFromState = 5; + +pageFromQuery == pageFromState; // true +``` + +코드 상에서 보통 숫자는 Number 형식으로 처리될 것이다. 하지만, URL 쿼리스트링은 문자열 형식의 숫자값이다. +이는 서로 타입은 다르지만, '=='로 비교하였을 때 같은 값으로 인식된다. + +하지만 이러한 비교는 권장되지는 않는다. 이유는 `==`가 자동 타입 변환을 수행하기 때문에, 의도하지 않은 비교 결과가 나올 수 있기 때문이다. ```js @@ -38,6 +75,8 @@ pageFromQuery == pageFromState; // true null == undefined; // true ``` +- 자세한 자료: [MDN - 동등 비교 및 동일성](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Equality_comparisons_and_sameness) + 따라서 쿼리스트링 값은 명시적으로 변환한 뒤 `===`로 비교하는 것이 더 안전한다. ```js From 58f8409e90129c0342b0465c99a54d086cb8c8e6 Mon Sep 17 00:00:00 2001 From: Minhyeok Kim Date: Wed, 6 May 2026 20:10:16 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EB=8F=99=EB=93=B1=20=EC=97=B0=EC=82=B0?= =?UTF-8?q?=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\352\271\200\353\257\274\355\230\201.md" | 44 +++---------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git "a/week-01/\352\271\200\353\257\274\355\230\201.md" "b/week-01/\352\271\200\353\257\274\355\230\201.md" index 2fb0dce..b664885 100644 --- "a/week-01/\352\271\200\353\257\274\355\230\201.md" +++ "b/week-01/\352\271\200\353\257\274\355\230\201.md" @@ -32,40 +32,6 @@ pageFromQuery == pageFromState; // true 코드 상에서 보통 숫자는 Number 형식으로 처리될 것이다. 하지만, URL 쿼리스트링은 문자열 형식의 숫자값이다. 이는 서로 타입은 다르지만, '=='로 비교하였을 때 같은 값으로 인식된다. -하지만 이러한 비교는## 1. 동등 연산자(`==`)와 일치 연산자(`===`) - -### 동등 연산자란? - -동등 연산자 `==`는 두 값의 **타입이 다르더라도 값이 같으면 `true`를 반환**한다. - -```js -6 == "6"; // true -0 == ""; // true -false == 0; // true -null == undefined; // true -``` - -반면 일치 연산자 `===`는 **타입과 값이 모두 같아야 `true`를 반환**한다. - -```js -6 === "6"; // false -6 === 6; // true -``` - -### 쿼리스트링 값 비교에 `==`를 사용해도 될까? - -예를 들어 URL 쿼리스트링으로 받은 값은 보통 문자열이다. - -```js -const pageFromQuery = "5"; -const pageFromState = 5; - -pageFromQuery == pageFromState; // true -``` - -코드 상에서 보통 숫자는 Number 형식으로 처리될 것이다. 하지만, URL 쿼리스트링은 문자열 형식의 숫자값이다. -이는 서로 타입은 다르지만, '=='로 비교하였을 때 같은 값으로 인식된다. - 하지만 이러한 비교는 권장되지는 않는다. 이유는 `==`가 자동 타입 변환을 수행하기 때문에, 의도하지 않은 비교 결과가 나올 수 있기 때문이다. @@ -99,7 +65,7 @@ JavaScript에서 문자열은 원시 타입이다. 그리고 원시 타입의 값은 **변경 불가능한 값**, 즉 immutable value이다. ```js -let text = "hello"; +const text = "hello"; text[0] = "H"; @@ -113,15 +79,15 @@ console.log(text); // "hello" 문자열 자체를 바꾸는 것이 아니라, **새로운 문자열을 만들어 변수에 다시 할당**한다. ```js -let text = "hello"; +const text = "hello"; -text = "H" + text.slice(1); +const newText = "H" + text.slice(1); -console.log(text); // "Hello" +console.log(newText); // "Hello" ``` 여기서 기존 문자열 `"hello"`가 직접 수정된 것이 아니다. -`"H" + text.slice(1)`의 결과로 `"Hello"`라는 새로운 문자열이 만들어지고, `text`가 그 값을 다시 가리키게 된다. +`"H" + text.slice(1)`의 결과로 `"Hello"`라는 새로운 문자열이 만들어지고, `newText`에 새로운 값을 할당해야 한다. ### `slice`와 `substring`