리액트 개발을 위해 꼭 알아야 할 자바스크립트 (2)

keemsebeen·2024년 4월 30일

1.5 이벤트 루프와 비동기 통신의 이해

자바스크립트는 싱글 스레드로 작동한다. 즉, 한 번에 하나의 작업만 동기 방식으로 작동한다. 그러나 이러한 싱글 스레드 기반의 자바스크립트에서도 많은 양의 비동기 작업이 이루어지고 있다.

해당 방식을 이해하기 위해서는 비동기 작업이 어떻게 처리되는지 이해하고 비동기 처리를 도와주는 이벤트 루프를 비롯한 다양한 개념에 대해 알고 있어야 한다.

싱글 스레드 자바스크립트

스레드란, 프로세스보다 더 작은 실행 단위로 스레드끼리 메모리 공유가 가능하며 여러가지 작업을 동시에 수행할 수 있다.

그렇다면 자바스크립트는 왜 싱글 스레드로 설계됐을까?

최초의 자바스크립트는 단순 작업만 진행했을 뿐더러, 멀티 스레드를 지원했다면 동시에 여러 스레드가 DOM을 조작하여 동시성 문제를 야기했을 것이다.

자바스크립트 개발자에게 동시성을 고민할 필요가 없다는 장점이 되지만, 하나의 작업이 끝나기 전에 다른 작업이 실행되지 않는 점이 단점이라고 느껴질 수 있다.

💡 Run-to-completion : 모든 코드는 ‘동기식’으로 한 번에 하나씩 순차적으로 처리된다.

이벤트 루프란?

자바스크립트 런타임 외부에서 자바스크립트의 비동기 실행을 돕기 위해 만들어진 장치이다.

호출 스택과 이벤트 루프
호출 스택 : 자바스크립트에서 수행해야 할 코드나 함수를 순차적으로 담아두는 공간

이벤트 루프 : 호출 스택이 비어 있는지 여부를 확인하는 공간

  • 호출 스택에 실행 중인 코드가 있는지, 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인한다.
  • 호출 스택이 비어지면 이벤트 루프가 태스크 큐에 있던 함수를 호출 스택에 들여보낸다.

태스크 큐 : 실행해야 할 태스크의 집합을 의미한다. queue가 아닌 set 형태를 띄고 있는데 선택된 큐 중에서 실행 가능한 가장 오래된 태스크를 가져와야 하기 때문이다.

  • Node.js 나 브라우저가 별도의 스레드에서 태스크 큐에 작업을 할당해 처리한다.
  • 외부 Web API등은 모두 자바스크립트 코드 외부에서 실행되고 콜백이 태스크 큐에 들어가게 된다.

태스크 큐와 마이크로 태스크 큐

마이크로 태스트 큐는 기존 캐스트 큐보다 우선권을 갖는다.

태스크 큐를 실행하기 전 마이크로 태스크 큐를 실행하고, 이 마이크로 태스크 큐를 실행한 뒤에 렌더링이 일어난다.

태스크 큐 : setTimeout, setInterval, setImmediate

마이크로 태스크 큐 : process.nextTick, Promise, queueMicroTask, MutationObserver

console.log('a');

// 태스크 큐
setTimeout(() => {
	console.log('b');
}, 0)

// 마이크로 태스크 큐
Promise.resolve().then(() => {
	console.log('c');
})

window.requestAnimationFrame(() => {
	console.log('d');
})

위 코드를 실행하면 a,c,d,b 순서로 출력된다. 즉, 브라우저에 렌더링 작업은 마이크로 태스크 큐와 태스크 큐 사이에서 일어난다.

💡 정리 자바스크립트 코드를 실행하는 것 자체는 싱글 스레드로 이루어져서 비동기를 처리하기 어렵지만 자바스크립트 코드를 실행하는 것 이외에 태스크 큐, 이벤트 루프, 마이크로 태스크 큐, 브라우저/Node.js, API 등이 존재하기 때문에 싱글 스레드로는 불가능한 비동기 이벤트 처리가 가능해진 것이다.

1.6 리액트에서 자주 사용하는 자바스크립트 문법

바벨이란, 다양한 부라우저 환경에서도 최신 문법을 지원할 수 있도록 코드를 트랜스 파일 해주는 도구이다.

구조 분해 할당
배열 또는 객체의 값을 말 그대로 분해해 개별 변수에 즉시 할당하는 것이다.

