준영
자바스크립트의 동등 비교
값 저장 방식의 차이
원시 타입
의 경우, 불변 형태의 값으로 저장된다.
새로운 값을 할당할 경우, 새로운 메모리에 재할당된 값이 저장되고 변수가 이를 가리키게 된다.
원시 타입은 변수를 통해 값에 바로 접근이 가능하다.
하지만 객체 타입
의 경우, 속성의 추가, 삭제, 수정과 같이 변경 가능한 형태로 저장된다.
객체 타입은 변수를 통해 참조에 접근이 가능하고, 참조를 통해 실제 값이 저장된 메모리에 도달해야 한다.
Object.is
vs==
vs===
객체 비교시 Object.is
와 ===
사이에 드라미틱한 차이는 없다.
Object.is
는 ES6에서 추가되었다.
==
(Equal Operator)- 비교할 양쪽이 같은 타입이 아니라면 비교할 수 있도록 형변환 후 비교
===
(Strict Equal Operator)- 타입이 다를 경우 즉시 false
- 어느 한쪽에라도 NaN이 존재하면 false
- +0과 -0은 같다고 판단
Object.is
- 파라미터로 받는 두 인자가 동일한지 확인하는 메서드
- +0과 -0은 다르다고 판단
- NaN 비교 가능
리액트 컴포넌트의 렌더링이 일어나는 이유 중 하나는
props의 동등 비교
에 따른 결과다.
props 비교는 객체의 얕은 비교를 기반으로 이뤄진다.
기본적으로는 Object.is
를 사용하며, ES6를 지원하지 않는 브라우저를 위해 폴리필을 함께 사용한다.
Object.is
를 통해 먼저 비교 후, 앞의 과정에서 수행하지 못한 비교를 위해 객체의 속성을 모두 꺼내 값이 같은지 확인한다.
얕은 비교란, 객체의 첫 번째 깊이만을 비교한다는 것을 의미한다.
props에 또 다른 객체를 넘겨준다면 렌더링이 예상치 못하게 작동할 수 있기 때문이다.
추가적으로 만약 객체를 완벽하게 비교하기 위해 재귀적으로 비교를 수행한다면 성능에 악영향을 끼칠 것이다.
✅ 리렌더링이 발생하는 조건
- props의 변경
- 상태의 변경
- 부모 컴포넌트의 리렌더링
- key의 변화
함수
- “작업을 수행하는 실행 단위”
함수 정의 방법
-
함수 선언문
function functionName(parameter) { /* ... */ }
-
함수 표현식
const functionName = function (parameter) { /* ... */ };
-
Function 생성자
- 다소 생소한 방식이며, 함수 전체를 텍스트로 작성해야 하므로 매우 불편함
- 클로저가 생성되지 않는다.
const functionName = new Function('parameter', '/* ... */');
-
화살표 함수
- ES6에 추가됨
const functionName = (parameter) => { /* ... */ };
“함수 선언문”과 “함수 표현식”의 차이
“호이스팅”에 있어 차이가 있다.
함수 선언문의 경우, 호이스팅이 일어나 함수 선언 실행 전에 미리 메모리에 등록하는 작업이 진행된다.
따라서 함수 선언 위치보다 앞선 위치에서 함수를 호출하여도 정상 실행된다.
함수 표현식의 경우, 호이스팅이 일어나지만 변수에 함수를 담기 때문에 undefined로 초기화되거나, 초기화 단계가 미리 이루어지지 않기 때문에 함수 표현식의 위치보다 앞선 위치에서 실행할 경우 오류가 발생한다.
화살표 함수와 다른 함수 생성 방식과의 차이
- 화살표 함수는 constructor를 사용할 수 없다.
- 화살표 함수에는
arguments
가 존재하지 않는다. - 화살표 함수는 함수 자체의 바인딩을 갖지 않아 내부에서
this
참조시 상위 스코프의 this를 그대로 따른다.
즉시 실행 함수 (Immediately Invoked Function Expression, IIFE)
함수를 정의하고, 그 즉시 실행되는 함수이다.
따라서 한 번만 호출될 뿐, 다시 호출할 수 없다.
일반적으로 실행 함수에 이름을 붙이지 않는다.
즉시 실행함수의 장점은 독립적인 함수 스코프를 운용할 수 있다는 것이다.
함수 선언과 실행이 바로 끝나기 때문에 함수 내부에 정의된 값은 해당 함수 외에는 접근이 불가능하다.
또한, 코드를 읽는 사람으로 하여금 해당 함수가 다시 호출되지 않음을 각인시켜줄 수 있다.
고차 함수
자바스크립트의 함수가 일급 객체라는 특징을 활용해, 함수를 인수로 받거나 결과로 새로운 함수를 반환시킬 수 있다.
이러한 역할을 하는 함수가 고차함수
이다.
이런 특징을 활용해 함수 컴포넌트를 파라미터로 받아 새로운 함수 컴포넌트를 반환하는 고차 컴포넌트(HOC)
로 활용할 수 있다.
함수를 만들 때 주의해야할 사항
- 부수효과는 보통 불가피하다~!
- 따라서 최대한 부수효과를 최소화하자!
- useEffect 사용을 최소화!
- 따라서 최대한 부수효과를 최소화하자!
→ 항상 결과가 동일하여 예측 가능하고 안정적이라는 장점
- 가능한 작게 만들자
- 코드 길이가 길어질수록 어떤 일을 하는지 추적이 어려워지고 리팩토링이 어려워진다.
- 재사용성을 높이자
- 누구나 이해할 수 있는 이름을 붙일 수 있도록 고민하자
클래스
클래스
- 특정한 형태의 객체를 반복적으로 만들기 위한 템플릿
- 클래스는 자바스크립트의
프로토타입
으로 작동한다.
클로저
스코프
- 전역 스코프
- 말그대로 전역에서 접근 가능한 스코프
- 해당 스코프에서 변수를 선언할 경우, 전역 객체에 바인딩
- 브라우저 - window
- Node.js - global
- 함수 스코프
- 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.
- var의 경우, 선언된 외부에서도 접근이 가능하다.
- 이름이 같은 var로 선언된 변수의 경우, 가장 가까운 스코프에서부터 찾아나간다.
- 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.
- 블록 스코프
- var와 달리 let, const는 블록 레벨 스코프를 따른다.
- 이 생성될 때마다 새로운 스코프가 형성됨을 의미한다.
함수가 선언된 어휘적 환경
함수 선언에 포함된 다른 변수나 함수들이 함수가 실행됨에 따라 실행 컨텍스트에 등록되고, return문을 통해 반환된 함수는 참조가 유지되어 가비지 컬렉션되지 않고 메모리에 남아있게 된다.
이때 반환된 함수를 클로저라고 부른다.
위 설명에서 함수가 선언되었을때 함수 내용에 포함된 다른 변수나 함수들을 “함수가 선언된 어휘적 환경”이라고 해석하였다.
위를 통해 얻을 수 있는 장점?
만약 변수가 전역 레벨에 선언되어 있다면 누구나 수정할 수 있다는 문제점이 있다.
하지만, 클로저 내부에 존재하는 변수들은 외부에서 조작할 수 있는 방법이 없다. → 캡슐화
따라서 리액트가 관리하는 내부 상태 값은 리액트가 별도로 관리하는 클로저 내부에서만 접근할 수 있다.
대표적인 예시는 useState
다.
useState 내부는 클로저가 활용되었다.
외부 함수인 useState
가 반환한 내부 함수인 setState
는 외부 함수가 종료되었음에도 외부 함수가 선언된 환경을 기억하기 때문에 state
값에 접근할 수 있는 것이다.
그럼 단점은?
클로저 자체는 굉장히 어렵고 쉽지 않은 개념이다.
가장 주의할 점은 클로저는 생성될 때마다 선언적 환경을 기억해야 한다.
따라서 이를 위한 메모리 공간을 필요로 한다.
이는 성능에도 영향을 미칠 수 있기 때문에 주의가 필요하다.
이벤트 루프와 비동기 통신의 이해
프로세스
- 프로그램이 메모리에 올라가 실행된 작업 단위
스레드
- 하나의 프로세스 내에서 동시에 여러 작업을 수행하기 위해 실행되는 작은 작업 단위
- 각 스레드는 스택을 제외한 나머지 메모리 공간을 공유한다.
왜 싱글 스레드로 구현되었을까?
- 내부적으로 처리가 복잡하다.
- 동시에 서로 같은 자원에 접근하여 Race Condition이 발생할 수 있다.
- 또한 하나의 스레드에서 문제가 발생했을때 자원을 공유하는 다른 스레드에도 문제가 발생할 수 있다.
- 예를 들어, 여러 스레드가 하나의 DOM을 조작한다면…. 타이밍 이슈와 같은 문제들이 발생할 수 있고, 이를 동기화하는데에는 큰 비용이 들 것임…
- 따라서 Call Stack이 하나인 싱글 스레드 형태로 구현되었고, 이 한계를 극복하기 위해 비동기 처리를 수행한다.
동기
- 한 번에 하나씩 순서대로 작업을 처리한다. (직렬 방식)
- 하나의 큰 작업이 수행될 경우, 블로킹이 발생한다.
비동기
- 요청한 즉시 결과가 주어지지 않을 수도 있고 응답이 언제 올지 알 수 없지만, 여러 작업을 동시에 수행할 수 있다. (병렬 방식)
이벤트 루프
- 자바스크립트의 비동기 실행을 돕기 위해 만들어진 장치
- 이벤트 루프의 역할은 다음과 같다.
- Call Stack이 비어있는지 확인
- 태스크 큐에 대기중인 함수들이 존재하는지 확인하고 꺼내와 실행시킴
비동기 함수는 누가 실행할까?
- 자바스크립트 메인 스레드가 아닌, 태스크큐가 할당되는 별도의 스레드에서 수행된다.
- 브라우저
- Node.js
- 즉, 코드 실행 자체는 메인 스레드에서 이루어지지만, 외부 Web API 등은 모두 자바스크립트 코드 외부에서 실행되고 콜백이 태스크 큐로 들어간다.
- 이렇게 태스크 큐로 들어간 콜백은 이벤트 루프가 Call Stack이 비어있는지 확인후 가져와 실행하는 것이다.
태스크 큐와 마이크로 태스크 큐
- 마이크로 태스크 큐는 태스크 큐와 다른 태스크를 처리하는 또다른 큐이다.
- 마이크로 테스크 큐는 태스크 큐보다 우선권을 갖는다.
- 대표적으로는 Promise를 처리한다.
렌더링은 언제 일어날까?
- 태스크 큐의 작업들을 실행하기 전에, 마이크로 태스크 큐에 쌓인 작업을 먼저 실행한다.
- 그리고 태스크 큐의 작업을 실행하기 전, 렌더링이 일어난다.
- 따라서 마이크로 태스크 큐의 작업이 끝날 때마다 한번씩 렌더링을 할 기회를 얻게 된다.
리액트에서 자주 사용하는 자바스크립트 문법
바벨 (Babel)
- 자바스크립트의 최신 문법을 다양한 브라우저에서도 일관적으로 지원할 수 있도록 코드를 트랜스파일한다.
- 트랜스파일 vs 컴파일
- 트랜스파일 : 언어를 다른 언어로 바꿔주는 것
- JavaScript → 바이트 코드
- TypeScript → JavaScript
- 컴파일 : 언어를 기계가 알 수 있게끔 로우 레벨 언어로 바꿔준 개념입니다. → C언어
- 트랜스파일 : 언어를 다른 언어로 바꿔주는 것
구조 분해 할당
- 배열 또는 객체의 값을 말 그대로 분해해 개별 변수에 즉시 할당하는 것이다.
- 배열 구조 분해 할당은 ES6, 객체 구조 분해 할당은 그 이후 ECMA 2018에 추가되었다.
useState는 왜 객체 구조 분해 할당이 아닌 배열 구조 분해 할당을 사용했을까?
- 추측이지만, 배열 구조 분해 할당은 구조 분해 할당으로 변수에 이름을 지정하는 것이 자유롭지만, 객체 구조 분해 할당은 그렇지 않다. (
속성: 변수 이름
형태로 수정해야 해서 번거로움)
전개(spread) 연산자 (
…
)
- 마찬가지로 배열은 ES6, 객체는 그 이후 ECMA 2018에 추가되었다.
객체의 구조 분해 할당과 스프레드 연산자
- 두 경우 모두. 트랜스파일 이후 번들링 크기가 상대적으로 커지기 때문에 잘 검토하여 사용할 필요가 있다.
- 객체의 속성을 모두 가져와 변수와 매칭하는 작업에서 코드가 매우 커진다.
배열 고차함수
- map
- filter
- reduce
- forEach
삼항 조건 연산자
- 조건부 렌더링을 위해 많이 쓰인다.
- 하지만 겹쳐 쓸 경우, 결과 예측이 어려울 수 있어 중첩해서 사용하지 않는 편이 좋다.
선택이 아닌 필수, 타입스크립트
unknown
unknown
은 어떠한 값이든 할당할 수 있는 top type이다.- 하지만
any
와 다르게, 타입을 좁혀야 사용이 가능하다. - 반대되는 bottom type으로는 어떠한 것도 할당할 수 없는 never가 있다.
타입 가드
-
타입을 사용할 때는 최대한 타입을 좁혀 사용하는 것이 좋다.
-
이를 도와주는 것이
타입 가드
이다. -
타입 가드는 다음과 같이 사용할 수 있다.
-
instanceof
-
지정한 인스턴스가 특정 클래스의 인스턴스인지 확인할 수 있다.
class Error { /* ... */ } if (e instanceof Error) { /* do something */ }
-
-
typeof
-
특정 요소에 대해 자료형을 확인하는데 사용할 수 있다.
if (typeof (value === 'number')) { /* do something */ }
-
-
in
-
어떤 객체에 키가 존재하는지 확인하는 용도로 사용된다.
-
property in object
interface Student { age: number; score: number; } function doStudy(person: Student) { if ('age' in person) { /* do something */ } }
-
-
제네릭
- 다양한 타입에 대응할 수 있도록 도와주는 도구
- 제네릭을 통해 기본값을 선언해 준다면 undefined로 값을 추론해버리는 문제를 방지할 수 있다.
인덱스 시그니처
- 객체의 키를 정의하는 방식
- 키에 원하는 타입을 부여할 수 있다.
도은
1.1 자바스크립트의 동등 비교
- 의존성 배열
- 보통은 리액트에서 제공하는 eslint-react-config로 자동 채우기
- 👆🏼 실제로 이것이 어떤 식으로 동작하는지, 또한 왜 이런 변수들을 넣어야 하는지 이해했는가? (아니요..😨)
- 렌더링
- 리액트 컴포넌트의 렌더링이 일어나는 이유:
props
의 동등 비교에 따른 결과 - props의 동등 비교 → 얕은 비교 기반
- 리액트 컴포넌트의 렌더링이 일어나는 이유:
- 리액트에서 많은 작업들은 동등 비교 기반으로 동작
- 가상 DOM과 실제 DOM 비교
- 리렌더링 판단
- 변수, 함수의 메모이제이션 등
1.1.1 자바스크립트의 데이터 타입
-
자바스크립트의 모든 값을 데이터 타입 보유 (
원시/객체
타입) -
원시 타입
- 객체가 아닌 모든 타입을 의미
- 객체가 아니므로 → 메서드 없음
- 총 7개 (boolean, null, undefined, number, string, symbol, bigint)
-
객체 타입
-
원시 타입 이외의 모든것
-
자바스크립트를 이루고 있는 대부분의 타입
-
배열, 함수, 정규식, 클래스 등이 포함
-
참조를 전달한다고 해서 **참조 타입(reference type)**으로도 불린다.
const objectA = { value: 1, };
objectA는 객체 자체를 저장하고 있는 것이 아니라, 생성된 객체를 가르키는 참조값을 저장
typeof [] === 'object'; // true typeof {} === 'object'; // true const hello = () => {}; typeof hello === 'function'; // true const hello1 = function () {}; const hello2 = function () {}; // 객체인 함수의 내용이 육안으로는 같아 보여도 참조가 다르기 때문에 false hello1 === hello2; // false
-
1.1.2 값을 저장하는 방식의 차이
-
원시타입과 객체타입의 가장 큰 차이점 →
값을 저장하는 방식
let hello = 'hello world'; let hi = 'hello world'; console.log(hello === hi); // true let helloObject = { greet: 'hello world', }; let hiObject = { greet: 'hello world', }; console.log(hello === hi); // false
-
원시 타입: 값을 비교
-
객체 타입: 참조 비교
1.1.3 자바스크립트의 또 다른 비교 공식, Object.is
-
Object.is
는 좀 더 개발자가 기대하는 방식으로 정확히 비교-0 === +0; // true Object.is(-0, +0); // false Number.NaN === NaN; // false Object.is(Number.NaN, NaN); // true NaN === 0 / 0; // false Object.is(NaN, 0 / 0); // true Object.is({}, {}); // true
🤔 Object.is 말이다
ECMAScript에서 미처 잡지 못한 부분을 메서드 하나 파서 잡은 느낌
1.1.4 리액트에서의 동등 비교
- 리액트에서는
Object.is
를 통해 동등 비교 연산 진행 Object.is
는 ES6에서 제공 → 이를 구현한 폴리필(Polyfill)을 함께 사용
🤔 폴리필(Polyfill)
웹 개발에서 기능을 지원하지 않는 웹 브라우저 상의 기능을 구현하는 코드이다.
- 리액트에서는 동등 비교를 하는
shallowEqual
이라는 함수 만들어 사용- 주어진 객체의 키를 순회하면서, 두 값이 엄격한 동등성을 가지는지 확인
- 객체의 길이 자체가 다르면 바로 false로 반환하여 더이상 작업 X
Object.is
(opens in a new tab) 로 먼저 비교 수행 → 객체 간 얕은 비교를 한번더 수행
🤔 엄격한 동등성
MDN에 의하면 (opens in a new tab) ===(삼중 등호) 비교를 의미한다.
🤔 리액트가 얕은 비교까지만 구현한 이유
props에서 꺼내온 값을 기준으로 렌더링을 수행하는데, 이건 얕은 비교로 충분한 작업이므로
shallowEqual({ hello: 'world' }, { hello: 'world' }); // true
1.2 함수
- 리액트에서 함수 컴포넌트를
화살표 함수
와일반 함수
를 혼재해서 쓰는 경우 다수.. 이 둘의 차이는?
1.2.1 함수란 무엇인가?
- 자바스크립트에서 함수란? 작업을 수행하거나 값을 계산하는 과정을 표현하고, 하나의 블록으로 감싼 실행 단위
1.2.2 함수를 정의하는 4가지 방법
-
함수 선언문
function add(a, b) { return a + b; }
함수 선언문은 일반 문(statement)로 분류 → 변수에 할당 가능
-
함수 표현식
// 할당하려는 함수의 이름을 생략하는 것이 일반적 const sum = function add(a, b) { return a + b; }; sum(10, 24); // 34 add(10, 24); // Uncaught ReferenceError: add is not defined
🤔 함수 표현식과 선언식의 차이
호이스팅! ✨
호이스팅: 함수 선언문이 마치 코드 맨 앞단에 작성된 것처럼 작동하는 자바스크립트의 특징
- 함수의 호이스팅 → 함수에 대한 선언을 실행 전에 미리 메모리에 등록하는 작업
- 함수 표현식
- 함수를 변수에 할당
- 변수 호이스팅 : 런타임 이전에는
undefined
로 초기화 → 런타임 시점에 함수가 할당되어 작동
-
Function 생성자
const add = new Function('a', 'b', 'return a + b'); add(10, 24); // 34
- 썩 좋아보이진 않는다.
- 해당 방법으로 함수를 선언하면, 함수의 클로저 생성 X
-
화살표 함수
const add = (a, b) => { return a + b; };
- ES6에서 새롭게 추가된 함수 생성 방식
constructor
사용 Xthis
바인딩- 함수가 어떻게 호출되느냐에 따라 동적으로 결정 (정의 시점 기준 X)
- 화살표 함수는 함수 자체의 바인딩 X
- 화살표 함수 내부에서
this
를 참조하면 상위 스코프의this
를 그대로 따르게 된다.
1.2.3 다양한 함수 살펴보기
- 즉시 실행 함수
- 함수 정의하자마자 실행하는 함수
- 🤔 일회용으로 바로 쓰고 버릴 때 주로 사용하는 것 같음 (그래서 이름도 안 붙임)
- 고차 함수
- 함수를 인자로 받거나 결과로 새로운 함수를 반환하는 역할을 하는 함수
- 대표적으로
Array.prototype.map
- 이러한 특징을 활용해
- 새로운 컴포넌트를 반환하는 고차 함수 컴포넌트를 만들 수 있다 (→ 고차 컴포넌트)
- 고차 함수 컴포넌트는 공통으로 관리되는 로직을 분리해 관리할 수 있어 리팩토링에 유용
🤔 HOC 패턴
-
고차 컴포넌트는 는 인자로 넘긴 컴포넌트에게 추가되길 원하는 로직을 가지고 있다
-
HOC는 로직이 적용된 엘리먼트를 반환하게 된다.
- 와닿는 예:
React.memo
function withStyles(Component) { // 🤔 props를 받아 꾸며진 컴포넌트를 만들어 내보내는 느낌 return props => { const style = { padding: '0.2rem', margin: '1rem' } return <Component style={style} {...props} /> } } const Button = () = <button>Click me!</button> const Text = () => <p>Hello World!</p> const StyledButton = withStyles(Button) const StyledText = withStyles(Text)
- 와닿는 예:
1.2.4 함수를 만들 때 주의해야 할 사항
- 함수의 부수 효과(side-effect)는 최대한 억제하라
- 함수 내의 작동으로 인해 외부에 영향을 끼치는 것을 의미
- 부수 효과가 없는 함수를 순수 함수라고 한다.
🤔 함수형 코딩에서 엄청 강조함
- 보통 배열이나 객체를 받으면 외부 효과를 단절하기 위해 내부에서는 spread 연산자를 통해 안전하게 복제본을 만들어 내부 동작시킴
- 가능한 한 함수를 작게 만들어라
- 길어질수록 코드 냄새가 날 확률 📈
- 코드 냄새 === 문제를 일으킬 여지가 있는 코드
- 길어질수록 함수의 역할도 모호해질 것
- 길어질수록 코드 냄새가 날 확률 📈
- 네이밍 !!!!!
1.3 클래스
- 함수 컴포넌트를 개선하기 위해서는 자바스크립트의 클래스가 어떤 식으로 자동하는지 이해해야 한다.
1.3.1 클래스란 무엇인가?
- 클래스 === 특정한 객체를 만들기 위한 일종의 템플릿
- 객체를 만드는 데 필요한 데이터나 이를 조작하는 코드를 추상화할 때 유용
- 클래스는 ES6에 도입 되었다.
- 이말은 곧, 클래스로 하는 모든 것들을 함수로도 동일하게 표현할 수 있다
🤔 ^ 위 문장 좋다. 항상 함수로 추상화 다 할 수 있어서 혼란스러웠는데?
- 이말은 곧, 클래스로 하는 모든 것들을 함수로도 동일하게 표현할 수 있다
-
contructor
- 생성자!
- 객체를 생성하는 데 사용하는 특수한 메서드
-
프로퍼티
- 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값
- 값을 받으면 내부에 프로퍼티로 할당
class Car { constructor(name) { this.name = name; } } const myCar = new Car('자동차'); // 프로퍼티 값을 넘겨주었다.
#
을 붙여서 private을 선언하는 방법도 추가 (ES2019)
-
정적 메서드
- 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드
class Car { static hello() { console.log('Hello World'); } } const myCar = new Car(); myCar.hello(); // Uncaught TypeError: myCar.hello is not function Car.hello(); // 안녕하세요
1.3.2 클래스와 함수의 관계
- 클래스가 작동하는 방식 === 자바스크립트의 프로토타입을 활용하는 것
- 결국 클래스를 트랜스파일링하면 함수가 된다! 👏🏽
1.4 클로저
- 함수 컴포넌트 이해 ===
클로저
✨ - 클로저가 중요한 이유
- 함수 컴포넌트의 구조, 작동방식, 훅의 원리, 의존성 배열 등 대부분의 기술이 의존
1.4.1 클로저의 정의
- MDN 왈 “클로저는 함수와 함수가 선언된 어휘적 환경(Lexical Scope)의 조합”
function add() {
const a = 10;
function innerAdd() {
const b = 20;
console.log(a + b);
}
innerAdd(); // 30
}
add();
a
변수의 유효범위: add 전체b
변수의 유효범위: innerAdd 전체- innerAdd는 add 내부에서 선언돼 있어 a를 사용할 수 있게 된 것
- ⭐️ 선언된 어휘적 환경
- 변수가 코드 내부에서 어디서 선언됐는지를 말하는 것
1.4.2 변수의 유효 범위, 스코프
-
전역 스코프
- 이 스코프에서 변수를 선언하면 어디서든 호출할 수 있게 된다.
- 대표적으로 window, global 객체
-
함수 스코프
- 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.
var x = 10; function foo() { var x = 100; console.log(x); // 100 function bar() { var x = 1000; console.log(x); // 1000 } bar(); } console.log(x); // 10 foo(); // 100 1000
1.4.3 클로저의 활용
- 함수 레벨 스코프를 활용해 어떤 작업 가능
function outerFn() {
var x = 'hello';
function innerFn() {
console.log(x);
}
return innerFn;
}
const innerFn = outerFn();
innerFn(); // 'hello'
innerFn
은x
라는 변수가 존재하던 환경을 기억하기 때문에 정상적으로 출력
-
클로저의 활용
- 리액트가 관리하는 내부 상태 값은 리액트가 별로로 관리하는 클로저 내부에서만 접근 가능
🤔 별도로 관리하는 클로저 내부에서만 접근 가능이 무슨 얘기일까?
- 일단 클로저 한마디 정의
- 함수가 자신이 선언된 환경(스코프)을 기억하는 기능
- 리액트는 각 컴포넌트의 렌더링마다 새로운 클로저를 생성
- 상태 값은 해당 클로저 내부에서만 접근 가능
- 클로저가 왜 캡슐화?
- 함수 내부의 변수나 상태를 외부에서 직접 접근하거나 수정할 수 없게 만든다.
- 즉, 함수 내부에서만 기억하기 때문에 외부에서 접근 X
🤔 리액트에서는 상태 변경을 캡슐화한 이유?
- 리액트는 각 컴포넌트가 자체적으로 상태를 관리하도록 설계
- 컴포넌트 내부에서만 상태를 변경할 수 있게 되므로, 상태 관리가 단순
- 의도치 않게 변경되는 것을 방지
- 상태 변경을 캡슐화하면 상태를 직접 변경하는 대신 새로운 상태를 반환하는 방식으로 상태를 업데이트 가능
- 상태 변경이 예측 가능하고 안전하게 이루어지도록
- 만약 캡슐화 되어 있지 않고 객체마냥 수정이 가능했다면
- 상태 변경을 추적하기 어려웠을 것
- 리액트에서는 set 함수를 통해서만 상태 변경이 가능하므로 추적이 간편
-
리액트에서의 클로저
- 클로저의 원리를 사용하고 있는 대표적인 것,
useState
function Component() { const [state, setState] = useState(); const handleClick = () => { // useState 호출은 위에서 끝났지만 // setState는 계속 내부의 최신값을 알고 있다 // 이는 클로저를 활용했기 때문에 가능하다 setState((prev) => prev + 1); }; }
- 클로저의 원리를 사용하고 있는 대표적인 것,
1.5 이벤트 루프와 비동기 통신의 이해
- 자바스크립트는 싱글 스레드 (= 한번에 하나의 작업만 동기 방식으로 처리 가능)
- 웹 페이지에서는 다양한 비동기 작업을 수행
- 자바스크립트는 어떻게 여러 가지 요청을 동시에 처리하고 있는지?
1.5.1 싱글 스레드 자바스크립트
- 프로세스(process): 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업 단위
- 스레드(thread): 프로세스 내에서 실행되는 여러 흐름의 단위
- 스레드는 하나의 프로세스에서 동시에 서로 같은 자원에 접근 가능
- 동시에 작업을 수행하다 보면 동시성 문제 발생 가능, 이에 대한 처리 필요
- 하나의 스레드가 문제 생기면, 이를 공유하는 모든 스레드에 동시에 문제 발생 가능
- 자바스크립트가 탄생할 때
- 멀티 스레딩을 지원해 동시에 여러 스레드가 DOM을 조작할 수 있었다면
- 메모리 공유로 인해 타이밍 이슈
- 이는 브라우저의 DOM 표시에 큰 문제 야기할 수 있다.
1.5.2 이벤트 루프란?
- 이벤트 루프는 ECMAScript에 나와있는 내용은 아니다.
- 이벤트 루프? 자바스크립트 런타임 외부에서 자바스크립트의 비동기 실행을 돕기 위해 만들어진 장치
- 호출 스택과 이벤트 루프
- 호출 스택(call stack): 수행해야 할 코드나 함수를 순차적으로 담아주는 스택
- 이벤트 루프의 역할: 호출 스택이 비어있는지 수시로 확인
코드를 실행하는 것
과호출 스택이 비어있는지 확인하는 것
모두가 단일 스레드에서 일어난다.- 즉, 두 작업은 동시 X
- ⭐️ 태스크 큐의 자료구조는
set
이다. (ㅋㅋ)- 태스크 큐는 “실행 가능한 가장 오래된 태스크를 가져와야 한다”
- 이벤트 루프는
- 호출 스택에 실행 중인 코드가 있나?
- 태스크 큐에 대기 중인 함수가 있나?
- ^ 이를 반복해서 확인
1.5.3 태스크 큐와 마이크로 태스크 큐
- 이벤트 루프는 하나의 마이크로 태스크 큐를 갖고 있다.
- 대표적인 마이크로 큐는
Promise
- ⭐️ 마이크로 태스크 큐는 기존 태스크 큐보다 우선권을 갖는다.
- 즉, setTimeout과 setIntercval은
Promise
보다 늦게 실행된다. - 마이크로 태스크 큐가 빌 때까지는 기존 태스크 큐의 실행은 뒤로 밀어진다.
- ⭐️ 마이크로 태스크 큐 작업이 끝날 때마다 한 번씩 렌더링할 기회를 얻게 된다.
1.6 리액트에서 자주 사용하는 자바스크립트 문법
- 자바스크립트의 표준 === ECMAScript
- 자바스크립트 문법이 어느 ECMAScript 버전에서 만들어졌는지도 파악 해야 한다.
- 모든 브라우저와 자바스크립트 런타임이 항상 새로운 자바스크립트 문법을 지원하는 것이 아니기 때문!
Can I use
- 다양한 환경에서 지원하기 위해 탄생한 것이 바벨 !
- 최신 문법을 다양한 브라우저에서 일관적으로 지원할 수 있도록 트랜스파일링
🤔 트랜스파일러가 있는데 왜 우리는 지원 버전을 매번 체크해야 하는걸까?
- 모든 기능을 완벽하게 지원하지 못하는 경우 존재
- 일부 최신 API나 기능은 트랜스파일링으로 해결 X
- 뭐가 되었든 변환하는 과정이 끼면, 성능 저하
- 최근 회사에서도..
- 지원 버전이 너무 작아서 폴리필로 직접 구현해서 사용하더라
1.7 선택이 아닌 필수, 타입스크립트
- 자바스크립트에서 런타임에만 타입을 체크할 수 있는 한계를 극복해 코드를 더욱 안전하게
1.7.1 타입스크립트란?
- 자바스크립트 문법에 타입을 가미한 것
- 자바스크립트는 동적 타입 언어
- 대부분의 에러를 런타임에 확인 가능하다
- 자바스크립트로 매번
typeof
로 타입을 체크하는 것은 너무 번거롭고 코드 벌크업 - 타입스크립트는
- 타입 체크를 빌드타임에 수행할 수 있게 해준다.