제어의 역전
내가 코드를 컨트롤 한다. vs 누군가의 규칙을 따라야 한다.
예를 들어 Next.js에서의 라우팅 처리와 리액트의 react-router-dom을 이용한 라우팅 처리.
또한 Next.js의 정해진 디렉토리 구조와 리액트의 규칙없는 디렉토리 구조.

Framework(프레임워크)
프레임워크는 어플리케이션을 개발시 코드의 품질, 필수적인 코드, 알고리즘, 암호화, 데이터베이스 연동 같은 기능들을 어느정도 뼈대가 구성된 것을 제공하도록 만들어 놓은 걸 활용하는게 프레임워크.
ex 여행이 어플리케이션이라고 할 때, 프레임워크는 여행(어플리케이션 개발)의 전체 일정(구조)과 방문지(구성 요소), 이동 수단(기능) 등을 미리 계획하고 정해둔다. 여행자는 이러한 관광상품 안에서 여행을 하게 되며, 많은 규약이 이미 관광상품에 의해 정해져 있음.
Library(라이브러리)
라이브러리는 특정 기능에 대한 API(도구 / 함수)를 모은 집합을 라이브러리라고 함.
라이브러리를 여행 중 스케줄에 공백이 있어 선택적으로 할 수 있는 당일 투어에 비유할 수 있다. 개발자는 전체 여행(어플리케이션 개발)의 계획을 스스로 세우면서, 특정한 작업을 수행하기 위해 라이브러리(당일 투어)를 선택할 수 있음. 라이브러리는 개발자가 특정 기능을 구현할 때 가져다 쓸 수 있는 도구로, 전체적인 어플리케이션의 구조에 대한 제약을 주지 않음.
그래서 리액트는 무엇인가
리액트(React)가 공식적으로는 '라이브러리'라고 분류되지만, 사용하다 보면 때때로 '프레임워크'처럼 느껴진다. 그 이유는 아래와 같다.
사이드 이펙트 추적 용이
데이터를 단방향으로 하기 때문에, 양방향 데이터를 사용하는 앵귤러 같은 거보다 사이드 이펙트를 추적하기 용이하다.
컴포넌트의 상태관리 용이성 및 재사용성

애플리케이션을 구현할 때, 아이템을 별도의 컴포넌트로 분리할 수 있습니다. 이렇게 함으로써, 각 아이템의 상태 관리가 용이해지고, 컴포넌트를 다른 프로젝트에서 재사용할 수 있다.
강력한 생태계와 커뮤니티
React Router와 같은 라이브러리를 사용하면, 싱글 페이지 애플리케이션(SPA)의 라우팅을 쉽게 구현할 수 있다.
강력한 성능 가상 DOM의 활용
리액트는 가상 DOM을 사용하여 실제 DOM과의 상호작용을 최소화 하여, 상태가 변경됐을 때 가상DOM에 변경사항을 먼저 적용한 후, 가상 DOM과 실제 DOM을 비교해서 실제 변경이 이루어진 부분만 실제DOM에 적용해서 불필요한 DOM조작을 줄여 성능을 향상시킴.
DOM이란?

여기서 트리의 구조는 루트, 노드, 부모-자식 관계로 구성됌.
루트 노드: 보통 document 객체로, 전체 문서의 시작점.
노드: 웹 페이지의 각 요소(Element), 속성(Attribute), 텍스트(Text) 등이 노드로 표현.
부모-자식 관계: 트리 안에서 각 노드는 다른 노드와 연결되어 있으며, 특정한 부모-자식 관계를 가진다
웹 페이지는 HTML을 통해 만들어지는데 DOM은 HTML 문서를 구조적으로 표현한 것이다. 즉 웹 안에 있는 모든 요소(ex. 태그)와 텍스트는 DOM에서 객체로 변환된다. DOM은 문서의 구조를 '트리' 구조로 나타낸다.
리액트의 V-DOM이 동작하는 방법

