6월 3일부터 2주 간 원티드에서 진행하는 프리온보딩 챌린지가 시작한다. 자바스크립트, React, Next.js 위주의 진행.
본격적인 시작에 돌입하기 전 가벼운(그러나 마냥 가볍지 않은..) 웜업 과제가 주어졌다.
문자열 표기법으로서 백틱(``
)을 사용해 문자열을 여러 줄로 작성 가능하다. 또한, ${}
를 활용해 변수와 표현식을 문자열 내에 포함할 수 있다.
const name = 'everyone';
const greeting = `Hello, ${name}!`;
console.log(greeting) // Hello, everyone!
함수는 호출하는 시점에서 this를 동적으로 바인딩한다. 그래서 함수를 어떻게 호출하느냐에 따라 this가 정해진다. 상황별로 정리하면 아래와 같다.
일반 함수로서 호출 ➡️ 전역객체 window
메서드로서 호출 ➡️ 함수명 앞의 객체
ex. obj.methodA()
의 경우 obj
가 this
생성자 함수로서 호출 ➡️ 생성할 인스턴스
콜백 함수로서 호출 ➡️ 해당 콜백 함수의 제어권을 넘겨받은 함수 or 전역객체
✅ 내부 함수로서 호출 ➡️ 전역객체 window
문제가 되는 건 가장 마지막에 있는 내부 함수로서 호출할 경우이다. 함수의 내부 함수라면 당연히 해당 함수의 외부 함수에 대해 this를 바인딩 할 것이라고 예측하기 쉽다. 하지만 실제로는 외부 함수 대신 전역 객체를 this로 가리킨다.
예상치 못하게 동작할 수 있으므로 call, apply, bind 메서드를 활용해 명시적으로 this를 바인딩 할 수 있었다. 하지만 이보다 더 간단한 방법이 ES6에 도입되었으니, 바로 화살표 함수다.
화살표 함수를 사용하면 this를 별도로 생성하지 않고, 상위 스코프의 this를 정적 바인딩한다. 이는 클로저 개념과 연관된다. 자바스크립트 엔진은 현재 스코프에서 탐색할 수 있는 식별자가 없을 시 스코프 체인에 의해 상위 스코프를 탐색한다. 즉 현재 스코프에는 this가 존재하지 않으므로 상위 스코프로 범위를 넓혀 this를 찾고, 바인딩하는 것이다.
일반 함수의 유사 배열 객체인 arguments
를 지원하지 않는다. 함수에 전달된 모든 인수를 한꺼번에 사용하려면 나머지 파라미터를 사용해야 한다.
한 줄로 작성할 경우 return을 생략할 수 있고, function 키워드를 대신해 화살표 =>
로 처리하므로 보기에 더 직관적이다.
위 특성을 비롯해 가독성 개선에 도움을 주는 만큼, 컨텍스트가 없는 짧은 코드 작성 시에 적절하다.
const person = {
name: 'yunhye',
greeting: () => {
console.log(`Hello, I'm ${name}.`);
}
}
person.greeting(); // Hello, I'm yunhye.
배열이나 객체의 요소를 펼쳐서 개별 요소로서 활용(복사, 결합 등)하고 싶을 때 적절하다. 배열이나 객체 앞에 ...
를 붙여서 사용한다.
// 배열의 요소로 사용
const arr = [1, 2, 3, 4, 5];
const max = Math.max(...arr);
console.log(max); // 5
// 객체의 결합
const obj1 = {a: 1, b: 2};
const obj2 = {c: 3, d: 4};
console.log({...obj1, ...obj2}); // {a: 1, b: 2, c: 3, d: 4}
React.Component를 상속받는다. constructor를 만들어 state를 사용할 수 있고, 메서드(componentDidMount
, componentDidUpdate
, componentWillUnmount
)를 통해 라이프 사이클을 활용한다.
React의 라이프 사이클
React의 모든 컴포넌트는 라이프 사이클을 가지고 있다. 이는 컴포넌트의 마운트(생성) - 업데이트 - 언마운트(제거)를 의미하는데, 특정 주기에 특정 동작을 가능케하여 세밀하게 데이터 처리를 할 수 있다.
import { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('카운트 증가');
}
}
handleClick = () => {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<h1>Counter</h1>
<button onClick={this.handleClick}>{this.state.count}</button>
</div>
);
}
}
export default Counter;
클래스형 컴포넌트는 여러 번거로움이 많다. state를 사용하기 위해서 constructor를 정의하고, 화면에 컴포넌트를 표시하기 위해서 render()
를 필수적으로 사용하고, 또 props나 state에 this
로 접근한다.
이러한 불편사항을 개선한 것이 함수형 컴포넌트다.
그 자체로 렌더 함수이기 때문에 render()
가 필요 없다. 빌드 시간도, 메모리 자원도 클래스형에 비해 적다.
state와 라이프 사이클은 클래스형 컴포넌트에서만 사용할 수 있었지만, React hooks가 도입된 후로 함수형 컴포넌트에서도 사용이 가능해졌다. 각각 useState
와 useEffect
를 활용한다.
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('카운트 증가');
}, [count]);
return (
<div>
<h1>Counter</h1>
<button onClick={() => setCount(count + 1)}> {count}</button>
</div>
);
}
위에 작성한 클래스형 컴포넌트를 똑같이 함수형으로 바꿨을 뿐인데 눈에 띄게 간결하다. React 팀에서 함수형 컴포넌트와 hooks의 사용을 적극 권장할 정도이니, 편리성이 새삼 상당하다.
재사용성 향상:
state와 라이프 사이클을 한 곳에 작성 가능해서 컴포넌트 단위로 로직을 처리할 수 있게 되었다.
성능 최적화:
불필요한 리렌더링을 방지하고자 useCallback
과 useMemo
, Memo
등 함수 메모이제이션이 등장했다.
app 디렉터리에 새로운 디렉터리를 생성하면 그 자체로 라우팅이 된다. 중첩 라우팅이 편리하고 레이아웃 구조가 직관적이다. 동적 라우팅 또한 가능하다.
/app
/(home) --> /
/movie --> /movie
/layout.tsx
/[id] --> /movie/[id]
/page.tsx
소괄호()
는 라우팅으로 인식하지 않으므로 해당 위치의 파일들을 정리하기 용이하다.
이전 방식과 마찬가지로 대괄호[]
는 때마다 달라지는 URL을 동적으로 생성하게 해준다.
layout
으로 정의한 파일은 해당 라우팅에 속한 모든 URL에 적용된다. 예시로 말하자면, '/movie' 이후의 모든 라우팅(Ex. /movie/13, /movie/3663, ...)은 해당 레이아웃의 영향을 받는다.
이처럼 라우팅을 훨씬 세밀하고 유연하게 작성할 수 있고, 각 라우트에 대한 레이아웃을 개별적으로 설정할 수 있다. 또한, 서버 컴포넌트가 getServerSideProps()
, getStaticProps()
, getStaticPaths()
를 모두 대체하면서 데이터 페칭과 컴포넌트 렌더링이 통합되었다.
복잡한 라우팅 설계를 간단하게 처리할 수 있다.
서버 메서드 대신 이를 컴포넌트화 하면서 로직의 통합과 분리, 재사용이 유연해졌다고 느꼈다. 예를 들어, 보안이 필수적인 데이터를 환경 변수 등을 사용해 숨길 필요 없이 컴포넌트 자체로 분리된 점이 편리해 보인다.
SSR과 SSG 모두 pre-rendering 한다는 점은 동일하지만, 그 방식이 동적인지 정적인지에 따라 내용이 달라진다.
서버 단의 Pre-rendering
사용자가 페이지를 요청할 때마다 서버 측에서 HTML 파일을 먼저 렌더링하고, 자바스크립트 파일 등과 함께 클라이언트에 전송한다.
Next.js에서의 SSR은 기본 HTML 파일을 렌더한 후, 자바스크립트 파일을 실행하며 동적으로 생성한다. 이 과정을 hydration이라고 명명했다. 첫 렌더링 이후로는 동적 생성을 하기 때문에 사용자의 상호작용에 따라 불러올 데이터가 다를 때, 혹은 최신 정보로 업데이트 되는 환경에 적절하다.
Ex. 프로필 페이지, 사용자 대시보드 등
빌드 시, 모든 페이지를 미리 렌더링하여 정적 HTML 파일을 생성한다. 그래서 요청이 들어올 때 새 페이지를 렌더링하는 게 아니라 기존에 렌더링 해둔 페이지를 전송한다. 따라서, 언제나 동일한 정보를 반환하는 경우에 유용하다.
Ex. 쇼핑몰 제품 리스트, 블로그 포스팅, 마케팅 페이지 등
문제 자체가 어려운 건 아니지만 범위가 방대한 질문이라 파고 들면 파고들수록 쓸 거리가 늘어났다. 정리하다 보니 내가 부실하게 알고 있는 점들도 발견했고. 특히 SSR과 SSG에 대한 구분을 제대로 찾아본 적이 없었음을 깨달았다.
this는 코어 자바스크립트를 2번씩 읽었는데 이제야 좀 이해가 되는 듯하다! 열심히 해봐야지.