배열 구조 분해 할당
, 의 위치에 따라 값이 결정된다. 배열의 길이가 짧거나 값이 없는 경우 (undefined)에는 기본 값을 사용한다.

// 트랜스 파일 하기 전
const array = [1,2,3,4,5];
const [first, seconde, third, ...arrayRest] = array;
// arrayRest [4,5]

// 트랜스 파일 된 결과
var array = [1, 2, 3, 4, 5];
var first = array[0],
	second = array[1],
	third = array[2],
	arrayRest = array.slice(3);

객체 구조 분해 할당
객체 내부 이름으로 꺼내오게 된다. 사용하는 쪽에서 원하는 이름으로 변경하는 것이 번거롭다.

const object = {
	a: 1,
	b: 2
	c: 3,
	d: 4,
	e: 5,
}
const { a, b, c, ...objectRest } = object;
// a 1 , b 2, c 3, objectRest = { d: 4, e: 5}

전개 구문
배열이나 객체, 문자열과 같이 순회할 수 있는 값에 대해 말 그대로 전개해 간결하게 사용할 수 있는 구문이다.

배열의 전개 구문
…배열 구문을 사용하여 해당 배열을 마치 전개하는 것처럼 선언하고 이를 내부에서 사용 가능하다.

const arr1 = ['a', 'b'];
const arr2 = arr1; 
arr1 === arr2 
// true. 내용이 아닌 참조를 복사하기 때문에

const arr1 = ['a', 'b'];
const arr2 = [...arr1];
arr1 === arr2
// false. 실제 값만 복사됐을 뿐, 참조는 다르므로 false가 반환된다.
// 트랜스파일하기 전
const arr1 = ['a', 'b'];
const arr2 = [...arr1, 'c', 'd'];

// 트랜스파일된 결과
var arr1 = ['a', 'b'];
var arr2 = [].concat(arr1, ['c', 'd']);

객체의 전개 구문

객체 전개 구문은 순서가 중요하다.

const obj = {
	a: 1,
	b: 2
	c: 3,
	d: 4,
	e: 5,
};

const a0bj = {
	...obj,
	c: 10,
}; // { a:1, b:1, c:10, d:1, e:1}

const b0bj = {
	c: 10,
	...obj,
}; // { a:1, b:1, c:1, d:1, e:1}

전개 구문 이후에 값 할당이 있다면 할당한 값이 이전에 전개했던 구문 값을 덮어 쓴다. 반대의 경우 전개 구문이 해당 값을 덮어 쓴다.

트랜스 파일을 한 결과는, 단순히 값을 복사하는 것이 아닌 객체의 속성값 및 설명자 확인, 심벌 체크 등 코드가 커지게 된다.

객체 초기자
객체에 넣고자 하는 키와 값을 가지고 있는 변수가 이미 존재한다면 해당 값을 간결하게 넣어줄 수 있는 방식이다.

const a = 1;
const b = 2;

const obj = {
	a,
	b,
};
// {a:1, b:2}

Array 프로토타입의 메서드 : mpa, filter, reduce, forEach

모두 배열과 관련된 메서드다. 기존 배열의 값을 건드리지 않고 새로운 값을 만들어 내기 때문에 안전하게 사용할 수 있다.

Array.prototype.map
배열의 각 아이템을 순회하면서 각 아이템을 콜백으로 연산한 결과로 구성된 새로운 배열을 만든다.

const arr = [1,2,3,4,5];
const doble = arr.map((items) => items * 2)
// 2,4,6,8,10

Array.prototype.filter
truthy 조건을 만족하는 경우에만 해당 원소를 반환한다. filter 결과에 따라 원본 배열의 길이 이하의 새로운 배열이 반환된다.

const arr = [1,2,3,4,5];
const even = arr.filter((items) => items % 2 === 0)
// [2,4]

Array.prototype.reduce
첫 번째 인수는 초깃값의 현재값을 의미하고, 두 번째 인수는 현재 배열의 아이템을 의미한다. 초깃값에 누적해 결과를 반환한다.

const arr = [1,2,3,4,5];
const sum = arr.reduce((result,items) =>{
	return result + item
}, 0)
// 15

Array.prototype.forEach
배열을 순회하면서 단순히 콜백 함수를 실행하기만 하는 메서드다.
주의할 점은 아무로 반환값이 없고, return, break 등을 사용해도 배열 순회를 멈출 수 없다.

const arr = [1,2,3];
arr.forEach((item) => console.log(item));
// 1 2 3

삼항 조건 연산자
조건문 ? 참일때 값 : 거짓일 때 값