함수 컴포넌트
클래스 컴포넌트
함수형 컴포넌트라는 용어는 함수형 프로그래밍(Functional Programming)과 혼동을 일으킬 수 있다. 함수형 프로그래밍은 순수 함수(pure functions), 불변성(immutability), 고차 함수(higher-order functions) 등 특정 프로그래밍 패러다임을 하지만, 리액트의 함수 컴포넌트는 단순히 컴포넌트를 정의하기 위해 함수를 사용한다. 그리하여 2018년 이후 리액트 측에서도 함수형 컴포넌트 가 아닌 함수 컴포넌트로 명칭을 수정했다.
state
컴포넌트 내부에서 관리되고 컴포넌트의 상태를 나타내는 데이터. 사용자의 액션, 네트워크 응답과 같은 것에 의하여 변경될 수 있는 데이터.
동적인 데이터를 주로 관리하고, 사용자의 인터렉션 및 시간의 흐름에 따라 변경될 수 있는 데이터를 다루는데 사용한다.
props
부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터. 컴포넌트 간의 데이터를 전달할 때 사용함.
props는 Read-only이다. 자식 컴포넌트는 받은 props를 직접 변경할 수 없다.
import React, { useState } from 'react';
// 자식 컴포넌트: MessageDisplay
function MessageDisplay(props) {
// props.message를 사용하여 메시지를 표시합니다.
return <p>{props.message}</p>;
}
// 부모 컴포넌트: MessageInput
function MessageInput() { // MessageInput 컴포넌트는 내부 상태(message state)를 가지고 있으며, 사용자가 입력 필드에 텍스트를 입력할 때마다 이 상태를 업데이트
// state를 사용하여 사용자 입력을 관리합니다.
const [message, setMessage] = useState('');
// 사용자가 입력할 때마다 state를 업데이트합니다.
const handleChange = (event) => {
setMessage(event.target.value);
};
return (
<div>
<input type="text" value={message} onChange={handleChange} />
{/* MessageDisplay 컴포넌트에 message state를 props로 전달합니다. */}
<MessageDisplay message={message} /> // MessageDisplay 컴포넌트는 전달받은 props를 직접 변경할 수 없으며, 오직 표시만 담당
</div>
);
}
export default MessageInput;
props는 기본적으로 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는데 사용한다.
자식에서 부모로 props로 전달하는 것은 불가능 하지만, 자식 컴포넌트에서 부모 컴포넌트의 함수롤 호출함으로써 간접적인 데이터를 전달할 수 있다.
import React, { useState } from 'react';
// 자식 컴포넌트: UpdateName
function UpdateName({ onNameChange }) {
return (
<input
type="text"
placeholder="이름 입력"
onChange={(e) => onNameChange(e.target.value)}
/>
);
}
// 부모 컴포넌트: UserComponent
function UserComponent() {
const [name, setName] = useState('');
const handleNameChange = newName => {
setName(newName);
};
return (
<div>
<h1>사용자 이름: {name}</h1>
{/* 자식 컴포넌트로 함수를 props로 전달. */}
<UpdateName onNameChange={handleNameChange} />
</div>
);
}
export default UserComponent;
Flux는 페이스북에 의해 개발된 아키텍처. 단방향 데이터에 대한 흐름을 가지고, 데이터 흐름을 예측 가능하고 이해하기 쉽다. Flux가 개발된 배경은 페이스북에서 크고 복잡한 웹 어플리케이션에서 아래와 MVC패턴이 확장불가능 하다고 판단하여 FLUX 패턴을 개발했다.

FLUX 패턴

