: 새로고침 없이 화면이 전환 됨.
📎 install
npm i react-router-dom
import {BrowserRouter as Router, switch, Route} from 'react-router-dom'
BrowserRouter
: HashRouter
: #을 사용하여 경로 설정 ex) /#/movie
git pages
사용 시 BrowserRouter는 경로 설명이 까다롭기 때문에 HashRouter사용 하는 것이 좋음.<switch>
: 위에서 아래로 읽어들여 path와 가장 먼저 일치하는 항목을 찾으면 switch로 인해 중단되고 일치한 첫번째 라우트를 렌더링<Route>
: path지정, Route를 찾으면 해당 컴포넌트를 렌더링 함.exact={true}
: 정확한 경로에만 해당 화면이 이동함. 👾 App.js
<switch>
<Route path="/">
<Home />
</Route>
<Route path="/movie">
<Movie />
</Route>
<Route path="/movie/:id">
<MovieDetail />
</Route>
</switch>
👉🏻 url이 /movie
일 경우 만 활성화 시킴
👉🏻 exact를 사용하면 정확한 경로에만 해당 라우트가 렌더링되게 함.
ex) <Route exact path="/movie">
NavLink
activeClassName
: active된 요소에 값으로 지정한 클래스가 추가됨.<NavLink className={({isActive}) => (isActive ? " okay" : "")}>
=> active가 추가된 메뉴요소에만 okay클래스가 추가됨.<NavLink activeClassName={{ fontWeight: "bold", color: "red" }}>
=> active될때의 스타일을 객체로 설정할 수 있다!👾 MainNavigation.js
import { NavLink } from "react-router-dom";
const MainNavigation = () => {
return (
<header>
<div id="logo">Great Quotes</div>
<nav>
<ul>
<li>
<NavLink to="/todo" activeClassName="selected">
All Quotes
</NavLink>
</li>
<li>
<NavLink to="/new-todo" activeClassName="selected">
Add a Quote
</NavLink>
</li>
</ul>
</nav>
</header>
);
};
NavLink
<NavLink className={(navData)=> navData.isActive ? 'active' : ''} to='/welcome'> welcome </NavLink>
:
사용하여 동적 경로 설정<Route path="/product/:productId">
<ProductDetail />
<Route>
import { useParams } from 'react-router-dom';
...
const params = useParams();
console.log(params.productId); // p1
...
👉🏻 url이 /product/p1일 경우, params에 productId가 키고 값이 p1인 객체가 저장된다!
👾 App.js
<switch>
<Route path="/welcome">
<Welcome />
</Route>
...
</switch>
👾 Welcome.js
<section>
<h1>The Welcome Page</h1>
<Route path="/welcome/new-user">
<p>welcome new user!!!</p>
</Route>
</section>
👉🏻 url이 /welcome
일 경우<h1>The Welcome Page</h1>
만 화면에 렌더링 되고,
👉🏻 url이 /welcome/new-user
일 경우 <h1>The Welcome Page</h1><p>welcome new user!!!</p>
가 렌더링 된다!
👾 App.js
<Routes>
<Route path="/welcome/*" element={<Welcome />} />
...
</Routes>
1️⃣ 첫번째 방법
👾 Welcome.js
<section>
<h1>The Welcome Page</h1>
<Routes>
<Route path="new-user" element={<p>welcome new user!!!</p>} />
</Routes>
</section>
👉🏻 중첩 라우트를 사용하는 컴포넌트에 <Routes>
는 필수적으로 작성해야 한다.
👉🏻 중첩 라우트의 상위 라우트 path를 /*
붙여야 중첩 라우트 경로가 실행된다!
💡 중첩 라우트를 작성하는 컴포넌트 자체를 이미 라우팅을 통해 로드된 구성 요소 속에 있으므로 path="/welcome/new-user"
을 path="new-user"
로 단축하여 작성해야 한다.
2️⃣ 두번째 방법
👾 App.js
<Routes>
<Route path="/welcome/*" element={<Welcome />} >
<Route path="new-user" element={<p>welcome new user!!!</p>} />
</Route>
...
</Routes>
👉🏻 중첩 라우팅을 한꺼번에 정의할 수 있다.
👉🏻 이렇게 작성하는 이유는 한 군데에 모든 라우팅 정의가 있다는 것이다.
✔️ Outlet
: 중첩 컨텐츠가 추가될 영역을 알린다.
👾 Welcome.js
import {Outlet} from 'react-router-dom';
...
<section>
<h1>The Welcome Page</h1>
<Outlet />
</section>
<Redirect to="path" />
: 사용자를 path에 설정한 url로 리디렉션함👾 App.js
// v5
<Switch>
<Redirect exact patch="/" to="login" />
</Switch>
// v6
<Routes>
<Route path="/" element={<Navigate replace to="/login"/>} />
</Routes>
👉🏻 url이 /
일 경우/login
으로 변경하여 해당 화면을 렌더링한다.
ex) 404페이지
: <Route path="*">
를 맨 밑에 추가하여 일치하는 url이 없을 경우 이 라우트에 설정한 컴포넌트 화면으로 전환된다.
: *(와일드 카드)
문자는 리액트 라우터에 모든 경로, 모든 URL이 이 라우트와 일치하게 한다. 그래서 작성한 라우트들중 가장 아래에 작성해야한다!
👾 NotFound.js
export default function NotFound(){
return (
<div>
<p>Page not found!</p>
</div>
);
}
👾 App.js
<switch>
<Route path="/" exact>
<Redirect to="/welcome" >
</Route>
<Route path="/welcome">
<Welcome />
</Route>
<Route path="*">
<NotFound />
</Route>
</switch>
👾 NewTodo.js
export default function todoForm() {
const history = useHistory();
const addTodoHandler = (todoData)=>{
history.push('/todos'); // url이 /todos로 변경되어 화면전환 됨.
}
return <TodoForm onAddTodo={addTodoHandler}></TodoForm>
}
const navigate = useNavigate();
navigate('/welcome');
or
navigate('/welcome',{replace: true}); // 특정 페이지로 이동
navigate('-1'); // 이전 페이지로 이동
🚨 V6부터는 사용하지 못한다!!
: 이 컴포넌트는 우리가 다른 곳으로 이동할 때 자동으로 감지함. 그리고 특정 조건이 충족되면 떠나기 전 경고를 표시해 줌.
when
: 사용자가 url을 변경하는 경우 이 프롬프트가 표시되어야 하는지 여부를 확인하는 프롭, true/false를 저장message
: 보여주고 싶은 텍스트가 포함된 문자열을 반환하는 함수를 지정(location)=>'메세지'
: 페이지에 대한 정보를 담고 있는 어떤 위치 객체를 인수로 받음. 보여줄 메세지를 반환해야함.import { Prompt } from 'react-router-dom'
<Prompt when={true} message={(location)=> '보여줄 메세지를 반환!'}/>
: 예를들어 폼양식 작성 후 사용자가 실수로 뒤로가기 버튼을 눌러, 작성돼있는 글이 reset되지 않게 propmt창을 사용하여 사용자에게 경고창을 표시하는 것이 좋다.
1️⃣ 양식이 포커스가 돼있는지 확인한다. form에 onFocus이벤트를 등록하여 핸들러 함수에 이 양식이 포커스 되었다는 정보를 저장한다.
const [isEntered, setIsEntered] = useState(false);
const formFocusedHandelr = ()=>{
// 사용자가 양식에서 작업하고 있음을 알 수 있음.
setIsEntered(true);
}
...
<form onFocus={formFocusedHandelr}>
...
2️⃣ Prompt컴포넌트 사용하여 isEntered가 true일때 화면을 이동할 경우 경고창 띄움.
import { Prompt } from 'react-router-dom'
...
<Prompt when={isEntered} message={(location)=> '정말로 이 화면을 떠나겠습니까?, 입력한 양식이 사라집니다.'}/>
<form onFocus={formFocusedHandler}>
...
3️⃣ 양식 작성 후 폼전송 버튼 클릭 시 프롬프트창 뜨지 않게 하기 위해 폼전송 버튼에 양식작성을 마쳤다는 함수를 등록한다.
💡 submit핸들러 함수에서 setIsEntered(false);
로 설정하면 안됨.
setIsEntered 이 상태 업데이트를 실제로 탐색 작업을 트리거하기 전에는 진행되지 않음.
이렇게 따로 함수를 생성하여 변경해야 실제 양식 제출 처리 전에 트리거가 된다!
...
const finishEnteringHandler = ()=>{
setIsEntered(false);
}
...
<button onclick={finishEnteringHandler}>Add Todo</button>
: 쿼리 매개변수는 url에 물음표와 함께 매개변수 쌍이 오는 경우가 있는데 로드된 페이지에 데이터를 추가로 넣어 준다.
: todoId와 같은 일반 route 매개변수와 다른점은, 일반 매개변수는 필수로 작성해야하고 쿼리 매개변수는 선택사항이다!
: 물음표가 route매칭을 바꾸지 않음!! 쿼리 매개변수 데이터에 접속하여 로드된 페이지의 '행동'을 바꿈.
: useHistory는 history객체에 접속하게 하고, history객체는 url을 바꿀 수 있게 해줌.
: useLocation은 location객체에 접속하게 하고, location객체엔 최근 로드된 페이지와 URL에 대한 정보가 있음!
: location객체엔 hash, key, pathname, search, state 키와 값이 있다.
: 예를들어 todoList에서 정렬한다고 했을 때 todo를 id에 따라 오름차순이나 내림차순으로 정렬한 다음, 연도로 정렬할 경우
: 쿼리 매개변수를 이용해 정렬한 걸 저장하고, 쿼리 매개변수가 있는 링크를 공유할 수 있는데 다른 유저가 그 링크를 쓰면 정렬한 대로 자동 정렬이 된다.
: 쿼리 매개변수가 url에 없으면 기본 설정대로 정렬이 된다.
1️⃣ 정렬 버튼 설정
👾 TodoList.js
import {useHistory} from 'react-router-dom';
...
const history = useHistory();
const changeSortHandler = ()=>{
// push로 해야 back버튼으로 변경하기 전의 URL사용 가능함.
// sort 버튼 클릭 시 url 변경
history.push('/todoes?sort=' + 'asc');
};
...
<div className={"sorting"}>
<button onClick={changeSortHandler}>오름차순</button>
</div>
<ul>
...
2️⃣ 쿼리 매개 변수값을 읽어 내고 실행하면서 정렬과 버튼 이름을 바꾼다.
👾 TodoList.js
import {useHistory, useLocation} from 'react-router-dom';
...
const history = useHistory();
const location = useLocation();
// Location객체의 search는 쿼리매개변수를 값으로 가지고 있다!
// URLSearchParams는 쿼리매개변수를 key로 추출하는 객체이다
// queryParams = { sort:"asc" }가 저장됨
const queryParams = new URLSearchParams(location.search);
// 오름차순이면 true가 할당됨
const isSortingAscending = queryParams.get('sort') === 'asc';
const changeSortHandler = ()=>{
// 클릭할 때마다 TodoList컴포넌트는 재평가가 된다!!
history.push('/todoes?sort=' + (isSortingAscending ? 'decs' : 'asc'));
};
...
<div className={"sorting"}>
// 현재 오름차순일 경우 버튼 클릭 시 내림차순이 되어야함.
<button onClick={changeSortHandler}>{isSortingAscending ? '내림차순' : '오름차순'}</button>
</div>
<ul>
...
👉🏻 history.push('/todoes?sort=' + (isSortingAscending ? 'decs' : 'asc'))
대신
history.push({
pathname: location.pathname,
search: `?sort=${(isSortingAscending ? 'desc': 'asc')}`
});
처럼 작성하면 보다 유연한 코드를 작성할 수 있음.
3️⃣ 정렬 로직을 가지는 함수를 추가한다.
👾 TodoList.js
const sortTodos = (todos, ascending) => {
return quotes.sort((todoPrev, todoCur) => {
if (ascending) {
// 오름 차순
return todoPrev.id > todoCur.id ? 1 : -1;
} else {
// 내림 차순
return todoPrev.id < todoCur.id ? 1 : -1;
}
});
};
const TodoList = ()=>{
...
// 오름차순이면 true가 할당됨
const isSortingAscending = queryParams.get('sort') === 'asc';
const sortedTodos = sortTodos(todos,isSortingAscending);
...
return(
...
<ul>
{sortedTodos.map(()=>(
<TodoItem .../>
))}
</ul>
)
}
cons match = useRouteMatch();
console.log(match); // {path: "/todos/:todoId", url: "todos/t1", isExact: false, params: {todoId: "t1"}}