React에서 라우팅 처리에 대해 배워보자.
페이지 이동을 할 때, 새로 로딩을 하지 않고 이동해보자.
F12로 네트워크
탭을 켜보자. 페이지가 이동할 때마다 새로 로딩을 하고 있다.
어떻게 새로 로딩을 하지 않고 페이지를 이동할 수 있을까?
주소 앞에 #
혹은 #!
을 붙이면 새로 로딩하지 않고 이동할 수 있다.
<li>
<a href='/#/'>Home</a>
</li>
<li>
<a href='/#/about'>About</a>
</li>
이 방식은 history.pushState 도입 전에 방식이다.
지금도 Gmail은 이 방식을 쓰고 있다. 예를 들어,
https://mail.google.com/mail/u/0/#inbox
#
는 해시라고 하며,#!
는 해시뱅이라고 한다.
실제로 이동은 하지 않고 눈 앞에 보이는 주소만 바꿔보자.
이동을 하지 않으니 내용은 바뀌지 않는다.
preventDefault
을 이용해서 이벤트를 막는다. 그러면 이동하는 이벤트도 막힌다. 클릭해도 아무 일도 일어나지 않게 된다.
const handleClick = (event: SyntheticEvent) => {
event.preventDefault();
};
이 상태에서 강제로 이동해보자. history.pushState
쓰면 된다.
history.pushState 는 3개의 값을 받게 되어 있다.
const handleClick = (event: SyntheticEvent) => {
event.preventDefault();
const state = {};
const title = '';
const url = '/about';
history.pushState(state, title, url);
};
클릭했을 때 실제로 이동은 하지 않고 눈 앞에 보이는 주소만 바뀌게 된다.
이게 안되던 시절에 네이버 블로그는 현재 글에서 다른 글로 이동할 때 주소가 바뀌지 않았다. 지금은 웹 브라우저가
history.pushState
같은 걸 제공해주니까 주소 변경이 가능한 것이다.
history.pushState
는 HTML5부터는 제공되는 전역 객체의 함수이다.
그래서 테스트 코드에서는 돌아가지 않는다.
const handleClick = (event: SyntheticEvent) => {
event.preventDefault();
const state = {};
const title = '';
const url = '/about';
history.pushState(state, title, url);
};
return (
<header>
<nav>
<ul>
<li>
<a href='/' onClick={handleClick}>Home</a>
</li>
<li>
<a href='/about' onClick={handleClick}>About</a>
</li>
</ul>
</nav>
</header>
);
history.pushState
를 썼을 때는 주소는 바꿀 수 있었다. 내용은 바꿀 수 없었다.
React Router에서 제공하는 것을 사용해보자. 페이지 이동할 때 주소와 내용을 모두 바꿀 수 있다.
내용이 바꿀 수 있는 이유는 주소가 바뀌면 React Router 한테 "지금 주소 바뀌었으니까 확인해봐" 라고 하기 때문이다.
위에서 onClick
으로 잡았던 게 이제 필요없다. 알아서 처리해준다.
마찬가지로 페이지 이동을 해도 새로 페이지를 불어오지 않고 이동한다.
return (
<header>
<nav>
<ul>
<li>
<Link to='/' >Home</Link>
</li>
<li>
<Link to='/about' >About</Link>
</li>
</ul>
</nav>
</header>
);
Link
와 다른 NavLink
의 특징은 특정 페이지를 가면 그 페이지가 현재 위치라는 것을 알 수 있다.
예를 들어 현재 /about
주소에 있다면 /about
를 잡고 있는 링크 태그의 class에 active
가 잡히게 된다.
현재 페이지에 해당 하는 링크 태그에 css를 주고 싶을 때 유용하겠다.
Navigate
는 무조건 리다이렉션을 해준다.
예를 들어 /Logout
주소로 가면 /
으로 리다이렉션 하게 할 수 있다.
import {Navigate} from 'react-router-dom';
export default function LogoutPage() {
return (
<div>
... 로그아웃 처리 ...
<Navigate to='/' /> // 추가
</div>
);
}
export default function Header() {
return (
<header>
<nav>
<ul>
<li>
<NavLink to='/' >Home</NavLink>
</li>
<li>
<NavLink to='/about' >About</NavLink>
</li>
<li>
<NavLink to='/logout' >Log out</NavLink> // 추가
</li>
</ul>
</nav>
</header>
);
}
const routes = [
{
element: <Layout />,
children: [
{path: '/', element: <HomePage />},
{path: '/about', element: <AboutPage />},
{path: '/logout', element: <LogoutPage />}, // 추가
],
},
];
이렇게 테스트를 하면 테스트가 터진다.
context('when the current path is “/about”', () => {
it('redirects to the home page', () => {
renderRouter('/logout');
screen.getByText(/Hello, World/);
});
});
return new Request(url, init);
^
ReferenceError: Request is not defined
테스트에서 ReferenceError: Request is not defined
에러가 나면 whatwg-fetch
를 임포트 해서 해결하면 된다.
npm i -D whatwg-fetch
'<rootDir>/src/setupTests.ts',
를 추가한다.
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'@testing-library/jest-dom/extend-expect',
'<rootDir>/src/setupTests.ts',
],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest', {
jsc: {
parser: {
syntax: 'typescript',
jsx: true,
decorators: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
}],
},
testPathIgnorePatterns: [
'<rootDir>/node_modules/',
'<rootDir>/dist/',
],
};
import 'whatwg-fetch';
개발용으로 설치해서 lint 에러가 날 수 있다. 괜찮다.
유승완님께서 주신 답변이다.
테스트가 터질때 어디서 터지는지 에러 메시지를 보면 아래 코드에서 터지는 걸 확인할 수 있는데요.
Request라는 생성자 함수가 존재하지 않아서 그런걸로 추측을 할 수 있을 것 같아요.
return new Request(url, init);// ReferenceError: Request is not defined
그런데 whatwg-fetch를 import했을 때 에러를 해결된다는 것은, whatwg-fetch라는 패키지가 Request라는 생성자 함수를 갖고 있을거라고 추측 을 할 수 있을 것 같아요. 실제로 MDN에서 Fetch API를 확인해보시면 Request라는 인터페이스를 확인할 수 있어요.
Request - Web API | MDN따라서 whatwg-fetch를 import 해주면 내부에는 Request에 대한 구현체가 있기 때문에 에러를 해결한다고 볼 수 있을 것 같아요.
whatwg-fetch Github에서 코드를 보면 Request에 대한 구현체를 확인할 수 있어요.
fetch/fetch.js at master · JakeChampion/fetch
whatwg-fetch
는 Fetch API의 폴리필(polyfill)이다.
Fetch API를 지원하지 않는 구형 브라우저에서도 이를 사용할 수 있게 해주는 도구이다.
새로운 브라우저 기능이나 API를 사용하려는데, 현재 실행 중인 브라우저에서 해당 기능을 지원하지 않을 때가 있다.
이때 이를 대처하여 사용할 수 있게 해주는 코드 조각이다.
폴리필은 구형 브라우저나 환경에서도 최신 기능을 사용할 수 있게 해준다.
useNavigate
를 가장 많이 쓴다. useNavigate
는 훅이다.
내가 직접 컨트롤 할 수 있다.
import {NavLink, Navigate, useNavigate} from 'react-router-dom';
export default function Header() {
const navigate = useNavigate();
const handleClickLogout = () => {
navigate('/');
};
return (
<header>
<nav>
<ul>
<li>
<NavLink to='/' >Home</NavLink>
</li>
<li>
<NavLink to='/about' >About</NavLink>
</li>
<li>
<button type='button' onClick={handleClickLogout}>
Log out
</button>
</li>
</ul>
</nav>
</header>
);
}
/logout
주소를 들리지 않도록 button으로 다시 만들었다.
이번 시간에는 주소 이동을 할 때 새로 불러오지 않는 방법에 대해 배웠다.
Link
와 NavLink
는 css를 어떻게 적용할지에 따라서 결정하면 될 듯 싶다.
리다이렉션에 대한 간단한 처리라면 Navigate
로, 로직이 있다면 useNavigate
쓸 것 같다.
Navigate
가 테스트를 통과하지 못하는 이유가 궁금했는데, 테스트 코드의 오류를 자세히 보면 금방 추측할 수 있다는 것을 깨달았다. Chat GPT에 너무 의존해서 이 친구가 답을 못 내니, 나도 아무것도 못하는 상태가 됐던 게 아니었나 반성한다.