고도연
Study

정리하기 파일

1.2 함수

1.2.2 함수를 정의하는 4가지 방법

  1. 함수 선언문

    function add(a, b) {
    	return a + b
    }
  2. 함수 표현식

    const sum = function (a, b) {
    	console.log(arguments.callee.name)
    	return a + b
    }
     
    sum(10, 24)

함수 표현식과 선언 식의 차이 (호이스팅 여부)

  • 함수 선언식 : 함수 전체를 호이스팅 합니다. 맨 위로 호이스팅되서 함수 선언 전에 함수를 사용할 수 있다
  • 수 표현식 : 별도 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅 하게 됩니다.
  1. Function 생성자

    const add = new Function('a', 'b', 'return a+b')
     
    add(10, 24)  // 34

    매개변수, 함수의 몸통을 모두 문자열로 작성해야 한다.

    실제 코딩에서 자주 사용되지 않는 방법이다.

  2. 화살표 함수

    ES6에서 새롭게 추가된 함수 생성 방식이다.

    const add = (a, b) => {
    	return a + b
    }
     
    const add = (a, b) => a + b

    화살표 함수에서는 constructor를 사용할 수 없다.

    화살표 함수에서는 arguments가 존재하지 않는다.

    화살표 함수와 일반 함수의 가장 큰 차이점은 바로 this 바인딩이다.

    • 일반 함수로서 호출된다면, 그 내부의 this는 전역 객체를 가리키게 된다.
    • 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 따르게 된다.
class Component extends React.Component {
	constructor(props) {
		super(props)
		this.state = {
			counter: 1,
		}
	}
}
 
functionCountUp(){
	console.log(this)
	this.setState((prev) => ({ counter: prev.counter + 1 }))
}
 
ArrowFunctionCountUp = () => {
	console.log(this) // class Component
	this.setState((prev) => ({ counter: prev.counter + 1 }))
}
 
render() {
	return (
		<div>
			<button onClick={this.functionCountUp}>일반 함수 </button>
			<button onClick={this.ArrowFunctionCountUp}>화살표 함수 </button>
		</div>
	)
}
 

두 메서드에서 모두 state를 하나씩 올리는 작업을 동일하게 하고 있다.

그러나 일반 함수에서의 this는 undefined를, 화살표 함수에서의 this는 우리가 원하는 대로 클래스의 인스턴스인 this를 가리킨다.

const hello = () => {
	console.log(this)
}
 
function hi() {
	console.log(this)
}
 
var _this = void 0
 
var hello = function hello() {
	console.log(_this)
}
 
function hi() {
	console.log(this)
}

1.2.3 다양한 함수 살펴보기

즉시 실행 함수

함수를 정의하고 그 순간 즉시 실행되는 함수를 의미. 단 한 번만 호출되고, 호출할 수 없는 함수다.

(function (a, b) {
	return a + b
})(10, 24);  // 34
 
((a, b) => {
	return a + b
})

고차 함수

함수를 인수로 받거나 결과로 새로운 함수를 반환시킬 수 있다.

// 함수를 매개변수로 받는 대표적인 고차 함수, Array.prototype.map
const doubledArray = [1, 2, 3].map((item) => item * 2)
 
doubledArray // [2, 4, 6]
 
// 함수를 반환하는 고차 함수의 예
const add = function (a) {
	return function (b) {
		reurn a + b
	}
}
 
add(1)(3)  // 4

1.2.4 함수를 만들 때 주의해야 할 사항

함수의 부수 효과를 최대한 억제하라

함수의 부수 효과란 함수 내의 작동으로 인해 함수가 아닌 함수 외부에 영향을 끼치는 것을 의미한다. 리액트의 관점에서 본다면 부수 효과를 처리하는 훅인 useEffect의 작동을 최소한으로 줄임으로써 함수의 역할을 좁히고, 버그를 줄이며, 컴포넌트의 안정성을 높일 수 있다.