1.7 선택이 아닌 필수, 타입스크립트

더욱 안전하게 작성하면서도 잠재적인 버그도 크게 줄일 수 있는 기회.

타입스크립트란?

자바스크립트는 기본적으로 동적 타입의 언어이기 떄문에 에러를 코드를 실행했을 경우에만 확인이 가능하다.

타입스크립트는 자바스크립트의 한계를 벗어나 타입 체크를 정적으로 런타임이 아닌 빌드 타임에 수행할 수 있게 해준다.

리액트 코드를 효과적으로 작성하기 위한 타입스크립트 활용법

타입스크립트는 얼마나 타입을 엄격하게, 그리고 적극적으로 활용하느냐에 따라 효용성에 큰 차이가 있다.

any 대신 unknown을 사용하자
unknown은 모든 값을 할당할 수 있는 이른바 top type으로 어떠한 값도 할당할 수 있다.

그러나 any와는 다르게 이 값을 바로 사용하는 것은 불가능하다.

function doSomething(callback: unknown){
	if(typeof callback === 'function') {
		callback()
		return
	}
	throw new Error('!!');
}

원하는 타입일 경우에만 의도대로 작동하도록 수정이 가능하다.
unknown과 반대되는 것으로는 never가 있다.

type what1 = string & number 

string과 number를 둘 다 만족시키는 타입은 없다. 따라서 never가 선언된다.

타입 가드를 적극 활용하자
타입을 사용하는 쪽에서 타입을 좁힐 수 있다.

  • instanceof와 typeof : 지정한 인스턴스가 특정 클래스의 인스턴스인지 확인할 수 있는 연산자
    if (e instanceof UbExpectedError) { // }
  • in : 어떤 객체에 키가 존재하는지 확인하는 용도로 사용이 가능하다.
    interface Student {
    	age: number
    	score: number
    }
    
    function doSchool(person : Student) {
     if ('age' in person) {
    	 person.age
     }
    }

제네릭
함수나 클래스 내부에서 단일 타입이 아닌 다양한 타입에 대응할 수 있도록 도와주는 도구이다.

function getFirstAndLast<T>(list : T[]): [T,T] {
	return [list[0], list[list.length -1]];
};

인덱스 시그니처
객체/의 키를 정의하는 방식 [key : string]

// record를 사용
type Hello = Record<'hello' | 'hi', string>

const hello:Hello = {
	hello: 'hello',
	hi: 'hi'
}

hello['안녕'] // undefined

// 타입을 사용한 인덱스 시그니처
type Hello = { [key in 'hello' | 'hi']: string }

동적으로 선언되는 경우를 최대한 지양하고, 객체의 타입도 좁히는 것이 좋다.

Object.key가 string[]으로 강제된 이유?

자바스크립트, 타입스크립트의 구조적 타이핑의 특징 때문이다.

타입스크립트의 핵심 원칙 중 하나는 타입 검사의 값의 형태에 초점을 맞춘다는 것입니다. 이를 “덕 타이핑”(어떤 객체가 필요한 변수와 메서드만 지니고 있다면 그냥 해당 타입에 속하도록 인정해 주는 것 ) 또는 “구조적 서브타이핑”이라고 부르기도 한다.

즉, 모든 키가 들어올 수 있는 가능성이 열려 있는 객체의 키에 포괄적으로 대응하기 위해 string[]을 제공하는 것이다.

타입스크립트 전환 가이드

tsconfig.json

{
	"compilerOptions" : {
		"outDir" : "./dist",
		"allowJs": true,
		"target": "es5"
	},
	"include" : ["./src/**/*"]
}
  • outDir : .ts나 .js가 만들어진 결과를 넣어두는 폴더이다.
  • allowJs : .js 파일을 허용할 것인지 여부이다.
  • target : 결과물이 될 자바스크립트 버전
  • include : 트랜스파일할 자바스크립트와 타입스크립트의 파일을 지정한다.

JSDoc과 @ts-check를 활용해 점진적으로 전환하기

자바스크립트 파일을 굳이 타입스크립트로 전환하지 않더라도 타입을 체크하는 방법이 있다.

  1. 상단에 //@ts-check을 선언
  2. JSDoc을 활용해 변수나 함수에 타입을 제공하면 타입스크립트 컴파일러가 자바스크립트 파일의 타입을 확인한다.
profile
프론트엔드 개발자 김세빈입니다. 👩🏻‍💻

0개의 댓글