1. 컴포넌트 기반 아키텍처
React는 UI를 독립적이고 재사용 가능한 컴포넌트로 나누어 개발할 수 있습니다. 이렇게 나누어진 컴포넌트는 서로 독립적으로 관리되며, 부모-자식 관계로 데이터를 주고받습니다.
예제: 컴포넌트 분리
const Button =({ label, onClick })=> {
return (
<button onClick={onClick}>
{label}
</button>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<Button label="Increment" onClick={() => setCount(count + 1)} />
<Button label="Decrement" onClick={() => setCount(count - 1)} />
</div>
);
}
- App 컴포넌트는 Button 컴포넌트를 사용해 label, onClick props를 전달 후, 생성한다.
- Button 컴포넌트는
서로 다른 버튼
이지만 하나의 컴포넌트 코드로 재사용
할 수 있다.
2. Virtual DOM을 활용한 고성능 렌더링
React는 Virtual DOM을 사용하여 변경 사항을 추적하고, 실제 DOM 조작을 최소화하여 성능을 최적화한다.
예제: Virtual DOM에서의 렌더링 비교
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
console.log('Rendered with count:', count); // Virtual DOM에서 변경 사항 확인
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
설명:
- 상태 변경 시 Virtual DOM에서 새 상태를 반영한 컴포넌트를 생성하고, 이전 Virtual DOM과 비교.
- 변경된 부분만 실제 DOM에 업데이트하므로 성능이 최적화.
- 브라우저 콘솔에서 상태 변경 시마다 컴포넌트가 다시 렌더링되는 것을 확인 가.
3. 단방향 데이터 흐름
React는 상위 컴포넌트에서 하위 컴포넌트로만 데이터를 전달합니다. 이를 통해 데이터 흐름이 명확해지고 관리가 쉬워집니다.
예제: 부모-자식 간 데이터 전달
// Child.js: 하위 컴포넌트
import React from 'react';
function Child({ message }) {
return <h2>{message}</h2>;
}
export default Child;
// App.js: 상위 컴포넌트
import React, { useState } from 'react';
import Child from './Child';
function App() {
const [message, setMessage] = useState('Hello, React!');
return (
<div>
<Child message={message} />
<button onClick={() => setMessage('Data Flow Updated!')}>
Update Message
</button>
</div>
);
}
export default App;
설명:
App
컴포넌트가 Child
컴포넌트로 message
를 전달한다.
- 단방향 데이터 흐름으로 인해, 데이터는 항상 부모에서 자식으로만 흐릅니다.
- Child 컴포넌트로 넘어온 message를 변경하고 싶다면, 부모의 setMessage 를 props로 받아와 실행하면된다.
React의 단점
단점 1: 초기 로딩 시간 해결 (코드 스플리팅)
React 애플리케이션은 초기 로딩 시간 증가 문제가 있지만, 코드 스플리팅을 통해 이를 해결할 수 있습니다.
예제: React의 코드 스플리팅
import React, { Suspense, lazy } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
<h1>Welcome to My App</h1>
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
export default App;
설명:
- lazy 를 통해
HeavyComponent
는 사용될 때만 로드된다.
Suspense
를 사용하여 로드 중인 상태를 처리할 수 있다.
- 이를 통해 초기 로딩 시간을 줄이고 성능을 개선할 수 있다.
단점 2: SEO 문제 해결 1(ReactDOMServer)
리액트의 ReactDOMServer 라이브러리를 통해 SSR을 직접 구현.
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';
const app = express();
app.get('*', (req, res) => {
const appHtml = ReactDOMServer.renderToString(<App />);
const html = `
<!DOCTYPE html>
<html>
<head><title>React SSR</title></head>
<body>
<div id="root">${appHtml}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
res.send(html);
});
app.listen(3000, () => console.log('Server is running on http://localhost:3000'));
- 서버에서 리액트 컴포넌트를 해석하는 라이브러리
ReactDOMServer
을 이용해 HTML 파일로 변경한다.
단점 2: SEO 문제 해결 2(Next.js 활용)
CSR 방식에서 발생하는 SEO 문제는 Next.js를 사용하여 서버 사이드 렌더링(SSR)을 도입함으로써 해결 가능합니다.
예제: Next.js에서 SSR 사용
// pages/index.js
import React from 'react';
export default function Home({ message }) {
return (
<div>
<h1>{message}</h1>
</div>
);
}
export async function getServerSideProps() {
return {
props: {
message: 'This page is rendered on the server!',
},
};
}
설명:
getServerSideProps
를 사용해 서버에서 데이터를 가져와 페이지를 렌더링한다.
- HTML 콘텐츠가 완전히 생성되어 클라이언트에 전달되므로 SEO와 초기 로딩 문제를 동시에 해결할 수 있다.