React Router는 리액트 컴포넌트로 페이지를 나눌 때 사용하는 라이브러리를 의미한다.
Router는 리액트 라우터에서 사용하는 데이터를 갖고 있는다. 현재 주소, 페이지 기록 등이 포함된다. 리액트 라우터를 사용하고 싶으면 Router 안에서 사용해야 한다.
JavaScript에서의 switch...case문을 떠올리면 좋다.
Link는 a 태그와 비슷하다.
Node.js를 설치하면 npm(Node Package Manager)라는 프로그램도 함께 설치된다. 패키지라는 건 자바스크립트 모듈을 모아 놓은 묶음 같은 것을 의미한다. npm은 패키지를 설치하거나 삭제하는 것처럼 패키지를 관리하는 프로그램이다. 패키지를 설치하면 미리 작성된 자바스크립트 모듈을 가져다 쓸 수 있게 된다.
패키지를 설치하려면 npm install <패키지 이름> 명령어를 실행하면 된다. npm install react-router-dom@6을 입력한다. 뒤에 있는 @6은 패키지 버전을 6점대로 설치하겠다는 의미이다. package.json 파일에 dependencies 아래에 react-router-dom이 추가되며, node_modules/ 폴더에 react-router-dom이라는 폴더가 생긴다.
VSCode에서 자동완성 기능을 사용하다 보면 가끔 react-router-dom이 아닌 react-router라는 패키지에서 임포트할 때가 있다. react-router라는 패키지는 리액트 라우터를 만들는 개발자들이 내부적으로 사용하는 패키지이다.
import { BrowserRouter } from 'react-router-dom';
import App from './components/App';
import HomePage from './pages/HomePage';
function Main() {
return (
<BrowserRouter>
<App>
<HomePage />
</App>
</BrowserRouter>
);
}
export default Main;
최상위 컴포넌트인 Main.js 파일에 가서 라우터 컴포넌트를 적요하면 된다. BrowserRouter라는 컴포넌트를 불러왕서 컴포넌트 전체를 감싼다.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import App from './components/App';
import HomePage from './pages/HomePage';
import CoursePage from './pages/CoursePage';
import CourseListPage from './pages/CourseListPage';
import WishlistPage from './pages/WishlistPage';
function Main() {
return (
<BrowserRouter>
<App>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="courses" element={<CourseListPage />} />
<Route
path="courses/react-frontend-development"
element={<CoursePage />}
/>
<Route path="wishlist" element={<WishlistPage />} />
</Routes>
</App>
</BrowserRouter>
);
}
export default Main;
element에는 jsx를 지정해 주어야 한다.
function Nav() {
return (
<div className={styles.nav}>
<Container className={styles.container}>
<Link to="/">
<img src={logoImg} alt="Codethat Logo" />
</Link>
<ul className={styles.menu}>
<li>
<Link to="/courses">카탈로그</Link>
</li>
<li>커뮤니티</li>
<li>
<UserMenu />
</li>
</ul>
</Container>
</div>
);
}
function getLinkStyle({ isActive }) {
return {
textDecoration: isActive ? 'underline' : '',
};
}
function Nav() {
return (
<div className={styles.nav}>
<Container className={styles.container}>
<Link to="/">
<img src={logoImg} alt="Codethat Logo" />
</Link>
<ul className={styles.menu}>
<li>
<NavLink style={getLinkStyle} to="/courses">
카탈로그
</NavLink>
</li>
<li>
<NavLink style={getLinkStyle} to="/questions">
커뮤니티
</NavLink>
</li>
<li>
<UserMenu />
</li>
</ul>
</Container>
</div>
);
}
NavLink는 style prop으로 함수를 지정해 줄 수 있다. isActive는 현재 페이지 주소가 Link에 해당하면 true를 리턴해주는 불린형이다.
function Main() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<HomePage />} />
<Route path="courses">
<Route index element={<CourseListPage />} />
<Route path="react-frontend-development" element={<CoursePage />} />
</Route>
<Route path="questions" element={<QuestionListPage />} />
<Route path="questions/616825" element={<QuestionPage />} />
<Route path="wishlist" element={<WishlistPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
// App
function App() {
return (
<>
<Nav className={styles.nav} />
<div className={styles.body}><Outlet /></div>
<Footer className={styles.footer} />
</>
);
}
function Main() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<HomePage />} />
<Route path="courses">
<Route index element={<CourseListPage />} />
<Route path=":courseSlug" element={<CoursePage />} />
</Route>
<Route path="questions">
<Route index element={<QuestionListPage />} />
<Route path="616825" element={<QuestionPage />} />
</Route>
<Route path="wishlist" element={<WishlistPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
// CoursePage
function CoursePage() {
const { courseSlug } = useParams();
const course = getCourseBySlug(courseSlug);
const courseColor = getCourseColor(course?.code);
...
:courseSlug라는 변수로 페이지 경로를 받는 것을 파라미터라고 부른다. 이렇게 하면 페이지 경로가 달라질 때마다 파라미터 값도 달라지므로, 같은 컴포넌트라도 파라미터 값에 따라 데이터만 다르게 보여줄 수 있게 된다.
// Route 맨 마지막에 추가
<Route path="*" element={<NotFoundPage />} />
// NotFound
function NotFoundPage() {
return (
<Container className={styles.container}>
<Warn
variant="big"
title="존재하지 않는 페이지에요."
description="올바른 주소가 맞는지 다시 한 번 확인해 주세요."
/>
<div className={styles.link}>
<Link to="/">
<Button as="div">홈으로 가기</Button>
</Link>
</div>
</Container>
);
}
function CoursePage() {
const { courseSlug } = useParams();
const course = getCourseBySlug(courseSlug);
const courseColor = getCourseColor(course?.code);
if (!course) {
return <Navigate to="/courses" />;
}
...
function CourseListPage() {
const [searchParam, setSearchParam] = useSearchParams();
const initKeyword = searchParam.get('keyword');
const [keyword, setKeyword] = useState(initKeyword || '');
const courses = getCourses(initKeyword);
const handleKeywordChange = (e) => setKeyword(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
setSearchParam(keyword ? { keyword } : {});
};
...
function CoursePage() {
const navigate = useNavigate();
const { courseSlug } = useParams();
const course = getCourseBySlug(courseSlug);
const courseColor = getCourseColor(course?.code);
if (!course) {
return <Navigate to="/courses" />;
}
const headerStyle = {
borderTopColor: courseColor,
};
const handleAddWishlistClick = () => {
addWishlist(course?.slug);
navigate('/wishlist');
};
...
리액트에서 경로에 따라 페이지를 나누도록 해주는 라이브러리이다. 리액트스러운 방법으로 컴포넌트를 사용해서 페이지를 나누는 것이 특징이다.
import { BrowserRouter } from 'react-router-dom';
function App() {
return <BrowserRouter> ... </BrowserRouter>;
}
리액트 라우터를 사용하려면 반드시 라우터라는 컴포넌트가 필요하다. BrowserRouter 컴포넌트를 최상위 컴포넌트에서 감싸주면 모든 곳에서 사용할 수 있다.
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="posts" element={<PostListPage />} />
<Route path="posts/1" element={<PostPage />} />
</Routes>
Routes 컴포넌트 안에다가 Route 컴포넌트를 배치해서 각 페이지를 나눠줄 수 있다. 이때 Routes 안에서는 위에서부터 차례대로 Route를 검사한다. 현재 경로와 path prop이 일치하는 Route를 찾는다.
<Link to="/posts">블로그</Link>
리액트 라우터에서는 <a> 태그 대신에 Link 컴포넌트를 사용한다. to라는 prop으로 이동할 경로를 정해주면 된다.
<Routes>
<Route path="/"><HomePage /></Route>
<Route path="posts" element={<PostLayout />} >
<Route index element={<PostListPage />} />
<Route path="1" element={<PostPage />} />
</Route>
</Routes>
Route 컴포넌트 안에다가 Route 컴포넌트를 배치하면 된다. 이때 하위 페이지에서 최상위 경로에 해당하는 경로는 path prop이 아니라 index라는 prop을 사용하면 된다.
import { Outlet } from 'react-router-dom';
function PostLayout() {
return (
<div>
<h1>블로그</h1>
<hr />
<Outlet />
</div>
);
}
export default PostLayout;
이때 부모 Route 컴포넌트에 element를 지정하고, Outlet이라는 컴포넌트를 활용하면 공통된 레이아웃을 지정해줄 수 있다.
<Routes>
<Route path="/"><HomePage /></Route>
<Route path="posts" element={<PostLayout />} >
<Route index element={<PostListPage />} />
<Route path=":postId" element={<PostPage />} />
</Route>
</Routes>
콜론(:)으로 시작하는 문자열을 사용하면 경로에 파라미터를 지정할 수 있다. 예를 들어 /posts/:postId라는 경로는 /posts/123 이라던지 /posts/abc라는 주소로 접속하면 123이나 abc라는 값을 postId라는 파라미터로 받는다.
function PostPage() {
const { postId } = useParams();
// ...
}
경로 파라미터를 사용하려면 useParams라는 훅을 사용하면 된다.
import { useSearchParams } from 'react-router-dom';
function PostListPage() {
const [searchParams, setSearchParams] = useSearchParams();
const filterQuery = searchParams.get('filter');
// ...
}
useSearchParams라는 Custom hook으로 SearchParams 객체를 받아올 수 있다. 이 hook은 SearchParams 객체와 Setter 함수를 배열형으로 리턴한다. 이때 쿼리 값은 SearchParams의 get 함수로 가져온다.
setSearchParams({
filter: 'react',
});
만약 쿼리 값을 변경하고 주소를 이동하고 싶다면 Setter 함수에 객체를 넘겨주면 된다. 이 때 객체의 프로퍼티로 쿼리 값을 지정할 수 있다.
function PostPage() {
// ...
const post = getPost(postId);
// post가 없는 경우 /posts 페이지로 이동
if (!post) {
return <Navigate to="/posts" />;
}
// ...
}
리턴값으로 Navigate 컴포넌트를 리턴하면 to prop으로 지정한 경로로 이동한다.
const navigate = useNavigate();
const handleClick = () => {
// ... 어떤 작업을 한 다음에 페이지를 이동
navigate('/wishlist');
}
useNavigate라는 hook으로 navigate 함수를 가져오면 이 함수를 통해 페이지를 이동할 수 있다.
사용자가 클릭해서 페이지를 이동하도록 할 때 사용하면 된다. 하이퍼링크 텍스트나 페이지를 이동하는 버튼, 이미지 등에 사용하면 된다. 대부분의 경우 Link만으로도 충분하다.
특정 경로에서 렌더링 시점에 다른 페이지로 이동시키고 싶을 때 사용하면 된다.
특정한 코드의 실행이 끝나고 나서 페이지를 이동시키고 싶을 때 사용하면 된다.
각 페이지에 들어갔을 때 페이지 제목이 바뀌지 않게 되는데, 페이지 제목은 public 폴더에 index.html 파일에 있는 <title> 태그에 지정된 것이다.
react-helmet이라는 라이브러리를 사용하여 바꿀 수 있다.
import { Helmet } from 'react-helmet';
import Button from '../components/Button';
import Container from '../components/Container';
import Lined from '../components/Lined';
import styles from './HomePage.module.css';
import landingImg from '../assets/landing.svg';
function HomePage() {
return (
<>
<Helmet>
<title>Codethat - 코딩이 처음이라면, 코드댓</title>
</Helmet>
<div className={styles.bg} />
<Container className={styles.container}>
<div className={styles.texts}>
<h1 className={styles.heading}>
<Lined>코딩이 처음이라면,</Lined>
<br />
<strong>코드댓</strong>
</h1>
<p className={styles.description}>
11만 명이 넘는 비전공자, 코딩 입문자가 코드댓 무제한 멤버십을
선택했어요.
<br />
지금 함께 시작해보실래요?
</p>
<div>
<Button>지금 시작하기</Button>
</div>
</div>
<div className={styles.figure}>
<img src={landingImg} alt="그래프, 모니터, 윈도우, 자물쇠, 키보드" />
</div>
</Container>
</>
);
}
export default HomePage;
Helmet이라는 컴포넌트로 감싼 다음에 안에다가 <title> 태그를 배치하면 이 컴포넌트가 렌더링 될 때 HTML의 <title> 태그를 덮어쓸 수 있다. (이름이 헬멧인 이유는 <head> 태그에 덮어쓰기 때문이다.)
클라이언트사이드 렌더링(Client-side Rendering)은 웹 브라우저에서 자바스크립트로 HTML 페이지를 만드는 것을 의미한다.
싱글 페이지 애플리케이션(Single Page Application)은 하나의 HTML 문서 안에서 자바스크립트로 여러 페이지를 보여주는 사이트를 의미한다.
클라이언트 사이드 렌더링(CSR)이란 웹 브라우저에서 자바스클비트로 HTML을 만들고, 이걸로 화면을 보여주는 것이다. 이걸 꼭 웹 브라우저에서 할 필요는 없다.
request를 받으면 서버가 렌더링 해서 response로 보내줄 수도 있고, 아예 미리 HTML 파일로 만들어 두었다가 response로 보내줄 수도 있다. 심지어는 리액트 코드를 HTML이 아니라 모바일 애플리케이션의 화면을 렌더링 하는 데 사용할 수도 있다.
리액트로 할 수 있는 가장 기본적인 방식의 렌더링이다. 리액트로 작성한 코드는 자바스브립트로 변환이 가능하다. 이런 변환을 트랜스파일링이라고 부른다. 즉, 클라이언트 사이드 렌더링은 자바스크립트로 변환된 리액트 코드를 웹 브라우저에서 실행해서 HTML을 만드는 걸 의미한다.
백엔드 서버에서 request를 받으면 상황에 맞는 HTML을 만들어서 response로 보내주는 방식을 '서버사이드 렌더링'이라고 한다. 서버에서 HTML을 만든다는 의미이다.
리액트에서도 서버사이드 렌더링을 할 수 있는 기능들을 제공한다. 이렇게 하면 이미 렌더링 된 것이 웹 브라우저에 도착하니까 훨씬 빨리 화면ㅇ르 띄워줄 수 있고, 검색 엔진에서 좋은 점수를 받아서 검색했을 때 사이트가 잘 노출될 수 있다는 장점이 있다.
서버에서 렌더링 하는 것도 좋지만, 데이터가 거의 바뀌지 않는다면 매번 새로 만드는 건 낭비이다. 그래서 미리 HTML 파일로 만ㄷ르고 이걸 서버로 배포하는 방법을 사용하는데, 이런 방식을 '정적 사이트 생성'이라고 한다. 서버에서는 request가 들어오면 HTML 파일을 읽어서 response로 보내주는 것이다.
'정적 사이트 생성'에서 정적이라는 말의 의미는 HTML을 파일로 만든다는 것이다. 개발자가 새로 배포하지 않는다면 서버에서 보내주는 HTML이 달라지지 않는다는 뜻이다. 쉽게 생각해서 리액트 코드로 HTML 파일을 만든다고 생각하면 된다. 물론 자바스크립트를 쓸 수 있기 때문에 정적으로 생성된 사이트에서도 동적인 데이터를 가져와 페이지를 보여줄 수 있다.
리액트에서는 서버사이드 렌더링을 하는 기능들을 제공하고 있지만, 아주 기본적인 방법만 제공한다. 때문에 매번 작성해야 하는 코드의 양도 많고 복잡하다. 그래서 개발자들은 서버사이드 렌더링을 대신 구현해주는 기술들을 만들기 시작했고, Next.js가 그 중 가장 인기 있는 기술이다.
2021년을 기준으로 말하면 리액트로 서버사이드 렌더링을 구현하는 경우 대부분 Next.js를 사용한다고 보면 된다. 심지어 리액트 공식 사이트에서도 Next.js를 추천한다. 특히 리액트 라우터랑은 다르게 HTML 파일을 나누듯이 자바스크립트 파일을 나눠 놓으면 곧바로 페이지로 사용할 수 있다는 장점도 있다.
npm run build 명령어로 프로젝트 빌드를 실행했다. 빌드라는 건 리액트로 소스코드들을 브라우저가 알아들을 수 있도록 만드는 걸 말한다. Gatsby는 리액트 코드를 미리 렌더링 해서 프로젝트를 빌드할 때 HTML 파일로 만든다. 이런 방식을 정적 사이트 생성이라고 했는데, Gatsby를 사용하면 리액트로 만든 사이트를 빌드해서 손쉽게 HTML 파일로 만들 수 있다.
회사 소개 사이트나 동아리 홈페이지 혹은 포트폴리오 사이트 같이 정적인 사이트를 리액트로 만들고 싶을 때 Gatsby를 사용하면 매우 효율적이다.
자바스크립트로 된 리액트 코드는 서버나 클라이언트에서 HTML로 변환된다. 하지만 모바일 화면에서 쓰는 것들을 미리 컴포넌트로 만들어두면 모바일 앱을 개발하는 것도 가능하다.
React Native는 이런 아이디어에서 출발했다. 리액트로 작성한 코드를 모바일 앱으로 만들 수 있게 해준다. 리액트 코드로 개발하면 웹과 안드로이드와 iOS 앱에서 사용하는 공통적인 코드를 한 번에 개발할 수 있다는 장점이 있다.
여러 플랫폼의 서비스를 빠르게 개발할 수 있기 때문에 많은 스타트업에서 React Native를 활용하고 있다.