가능한 한 함수를 작게 만들어라

ESLint에는 max-lines-per-function이라는 규칙이 있는데, 기본값으로 50줄 이상이 넘어가면 경고 메시지를 출력한다. 함수는 하나의 일을 하고 그 하나만 잘하면 된다. 그것이 함수의 원래 목적인 재사용성을 높일 수 있는 방법이다.

누구나 이해할 수 있는 이름을 붙여라

리액트에서 사용하는 useEffect나 useCallback 등의 훅에 넘겨주는 콜백 함수에 네이밍을 붙여준다면 가독성에 도움이 된다.

1.3 클래스

16.8 버전이 나오기 전까지 리액트에서는 모든 컴포넌트가 클래스로 작성돼 있었다. 과거에 작성된 리액트 코드를 읽기 위해서, 또 이 코드를 함수형으로 개선하기 위해서는 자바스크립트의 클래스가 어떤 식으로 작동하는지 이해해야 한다.

1.3.1 클래스란 무엇인가?

자바스크립트의 클래스란 특정한 객체를 만들기 위한 일종의 템플릿과 같은 개념으로 볼 수 있다.

// Car 클래스 선언
class car {
	// constructor는 생성자다. 최초에 생성할 때 어떤 인수를 받을지 결정할 수 있으며, 
	// 객체를 초기화하는 용도로도 사용된다.
	constructor(name) {
		this.name = name
	}
}
 
// 메서드
hook() {
	console.log(`${this.name}이 경적을 울립니다!`)
}
 
// 정적 메서드
static hello() {
	console.log('저는 자동차입니다.')
}
 
// setter
set age(value) {
	this.carAge = value
}
 
// getter
get age() {
	return this.carAge
}
 
// Car 클래스를 활용해 car 객체를 만들었다.
const myCar = new Car('자동차')
 
// 메서드 호출
myCar.honk()
 
// 정적 메서드는 클래스에서 직접 호출한다.
Car.hello()
 
// 정적 메서드는 클래스로 만든 객체에서는 호출할 수 없다.
// TypeError
myCar.hello()
 
// setter를 만들면 값을 할당할 수 있다.
myCar.age = 32
 
// getter로 값을 가져올 수 있다.
console.log(myCar.age, myCar.name) // 32 자동차

constructor

constructor는 생성자로, 객체를 생성하는 데 사용하는 특수한 메서드다. 단 하나만 존재할 수 있으며, 생략하는 것도 가능하다.

프로퍼티

프로퍼티란 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값을 의미한다.

class Car {
	constructor(name) {
		// 값을 받으면 내부에 프로퍼티로 할당된다.
		this.name = name
	}
}
 
const myCar = new Car('자동차')  // 프로퍼티 값을 넘겨주었다.

타입스크립트를 활용하면 private, protected, public을 사용할 수 있다. 자바스크립트에서는 기본적으로 모든 프로퍼티가 public이다.

getter와 setter

getter란 클래스에서 무언가 값을 가져올 때 사용된다. setter란 클래스 필드에 할당할 때 사용한다.

인스턴스 메서드

클래스 내부에서 선언한 메서드를 인스턴스 메서드라고 한다.

정적 메서드

정적 메서드는 특이하게 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드다. this에 접근할 수 없지만 인스턴스를 생성하지 않아도 사용할 수 있고, 여러 곳에서 재사용이 가능하다. 전역에서 사용하는 유틸 항수를 정적 메서드로 많이 활용하는 편이다.

class Car {
	static hello() {
		console.log('안녕하세요!')
	}
}
 
const myCar = new Car()
 
myCar.hello() // TypeError
Car.hello()   // 안녕하세요!

상속

리액트에서 클래스형 컴포넌트를 만들기 위해서 extends React.Component 또는 extends React.Pure Component를 선언한 것을 본 적이 있을 것이다. 이 extends는 기존 클래스를 상속받아서 자식 클래스에서 이 상속받은 클래스를 기반으로 확장하는 개념이라 볼 수 있다.

