SPA인 리액트(브라우저)에서 페이지를 전환하는 방법
v6 이상 Router 사용방법
대부분의 경우 App.js를 기준으로 Router를 Render하는 용도로 사용한다.
// 각 개별적인 용도를 하는 컴포넌트들을 별도의 폴더안에 정리
- components
- FileNum01
- FileNum02
- FileNum03
// 개별적인 용도의 컴포넌트들을 모아 하나의 화면을 구성하는 대표 페이지들을 별도의 폴더로 정리
- routes
- Home
- PageNum01
- PageNum02
컴포넌트별 분할해서 개발이 가능한 장점을 살려서 컴포넌트를 하나의 페이지로 사용이 가능하며 이를 route 폴더를 만들어서 분류가 가능하고 routes에 들어가는 각각의 컴포넌트들을 components 폴더를 만들어서 분류하는게 좋다.
npm i react-router-dom
or yarn add react-router-dom
*router의 사용방법은 변동될 수 있으며, v6 이상을 기준으로 작성함
npx create-react-app my-app
으로 리액트를 설치한 뒤 package.json에 보면 react-router-dom은 포함되어 있지 않기 때문에 추가로 설치해줍니다.
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
const App = () => {
return (
<Router>
<Routes>
<Route />
</Routes>
</Router>
)
}
export default App;
Route
를 감싸 핸들링하는 태그App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Contact from "./routes/Contact";
const App = () => {
return (
<Router>
<Routes>
<Route path="/" element={Home}/>
//<Route index element={Home}/>
<Route path="contact" element={Contact}/>
<Route path="about" element={About}/>
</Routes>
</Router>
)
}
export default App;
상위 라우트의 경로로 path="/" 보다 명시적으로 사용할 수 있는 Route의 props으로 index가 있습니다.
<Route path="/" element={Home}/>
<Route index element={Home}/>
<Route path="/" element={Home}/>
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Contact from "./routes/Contact";
const App = () => {
return (
<>
<Router>
<Routes>
<Route path="/" element={<Home />}/>
<Route path="contact" element={<Contact />}/>
<Route path="about" element={<About />}/>
</Routes>
</Router>
</>
)
}
export default App;
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return (
<>
<h1>Home</h1>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
</>
)
}
export default Home;
Link
는 html의 <a>
와 같은 역할을 하지만 <a>
와는 다르게 페이지가 새로고침되지 않으며 사용방법은 아래와 같다.
<Link to="/url path">Text</Link>
이렇게 링크에 경로를 적어주면 App.js에서 작성한 경로(path)에 해당하는 컴포넌트(element)로 새로고침 없이 이동이 가능하다.
Link의 기능 및 스타일을 편하게 적용하기 위해서 사용하면 좋은 기능
import React from "react";
import { NavLink } from "react-router-dom";
const Home = () => {
return (
<>
<h1>Home</h1>
<nav>
<NavLink to="/">Home</NavLink>
<NavLink to="about">About</NavLink>
<NavLink to="contact">Contact</NavLink>
</nav>
</>
)
}
export default Home;
<Link>
와 동일한 기능을 하지만 다른 점은 클릭시 active가 된다는 점이다.
위처럼 class="active"
가 자동으로 생성되기 때문에 기능 및 스타일을 적용하기가 매우 유용합니다.
유동적인 주소를 정의할 때 사용하는 방법으로 useParams
가 있으며, 주로 ID 및 특정한 데이터를 조회할 때 사용합니다.
유저의 프로필을 몇개 만들어서 유저별로 다른 페이지를 불러오는 임시 페이지를 만들어봅니다.
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Contact from "./routes/Contact";
const App = () => {
return (
<>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact/:id" element={<Contact />} />
</Routes>
</Router>
</>
);
};
export default App;
useParams는 path="/contact/:id
와 같이 :id
로 사용할 수 있습니다.
:(콜론)
: useParams를 사용하겠다는 의미id
: 변수와 같이 생각할 수 있으며 유동적인 url 값<Route path="/contact/:id" element={<Contact />} />
와 같이 contact 경로 뒤에 추가로 오는 경로의 값을 설정해두면 변경되는 값을 useParmas**로 확인할 수 있습니다.
import React from "react";
import { Link } from "react-router-dom";
const Home = () => {
return (
<>
<h1>Home</h1>
<ul>
<li>
<Link to="/about">/about으로 가는 링크</Link>
</li>
<li>
<Link to="/contact/user1">user1으로 가는 링크</Link>
</li>
<li>
<Link to="/contact/user2">user2으로 가는 링크</Link>
</li>
<li>
<Link to="/contact/user3">유저 정보가 없는 페이지로 가는 링크</Link>
</li>
</ul>
</>
);
};
export default Home;
App 컴포넌트에서 넣어준 :id의 값으로 <Link to="/contact/user1">user1으로 가는 링크</Link>
와 같이 user1 혹은 user2가 올 수 있습니다.
import React from "react";
import { useParams } from "react-router-dom";
const Contact = () => {
const users = {
user1: {
name: "DUDU",
age: 16,
},
user2: {
name: "POPO",
age: 33,
},
};
const params = useParams();
const userInfo = users[params.id];
return (
<>
{userInfo ? (
<div>
<h1>유저 정보</h1>
<div>
이름: {userInfo.name} 나이: {userInfo.age}
</div>
</div>
) : (
<h1>유저 정보가 없습니다</h1>
)}
</>
);
};
export default Contact;
결과를 확인하기 위해 콘솔을 찍어보면 아래와 같은 데이터를 받을 수 있습니다.
Home 화면 | user1으로 가는 링크 클릭 |
---|---|
user2으로 가는 링크 클릭 | 유저 정보가 없는 링크 클릭 |
---|---|
위의 url을 살펴보면 App에서 넘겨준 id값을 Contact의 useParams로 받아 사용할 수 있습니다.
말 그대로 라우트를 중첩되게 사용해야할 때가 있을텐데 컴포넌트를 추가로 만들지 않아도 리액트에서 제공하는 Outlet을 사용하면 쉽게 사용할 수 있습니다.
먼저 블로그들의 리스트를 보여주는 컴포넌트를 만들어줍니다.
Posts
import { Link } from 'react-router-dom';
const Posts = () => {
return (
<ul>
<li>
<Link to="/posts/1">블로그 리스트 1</Link>
</li>
<li>
<Link to="/posts/2">블로그 리스트 2</Link>
</li>
<li>
<Link to="/posts/3">블로그 리스트 3</Link>
</li>
</ul>
);
};
export default Posts;
Post
import { useParams } from 'react-router-dom';
const Post = () => {
const { blog } = useParams();
return (
<div>
<h1>블로그 내용 {blog}</h1>
</div>
);
};
export default Post;
App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Contact from "./routes/Contact";
import Posts from "./routes/Posts";
import Post from "./routes/Post";
const App = () => {
return (
<>
<Router>
<Routes>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact/:id" element={<Contact />} />
<Route path="/posts" element={<Posts />} />
<Route path="/posts/:blog" element={<Post />} />
</Routes>
</Router>
</>
);
};
export default App;
위와 같은 상황에 블로그 내용이 들어있는 페이지 안에 다시 블로그 리스트를 불러주기 위해서는
const Post = () => {
const { blog } = useParams();
return (
<div>
<h1>블로그 내용 {blog}</h1>
<Posts />
</div>
);
};
위와 같이 다시 컴포넌트를 불러와야하지만 리액트에서 제공하는중첩된 라우트 Outlet을 사용하면 보다 쉽게 구현할 수 있습니다.
Outlet 컴포넌트는 <Route>
의 자식으로 들어가는 JSX 엘리먼트를 불러오는 역할을 합니다.
const App = () => {
return (
<>
<Router>
<Routes>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact/:id" element={<Contact />} />
<Route path="/posts" element={<Posts />}>
<Route path=":blog" element={<Post />} />
</Route>
</Routes>
</Router>
</>
);
};
위처럼 중첩된 라우팅을 사용할 컴포넌트를 감싸주고 부모 (여기서는 Posts)컴포넌트에서 Outlet 컴포넌트를 불러오면 됩니다.
import { Link, Outlet } from "react-router-dom";
const Posts = () => {
return (
<div>
<Outlet />
<ul>
<li>
<Link to="/posts/1">블로그 리스트 1</Link>
</li>
<li>
<Link to="/posts/2">블로그 리스트 2</Link>
</li>
<li>
<Link to="/posts/3">블로그 리스트 3</Link>
</li>
</ul>
</div>
);
};
export default Posts;
상단의 네비게이션과 같은 모든 화면에 보여져야하는 컴포넌트의 경우에도 모든 컴포넌트에서 상단 네비게이션 컴포넌트를 사용하지 않아도Outlet을 사용하면 쉽게 구현이 가능합니다.
Header
import { Outlet } from 'react-router-dom';
const Header = () => {
return (
<div>
<header style={{ background: 'skyblue', padding: "2rem" }}>
상단 네비게이션 컴포넌트
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Header;
App
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./routes/Home";
import About from "./routes/About";
import Contact from "./routes/Contact";
import Posts from "./routes/Posts";
import Post from "./routes/Post";
import Header from "./routes/Header";
const App = () => {
return (
<>
<Router>
<Routes>
<Route element={<Header />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact/:id" element={<Contact />} />
<Route path="/posts" element={<Posts />}>
<Route path=":blog" element={<Post />} />
</Route>
</Route>
</Routes>
</Router>
</>
);
};
export default App;
상단에 들어갈 네비게이션 컴포넌트를 생성하고 App.js에서 위와 같이 사용합니다.
Link 컴포넌트를 사용하지 않고 페이지 전환할 때 사용할 수 있는 기능입니다.
Header
import { Outlet, useNavigate } from 'react-router-dom';
const Header = () => {
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
const goPosts = () => {
navigate("/posts");
}
return (
<div>
<header style={{ background: 'skyblue', padding: "2rem" }}>
상단 네비게이션 컴포넌트<br/>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goPosts}>블로그 리스트 바로가기</button>
</header>
<main>
<Outlet />
</main>
</div>
);
};
export default Header;
상단 네비게이션 컴포넌트에 위와 같이 useNavigate를 사용했습니다.
const goPosts = () => {
navigate("/posts", { replace: true });
};
홈에서 블로그 리스트 바로가기를 클릭하고 뒤로가기를 클릭하면 홈의 기록이 남지 않아 뒤로가기를 할 수 없습니다.
NotFound
const NotFound = () => {
return (
<h1 style={{fontSize: "4rem", color: "red"}}>404 NotFound</h1>
);
};
export default NotFound;
App
<Router>
<Routes>
<Route element={<Header />}>
<Route index element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact/:id" element={<Contact />} />
<Route path="/posts" element={<Posts />}>
<Route path=":blog" element={<Post />} />
</Route>
</Route>
<Route path="*" element={<NotFound />}/>
</Routes>
</Router>
<Route path="*" element={NotFound}/>
에서 *
는 css의 *
와 같다고 생각할 수 있는데 모든 경로에 적용한다는 의미로 만약 모든 경로를 확인했지만 일치하는게 없다면 해당 라우트로 전환됩니다.
회원에게만 보여지는 페이지를 사용자가 로그인을 하지 않은 상태로 들어갔을 때, 로그인 페이지로 넘어가지는 것을 경험해봤을 겁니다.
위와 같은 케이스에 사용할 수 있는 기능으로 리액트에서 제공하는 Navigate가 있습니다.
Login
import React from "react";
const Login = () => {
return (
<>
<h1>로그인 페이지</h1>
</>
);
};
export default Login;
MyPage
import React from "react";
import { Navigate } from "react-router-dom";
const MyPage = () => {
const isLogin = false;
return (
<>
<div>
{!isLogin ? (
<Navigate to={"/login"} />
) : (
<h1>로그인 확인 회원 전용 페이지</h1>
)}
</div>
</>
);
};
export default MyPage;
isLogin 변수에 담긴 값이 로그인의 유무에 따라서 true
or false
로 변경된다고 가정했을때 로그인이 되지 않은 상황에서는 위와 같이 MyPage 컴포넌트가 아닌 Login 컴포넌트로 전환됩니다.
이때 <Navigate to={"/Login"} replace={ true } />
와 같이 replace={ true }
를 넘겨주면 사용자가 로그인을 완료한 상태에서 뒤로가기를 눌러도 로그인 페이지는 기록되지 않습니다.