사용자의 액션은 디스패쳐에 의해 컨트롤 된다. 디스패처가 스토어를 업데이트하고 변경된 스토어에 대한 뷰를 리렌더링 한다.
뷰에서는 스토어에 직접 접근하지 않으며, 디스패처로 다시 액션을 보내고 스토어를 업데이트한 뒤, 다시 뷰를 리렌더링하는 단방향적 구조를 가짐.
FLUX 패턴은 이러한 단방향적인 데이터 흐름 구조를 통해 어떤 액션이 디스패처에 의해 어떤 결과를 낳고 변화되는지 명확히 파악할 수 있다.
// store.js
// zustand에서 store 만드는 방법. 애플리케이션의 상태를 저장하고 관리한다.
import create from 'zustand';
const useStore = create(set => ({
count: 0, // 초기 상태
increase: () => set(state => ({ count: state.count + 1 })),
decrease: () => set(state => ({ count: state.count - 1 })),
}));
export default useStore;
// Counter.js
// 스토어를 사용하는 컴포넌트를 생성함. useStore 훅을 사용하여 스토어의 상태에 접근하고, increase와 decrease 액션을 트리거 할 수 있다.
// zustand에선 dispatcher가 명확하지 않고 action만으로 가능하여 더 직관적임
import React from 'react';
import useStore from './store';
function Counter() {
const { count, increase, decrease } = useStore();
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={increase}>증가</button>
<button onClick={decrease}>감소</button>
</div>
);
}
export default Counter;
리액트에서 상태의 불변성 유지하는 원칙은 매우 중요함 불변성을 유지해 성능을 최적화 할 수 있다.
불변성이란 상태를 직접 변경하지 않고, 상태가 업데이트될 때 새로운 객체를 생성해 사용한다는 것을 의미함.
리액트에서는 컴포넌트의 상태가 업데이트되었는지를 판단하기 위해 얕은 비교(shallow comparison)를 한다. 즉 객체 내부의 프로퍼티 값이 변경되어도 객체의 참조가 같다면 변경사항을 감지할 수 없기 떄문에 새로운 객체를 반환하여 얕은 비교를 할 수 있게 해주어야 함. 따라서 상태를 업데이트할 때 기존 상태 객체를 직접 변경(modify)하는 것이 아니라, 스프레드 연산자(spread operator)를 사용하여 기존 상태의 복사본을 만들고 그 복사본에 변경사항을 적용하여 새로운 객체를 생성하는 방식을 사용해야 한다.
// 초기 상태
const prevState = { name: 'John', age: 30 }; // 메모리 주소 : 0x1234
// 새로운 상태
const newState = { ...prevState, age: 31 }; // 메모리 주소 : 0x4567
// 물리적으로 다른 위치에 존재하기 때문에 리액트가 상태 변화를 감지한다.
순수 함수(Pure Function)
순수 함수는 입력 값이 같으면 항상 같은 출력 값을 반환하며, 외부 상태를 변경하지 않는다.
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 항상 5를 반환
불순 함수(Impure Function)
불순 함수는 외부 상태에 의존하거나 외부 상태를 변경할 수 있다.
let c = 10;
function addAndChangeC(a, b) {
c = a + b; // 외부 상태인 c를 변경
return c;
}
console.log(addAndChangeC(2, 3)); // 5를 반환하지만, 외부 변수 c의 값을 변경
리액트 컴포넌트의 라이프사이클은 크게 마운팅(Mounting), 업데이트(Updating), 언마운팅(Unmounting), 그리고 에러 처리(Error Handling) 단계로 구분
마운팅(Mounting)
마운팅 단계는 컴포넌트의 인스턴스가 생성되어 DOM에 삽입되는 단계
업데이트(Updating)
업데이트 단계는 컴포넌트의 props나 state가 변경되어 컴포넌트가 재렌더링되는 단계
언마운팅(Unmounting)
언마운팅 단계는 컴포넌트가 DOM에서 제거되는 단계
에러 처리(Error Handling)
에러 처리 단계는 컴포넌트 렌더링, 라이프사이클 메서드, 또는 자식 컴포넌트의 생성자에서 발생한 에러를 처리
useState
useState는 함수형 컴포넌트에서 상태를 관리할 수 있게 한다. 초기 상태 값을 인자로 받아, 상태 값과 그 상태를 업데이트하는 함수를 반환.
const [count, setCount] = useState(0);
useEffect
클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount에 해당하는 작업을 수행하거나, 데이터 패칭 등의 작업을 수행할 수 있음
useEffect(() => {
// effect를 수행하는 로직
return () => {
// cleanup 로직
};
}, [/* 의존성 배열 */]);
useReducer
useReducer는 복잡한 상태 로직을 관리할 때 useState보다 더 편리하게 사용할 수 있다. useReducer는 상태 업데이트 로직을 컴포넌트 바깥으로 분리할 수 있다.
const [state, dispatch] = useReducer(reducer, initialState);
useMemo
useMemo는 계산 비용이 많이 드는 함수의 반환값을 메모이제이션(기억)함으로써 성능을 최적화할 수 있게 해줌
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
useCallback은 메모이제이션된 콜백을 반환한다. 주로 자식 컴포넌트에 props로 콜백을 전달할 때, 불필요한 리렌더링을 방지하기 위해 사용.
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
useRef
useRef는 리액트 컴포넌트에서 DOM 요소를 직접 선택할 수 있게 해주는 훅
const myRef = useRef(initialValue);
커스텀 Hooks
커스텀 훅은 여러 컴포넌트에서 재사용할 수 있는 로직을 캡슐화하기 위해 사용자가 직접 만든 훅. 커스텀 훅을 사용하면 컴포넌트 로직을 재사용 가능한 함수로 분리하여 코드의 가독성과 유지보수성을 높일 수 있다.
function useMyCustomHook() {
const [someState, setSomeState] = useState(initialState);
// 커스텀 로직
return someState;
}
useMemo
계산 비용이 많이 드는 연산의 결과값을 메모이징하기 위해 사용. 즉, 연산 결과값을 재사용함으로써 성능을 최적화 한다.
useCallback
특정 함수를 메모이징하기 위해 사용됩니다. 즉, 함수의 재생성을 방지함으로써 성능을 최적화 한다.
둘의 차이
useMemo는 연산의 결과값을 메모이징하는 데 사용되며, useCallback은 함수 자체를 메모이징하는 데 사용한다.
useMemo는 메모이징된 값(value)을 반환하고, useCallback은 메모이징된 함수(function)를 반환한다.
setState는 비동기 동작이다. 아래와 같은 장점을 가진다.
setState의 비동기적 동작
setState 함수가 호출되면, 리액트는 새로운 상태를 큐에 넣고, 이벤트 처리가 완료된 후 적절한 시점에 상태 변경을 일괄적으로 처리한다.
setState가 비동기적인 이유
성능 최적화: 여러 setState 호출을 일괄적으로 처리함으로써, 리액트는 불필요한 렌더링을 줄이고, 애플리케이션의 성능을 향상.
일관성 유지: 상태 변경 로직을 일괄 처리함으로써, 리액트는 애플리케이션의 상태가 일관되게 유지되도록 보장.
useMemo와 useCallback 훅 사용
useMemo나 useCallback을 적절히 활용하여 최적화 할 수 있다.
리스트 렌더링 최적화
대량의 데이터를 리스트로 렌더링할 때는 key 속성을 올바르게 설정하여 리액트가 각 항목을 유일하게 식별할 수 있도록 한다. 데이터 변경 시 효율적인 재렌더링을 할 수 있다.
이미지와 리소스 지연 로딩(Lazy loading)
React.lazy와 Suspense를 사용하여 컴포넌트 또는 리소스를 필요할 때만 로드하도록 할 수 있다. 이를 통해 초기 로딩 시간을 단축할 수 있음.
코드 분할(Code Splitting)
웹팩 같은 번들러를 사용하여 애플리케이션을 여러 청크로 분할하고, 사용자가 필요로 하는 시점에만 특정 청크를 로드할 수 있도록 한다. 이는 애플리케이션의 초기 로딩 시간을 줄일 수 있다.
// 함수형 컴포넌트 예시
function MyComponent() {
const handleClick = () => {
console.log('버튼이 클릭되었습니다!');
};
return (
<button onClick={handleClick}>클릭하세요</button>
);
}
// 이벤트 핸들러에 인자 전달하기
function MyComponent() {
const handleClick = (message, event) => {
event.preventDefault();
console.log(message);
};
return (
<button onClick={(event) => handleClick('버튼 클릭됨', event)}>클릭하세요</button>
);
}