class Car {
	construnctor(name) {
		this.name = name
	}
 
	honk() {
		console.log(`${this.name} 경적을 울립니다.`)
	}
}
 
class Truck extends Car {
	constructor(name) {
		// 부모 클래스(Car)의 constructor를 호출한다.
		super(name)
	}
 
	load() {
		console.log('짐을 싣습니다.')
	}
}
 
const myCar = new Car('자동차')
myCar.honk()  // 경적을 울립니다.
 
const truck = new Truck('트럭')
truck.honk()  // 경적을 울립니다.
truck.load()  // 짐을 싣습니다.
 

1.4 클로저

클래스형 컴포넌트에 대한 이해가 자바스크립트의 클래스, 프로토타입, this에 달려있다면, 함수형 컴포넌트에 대한 이해는 클로저에 달려 있다.

1.4.1 클로저의 정의

클로저는 함수와 함수가 “선언된 어휘적 환경”의 조합이다. 선언된 어휘적 환경은 변수가 코드 내부에서 어디서 선언됐는지 말하는 것이다. 클로저는 이러한 어휘적 환경을 조합해 코딩하는 기법이다.

function add() {
	const a = 10
	function innerAdd() {
		const b = 20
		console.log(a + b)
	}
	innerAdd()  // 30
}
 
add()

1.4.2 변수의 유효 범위, 스코프

전역 스코프

전역 레벨에 선언하는 것을 전역 스코프라고 한다. 이 스코프에서 변수를 선언하면 어디서든 호출할 수 있게 된다.

함수 스코프

자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.

1.4.3 클로저의 활용

클로저의 활용

전역 스코프는 어디서든 원하는 값을 꺼내올 수 있다는 장점이 있지만, 반대로 누구든 접근할 수 있고 수정할 수 있다는 뜻도 된다.

var counter = 0
 
function handleClick() {
	counter++
}

위 counter변수는 전역 레벨에 선언돼 있어서 누구나 수정할 수 있다. window.counter를 활용하면 쉽게 해당 변수에 접근할 수 있다.

이를 클로저를 활용한 코드로 변경해보자.

function Counter() {
	var counter = 0
 
	return {
		increase: function () {
			return ++counter
		},
		decrease: function () {
			return --counter
		},
		counter: function () {
			console.log('counter에 접근')
			return counter
		}
	}
}
 
var c = Counter()
 
console.log(c.increase())  // 1
console.log(c.increase())  // 2

counter 변수를 직접적으로 노출하지 않음으로써 사용자가 직접 수정하는 것을 막았고, counter 변수의 업데이트를 increase와 decrease로 제한해 무분별하게 변경되는 것을 막았다.

이처럼 클로저를 활용하면 전역 스코프의 사용을 막고, 개발자가 원하는 정보만 개발자가 원하는 방향으로 노출시킬 수 있다는 장점이 있다.

리액트에서는 useState의 변수를 저장해 두고, useState의 변수 접근 및 수정 또한 클로저 내부에서 확인이 가능해 값이 변하면 렌더링 함수를 호출하는 등의 작업이 이루어질 것이다.

리액트에서의 클로저

클로저의 원리를 사용하고 있는 대표적인 것 중 하나가 useState다.

function Component() {
	const [state, setState] = useState()
 
	function handleClick() {
		// useState 호출은 위에서 끝났지만,
		// setState는 계속 내부의 최신값(prev)를 알고 있다.
		// 이는 클로저를 활용했기 때문에 가능하다.
		setState((prev) => prev + 1)
	}
}

1.4.4 주의할 점

클로저를 사용하는 데는 비용이 든다. 클로저를 활용하는 함수를 크롬 개발자 도구에서 확인해 보면 클로저를 활용하는 쪽이 압도적으로 부정적인 영향을 미친다.

