May 23, 2024
props의 동등 비교
boolean, null, undefined, number, string, symbol, bigint (7개)
undefined
null
특별한 점
typeof null === 'object' // true
undefined
vs null
undefined
: 선언했지만 할당되지 않은 값null
: 명시적으로 비어 있음을 나타내는 값falsy
한 값
truthy
String
template literal
Symbol
object
⇒ 객체 간 비교 발생 시 내부 값이 같더라도 결과는 대부분 false일 수 있음을 인지해야 함
function add(a, b) {
return a + b
}
자바스크립트에서 함수는 일급 객체
함수 표현식 vs 선언 식
const add = new Function('a', 'b', 'return a + b')
권장되지 않음
const add = (a, b) => a + b
화살표 함수와 일반 함수의 가장 큰 차이점 : this 바인딩
⇒ 별도의 작업을 추가로 하지 않고 this 접근 가능
(function (a, b) {
return a + b
})(10, 24)
((a, b) => {
return a + b
},
)(10, 24)
// 함수를 매개변수로 받는 대표적인 고차 함수
const doubledArray = [1, 2, 3].map((item) => item * 2)
함수의 부수효과(side-effect)
순수 함수
비순수 함수
리액트의 useEffect, useCallback 등의 훅의 콜백 함수에 네이밍을 붙여주는 게 가독성에 도움이 됨
useEffect(function apiRequest() {
// ... do something
}, [])
class Car {
// ...
static hello() {
console.log('저는 자동차입니다.')
}
}
const myCar = new Car('자동차')
// 정적 메서드는 클래스에서 직접 호출함
Car.hello()
// 정적 메서드는 클래스로 만든 객체에서는 호출할 수 없음
myCar.hello()
장점
⇒ 애플리케이션 전역에서 사용하는 유틸 함수를 정적 메서드로 많이 활용
각 컴포넌트 이해에 중요한 것 | |
---|---|
클래스형 컴포넌트 | 클래스, 프로토타입, this |
함수형 컴포넌트 | 클로저 |
호출되는 방식에 따라 동적으로 결정되는 this와는 다르게 코드가 작성된 순간에 정적으로 결정됨
⇒ 클로저는 이러한 어휘적 환경을 조합해 코딩하는 기법
스코프
: 변수의 유효범위전역 객체 | |
---|---|
브라우저 환경 | window |
Node.js 환경 | global |
{} 블록이 스코프 범위를 결정하지 않음
if (true) {
var global = 'global scope'
}
console.log(global) // 'global scope'
console.log(global === window.global) // true
var global은 분명 {} 내부에서 선언돼 있는데, {} 박에서도 접근이 가능함
⇒ JS는 함수 레벨 스코프를 가지기 때문
function outerFunction() {
var x = 'hello'
function innerFunction() {
console.log(x)
}
return innerFunction
}
const innerFunction = outerFunction()
innerFunction() // 'hello'
useState: 클로저의 원리를 사용하는 대표적인 것
function Component() {
const [state, setState] = useState()
function handleClick() {
// useState 호출은 위에서 끝났지만 setState는 계속 내부의 최신값(prev)을 알고 있음
// 클로저를 활용했기 때문에 가능
setState((prev) => prev + 1)
}
}
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
⇒ 5만 출력됨
JS는 함수 레벨 스코프를 따르기 때문에 var는 for문의 존재와 무관하게 해당 구문이 선언된 함수 레벨 스코프를 보기 때문에 전역 스코프에 var i가 등록됨
⇒ for 문 다 순회한 이후, 태스크 큐에 있는 setTimeout을 실행하려 했을 때, 이미 전역 레벨에 있는 i는 5로 업데이트가 완료돼있음
함수 레벨 스코프가 아닌 블록 레벨 스코프를 갖는 let으로 수정
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
클로저를 제대로 활용
for (var i = 0; i < 5; i++) {
setTimeout(
(function (sec) {
return function () {
console.log(sec
}
}(i),
i * 1000,
)
}
setTimeout의 콜백 함수가 바라보는 클로저는 즉시 실행 익명 함수가 됨
⇒ 각 for 문마다 생성되고 실행되기를 반복
동기(synchronous) : 직렬 방식으로 작업을 처리하는 것
비동기(asynchronous) : 병렬 방식으로 작업을 처리하는 것
모던 웹 애플리케이션에서는 사용자에게 많은 양의 정보를 다양한 방식으로 제공하기 위해 많은 것이 비동기로 작동함
리액트
과거 렌더링 스택을 비우는 방식으로 구현됐던 동기식의 렌더링이 16버전에서 비동기식으로 작동하는 법도 소개됨
⇒ 리액트에도 비동기식으로 작동하는 작업이 존재
과거
프로세스
: 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업 단위소프트웨어의 복잡성 증가 → 동시에 여러 개의 복잡한 작업을 수행할 필요성
⇒ 그래서 탄성한 더 작은 실행 단위 : 스레드
멀티 스레드
: 내부적으로 처리가 복잡하다는 단점
최초의 자바스크립트
JS가 멀티 스레딩을 지원해 동시에 여러 스레드가 DOM 조작이 가능하다면?
멀티 스레딩은 메모리 공유로 인해 동시에 같은 자원에 접근하면 타이밍 이슈 발생 가능
⇒ 브라우저의 DOM 표시에 큰 문제 야기 가능
싱글 스레드
: JS 코드의 실행이 하나의 스레드에서 순차적으로 이루어짐Run-to-completion
: 하나의 코드 실행 시 오래 걸리면 뒤이은 코드가 실행되지 않음async(asynchronous)
: 비동기 함수 선언 시 사용
콜스택
이벤트 루프
setTimeout(() ⇒ {}, 0)
이 정확히 0초 뒤에 실행된다는 것을 보장하지 못함태스크 큐
이름과 다르게 queue가 아닌 set의 자료구조를 가짐
⇒ 선택된 큐 중 실행 가능한 가장 오래된 태스크를 가져와야 하기 때문
비동기 함수는 누가 수행하나
n초 뒤 setTimeout을 요청, fetch를 기반으로 실행되는 네트워크 요청은 누가 보내고 응답을 받나
⇒ 이 작업들은 모두 JS가 동기식으로 실행되는 메인 스레드가 아닌 태스크 큐가 할당되는 별도의 스레드에서 수행됨
브라우저나 Node.js
의 역할Promise
리액트의 독특한 특징
바벨
배열의 구조 분해 할당은 ,의 위치에 따라 값이 결정됨
const [first, , , , fifth] = array
⇒ 배열의 길이가 작을 때 주로 쓰임
기본값 선언 가능
const array = [1, 2]
const [a = 10, b = 10, c = 20] = array
특정값 이후의 값을 다시 배열로 선언하고 싶다면 전개 연산자
사용
const [first, ...rest] = array
새로운 이름으로 재할당 가능
const object = {
a: 1,
b: 1.
}
const { a: first, b: second } = object
기본값 주는 것도 가능
const { a = 10, b = 10, c = 10} = object
트랜스파일을 거치면 번들링 크기가 상대적으로커서 웹 앱 개발 환경이 ES5를 고려해야 하고, 객체 구조 분해 할당을 자주 쓰지 않는다면 꼭 써야할지 검토가 필요
과거에는 배열 간 합성 시 push(), concat(), splice() 등의 메서드 사용 필요
⇒ 전개 구문 활용 시 쉽게 합성 가능
기존 배열에 영향 없이 값만 복사 가능
const arr1 = ['a', 'b']
const arr2 = [...arr1]
arr 1 === arr2 // false
⇒ 값만 복사되고 참조는 다르므로 false 반환
전개 구문 → 값 할당
: 전개 구문이 할당 값을 덮어쓰지만 반대면 반대로 덮어씀
const aObj = {
...obj,
c: 10,
}
const bObj = {
c: 10,
...obj,
}
트랜스파일 결과
const a = 1
const b = 2
const obj = {
a,
b,
}
map, filter, reduce
forEach
타입스크립트
any 사용
unknown
never
타입 가드
: 타입을 좁히는 데 도움을 줌
instanceof
: 지정한 인스턴스가 특정 클래스의 인스턴스인지 확인할 수 있는 연산자typeof
: 특정 요소에 대해 자료형을 확인하는 데 사용됨in
function doSchool(person: Student | Teacher) {
if ('age' in person) {
// ...
}
}
제네릭은 하나 이상도 사용 가능 → 적절한 네이밍 필요
function multipleGeneric<First, Last>(a1: First, a2: Last): [First, Last] {}
객체의 키를 정의하는 방식
type Hello = {
[key: string]: string
}
객체에 인덱스 시그니처 사용 시 다음 이슈 발생 가능
Object.keys(hello).map((key) => {
// No index signature with a parameter of type 'string' was found on type 'Hello'
const value = hello[key]
return value
})
해결법
Object.keys(hello)를 as로 타입 단언하기
(Object.keys(hello) as Array<keyof Hello>).map((key) => {
const value = hello[key]
return value
})
타입 가드 함수 만들기
function keysOf<T extends Object>(obj: T): array<keyof T> {
return Array.from(Object.keys(obj)) as Array<keyof T>
}
keysOf(hello).map((key) => {
const value = hello[key]
return value
})
가져온 key를 단언하기
Object.keys(hello).map((key) => {
const value = hello[key as keyof Hello]
return value
})
Object.keys는 함수 내부에서 적절히 추론 가능함에도 왜 string[]으로 강제돼 있나
⇒ JS의 특징과, 이를 구현하기 위한 TS의 구조적 타이핑의 특징 때문
덕 타이핑
: 객체의 타입이 클래스 상속, 인터페이스 구현 등으로 결정되지 않고 어떤 객체가 필요한 변수, 메서드만 가지면 해당 타입에 속하도록 인정해주는 것JS는 객체의 타입에 구애받지 않고 객체의 타입에 열려 있으므로 TS도 이러한 JS의 특징을 맞춰줘야 함
⇒ TS는 모든 키가 들어올 수 있는 가능성이 열려 있는 객체의 키에 포괄적으로 대응하기 위해 string[]으로 타입을 제공하는 것
{
"compilerOptins": {
"outDir": "./dist",
"allowJs": true,
"target": "es5"
},
"include": ["./src/**/*"]
}
//@ts-check
선언하고 JsDoc을 활용해 변수나 함수에 타입 제공 시 TS 컴파일러가 JS 파일의 타입을 확인함DefinitelyTyped
를 설치해야 함DefinitelyTyped
@types/react
와 @types/react-dom
등에 정의돼 있음모든 라이브러리가 @types를 필요로 하는 것은 아님
“Cannot find module ‘lodash’ or its corresponding type declarations” 에러
JS 기반 코드를 TS로 전환하는 것은 매우 인내심이 필요한 일
⇒ 코드를 하나씩 수정해 나가다 보면 어느새 코드가 더욱 단단해짐을 느낄 수 있음