SPA는 Single Page Application의 약자로, 한 개의 페이지로 이루어진 웹 애플리케이션을 의미합니다. 전통적인 웹 애플리케이션과는 달리, SPA는 최초 한 번 페이지를 로드한 후, 사용자와의 상호작용에 따라 필요한 데이터만을 동적으로 서버로부터 가져와서 페이지를 갱신한다. 이로 인해, 페이지를 새로 고침할 필요 없이 사용자에게 빠르고, 부드러운 경험을 제공할 수 있다.

SPA의 주요 특징
SPA의 장단점
장점
단점
초기 로딩 지연: 최초에 애플리케이션 전체를 로드해야 하므로 초기 로딩 시간이 길어질 수 있다.
SEO 최적화 문제: 클라이언트 사이드 렌더링 방식은 검색 엔진 최적화(SEO)에 어렵다. (최근엔 서버 사이드 렌더링(SSR)이나 정적 사이트 생성(Static Site Generation) 같은 기술로 대응 가능)

SSR은 서버 사이드 렌더링(Server Side Rendering)의 약자로, 클라이언트 사이드 렌더링(CSR)과 대비되는 개념으로, 웹 애플리케이션의 성능과 SEO(검색 엔진 최적화) 측면에서 중요한 역할을 할 수 있다.
SSR의 장점
SSR의 단점
SEO(Search Engine Optimization, 검색 엔진 최적화)는 웹사이트나 웹 페이지를 검색 엔진의 결과 페이지에서 더 높은 순위에 나타나게 하는 기술 및 전략을 말합니다. 목표는 웹사이트에 대한 가시성을 높여 더 많은 방문자를 유치하는 것이며, 이는 비즈니스에 매우 중요한 요소이다.
CSR의 SEO 문제점
SSR의 SEO 이점
구글의 크롤러를 제외한 많은 크롤러는 HTML을 분석해서 크롤링 하는데, SSR로 구현하여 SEO을 향상할 수 있다.
하이드레이션(hydration)은 주로 SSR (Server Side Rendering)과 함께 사용되는 개념으로, 클라이언트 사이드에서 초기에 서버로부터 받은 정적 HTML에 대해 추가적인 JavaScript를 연동하여 동적인 웹 애플리케이션으로 활성화하는 과정을 뜻한다. 이 과정을 통해 정적인 페이지가 동적인 상호작용을 포함한 SPA(Single Page Application)처럼 동작한다.
SSR 지원
PeerStudy도메인에 업로드된 컨텐츠가 잘 노출되도록 하기 위해서 SSR이 필요했다. 그래서 도입하게 됨
간편한 라우팅 App Router 기반 라우팅 시스템을 통해, 페이지를 추가하거나 수정하는 것이 매우 간단하며, 동적 라우팅도 지원하기 때문에 사용함.
Suspense는 React에서 비동기 로직을 다룰 때 사용하는 기능이다. 비동기 작업이 완료될 때까지 로딩 표시와 같은 대체 컨텐츠를 표시할 수 있게 할 수 있다.
// fetchData.js
const fetchData = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve("데이터 로딩 완료!");
}, 2000); // 2초 후 데이터 로딩 완료
});
};
export default fetchData;
// AsyncComponent.js
import React, { useEffect, useState } from 'react';
import fetchData from './fetchData';
const AsyncComponent = () => {
const [data, setData] = useState("로딩 중...");
useEffect(() => {
fetchData().then(data => setData(data));
}, []);
return <div>{data}</div>;
};
export default AsyncComponent;
// App.js
import React, { Suspense } from 'react';
const AsyncComponent = React.lazy(() => import('./AsyncComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>로딩 중...</div>}>
<AsyncComponent />
</Suspense>
</div>
);
}
export default App;
LCP가 뭔가요?
LCP(Large Contentful Paint)는 웹 페이지에서 가장 큰 콘텐츠 요소를 로드하고 사용자가 볼 수 있는 데 걸리는 시간을 측정하는 성능 지표.
LCP를 개선하기 위해 lazy load, 이미지 크기 및 형식 최적화, CDN(콘텐츠 전송 네트워크) 등의 기술을 사용하여 이미지 및 비디오와 같은 대용량 콘텐츠 요소의 로드를 최적화할 수 있다.
FCP가 뭔가요?
FCP(First Contentful Paint)는 브라우저가 웹 페이지의 첫 번째 콘텐츠 요소를 렌더링하는 데 걸리는 시간을 측정하는 성능 지표. 이 요소는 텍스트, 이미지와 같은 것들이 있다.
FCP를 개선하기 위해 HTML, CSS 및 JavaScript 파일의 크기를 줄이고 서버에 대한 요청 수를 최소화하여 웹 페이지 로드를 최적화할 수 있다.