즉, 클로저에는 꼭 필요한 작업만 남겨두도록 하자.

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

자바스크립트는 싱글 스레드에서 작동한다. 즉, 기본적으로 자바스크립트는 한 번에 하나의 동기 방식으로만 처리할 수 있다. 동기란 직렬 방식으로 작업을 처리하는 것을 의미하고, 비동기란 병렬 방식으로 작업을 처리하는 것을 의미한다.

1.5.1 싱글 스레드 자바스크립트

프로세스란 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업 단위를 의미한다. 즉, 하나의 프로그램 실행은 하나의 프로세스를 가지고 그 프로세스 내부에서 모든 작업이 처리되는 것을 의미한다.

프로세스보다 더 작은 실행 단위가 스레드다. 하나의 프로세스에서는 여러 개의 스레드를 만들 수 있고, 스레드끼리는 메모리를 공유할 수 있어 여러 가지 작업을 동시에 수행할 수 있다.

1.5.2 이벤트 루프란?

호출 스택과 이벤트 루프

호출 스택은 자바스크립트에서 수행해야 할 코드나 함수를 순차적으로 담아두는 스택이다. 호출 스택이 비어 있는지 여부를 확인하는 것이 이벤트 루프다. 태스트 큐란 실행해야 할 태스크(비동기 함수의 콜백 함수나 이벤트 핸들러 등)의 집합을 의미한다.

이벤트 루프의 역할은 호출 스택에 실행 중인 코드가 있는지, 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인하는 역할을 한다.

자바스크립트 코드 실행은 싱글 스레드에서 이루어지지만 이러한 외부 Web API 등은 모두 자바스크립트 코드 외부에서 실행되고 콜백이 태스크 큐로 들어가는 것이다.

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

이벤트 루프는 하나의 마이크로 태스크 큐를 갖고 있는데, 기존의 태스크 큐와는 다른 태스크를 처리한다. 대표적으로 Promise가 있다. 마이크로 태스크 큐는 기존 태크스 큐보다 우선권을 갖는다. 즉, setTimeoutsetIntervalPromise보다 늦게 실행된다.

  • 태스크 큐 : setTimeout , setInterval, setImmediate
  • 마이크로 태스크 큐 : process.nextTick , Promises , queueMicroTask , MutationObserver

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

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

리액트는 JSX 구문 내부에서 객체를 조작하거나 객체의 얕은 동등 비교 문제를 피하기 위해 객체 분해 할당을 하는 특징이 있다.

바벨은 자바스크립트의 최신 문법을 다양한 브라우저에서도 일관적으로 지원할 수 있도록 코드를 트랜스파일한다.

1.6.1 구조 분해 할당

구조 분해 할당이란 배열 또는 객체의 값을 말 그대로 분해해 개별 변수에 즉시 할당하는 것을 의미한다. 주로 선언문 없이 즉시 분해해 변수를 선언하고 할당하고 싶을 때 사용한다.

배열 구조 분해 할당

배열의 구조 분해 할당은 ,의 위치에 따라 값이 결정된다.

const array = [1, 2, 3, 4, 5]
 
const [first, second, third, ...arrayRest] = array 

객체 구조 분해 할당

객체 구조 분해 할당은 객체에서 값을 꺼내온 뒤 할당하는 것을 의미한다. 배열 구조 분해 할당과는 달리, 객체는 객체 내부 이름으로 꺼내온다는 차이가 있다.

const object = {
	a: 1,
	b: 1,
	c: 1,
	d: 1,
	e: 1,
}
 
const { a, b, c, ...objectRest } = object

이러한 방식은 리액트 컴포넌트인 props에서 값을 바로 꺼내올 때 매우 자주 쓰는 방식이다.

function SampleComponent({a, b}) {
	return a + b
}
SampleComponent({ a: 3, b: 5 })  // 8

변수에 있는 값으로 꺼내오는 계산된 속성 이름 방식도 가능하다.

const key = 'a'
const object = {
	a: 1,
	b: 1,
}
 
const { [key]: a } = object
 
// a = 1 

1.6.2 전개 구문

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

배열의 전개 구문

배열 간에 합성을 하려면 push(), concat(), splice() 등의 메서드를 사용해야 했다.

배열 내부에서 스프레드 문법(…연산자)을 사용하면 해당 배열을 마치 전개하는 것처럼 선언할 수 있다.

const arr1 = ['a', 'b']
const arr2 = [...arr1, 'c', 'd', 'e'] // ['a, 'b', 'c', 'd', 'e']

스프레드 문법(…연산자)은 기존 배열에 영향을 미치지 않고 배열을 복사하는 것도 가능하다.

const arr1 = ['a', 'b']
const arr2 = arr1
 
arr1 === arr2  // true (내용이 아닌 참조를 복사)
 
const arr1 = ['a', 'b']
const arr2 = [...arr1]
 
arr1 === arr2  // false (실제로 값만 복사)

객체의 전개 구문

객체에서도 배열과 비슷하게 사용이 가능하다. 주의할 점은 순서에 차이가 발생한다.

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

1.6.3 객체 초기자

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

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

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

Array.prototype.map

인수로 전달받은 배열과 똑같은 길이의 새로운 배열을 반환하는 메서드다. 리액트에서는 주로 특정 배열을 기반으로 어떠한 리액트 요소를 반환하고자 할 때 많이 사용한다.

const arr = [1, 2, 3, 4, 5]
const Elements = arr.map((item) => {
	return <Fragment key={item}>{item}</Fragment>
})

Array.prototype.filter

콜백 함수로 인수를 받는데, 이 콜백 함수에서 truthy 조건을 만족하는 경우에만 해당 원소를 반환하는 메서드다.

const arr = [1, 2, 3, 4, 5]
const evenArr = arr.filter((item) => item % 2 === 0)
// [2, 4]

Array.prototype.reduce

콜백 함수와 함께 초깃값을 추가로 인수를 받는데, 이 초깃값에 따라 배열이나 객체 등을 반환할 수 있는 메서드다.

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

Array.prototype.forEach

콜백 함수를 받아 배열을 순회하면서 단순히 그 콜백 함수를 실행하기만 하는 메서드다.

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

forEach는 아무런 반환값이 없다. 또, forEach는 실행되는 순간 에러를 던지거나 프로세스를 종료하지 않는 이상 이를 멈출 수 없다. break, return 을 이용해서 배열 순회를 멈출 수 없다.

1.6.5 삼항 조건 연산자

삼항 조건 연산자는 기존의 if 조건문을 간단하게 쓸 수 있다는 점에서 리액트에서 자주 쓰인다. 특히 JSX 내부에서 조건부로 렌더링하기 위해서 널리 쓰이는 방법이다.

function Component({ condition }) {
	return <>{condition ? '참' : '거짓'}</>
}

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

1.7.1 타입스크립트란?

타입스크립트는 타입 체크를 정적으로 런타임이 아닌 빌드 타임에 수행할 수 있게 해준다.

function test(a: number, b: number) {
	return a / b
}

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

any 대신 unknown을 사용하자

any는 정말 예외적인 경우에만 사용하는 것이 좋다. 아직 타입을 단정할 수 없는 경우에는 unknown을 사용하는 것이 좋다. unknown은 모든 값을 할당할 수 있는 이른바 top type으로, 어떠한 값도 할당할 수 있다.

타입 가드를 적극 활용하자

타입을 사용하는 쪽에서는 최대한 타입을 좁히는 것이 좋다. 조건문과 함께 타입 가드를 사용하면 타입을 효과적으로 좁힐 수 있다.

제네릭

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

인덱스 시그니처

인덱스 시그니처란 객체의 키를 정의하는 방식을 의미한다.