⚛︎ React-router-dom

zooyaho·2022년 8월 14일
0
post-thumbnail

⚛️ React-router-dom

: 새로고침 없이 화면이 전환 됨.

📎 install
npm i react-router-dom

🛠 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를 사용하여 메뉴 만들기

  • 클릭한 메뉴요소의 class에 active가 추가됨.
  • 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>
  );
};
  • className값으로 콜백함수 지정가능하며, 이 함수는 react router가 NavLink를 평가하거나 탐색이 변화할 때 호출되며, 인수로 객체가 들어온다. 객체에는 isActive 속성이 있다.
  • isActive 속성 : 이 링크가 현재 선택된 경로에 대해 활성화 됐는지 여부를 알 수 있다.
<NavLink className={(navData)=> navData.isActive ? 'active' : ''} to='/welcome'> welcome </NavLink>

● 동적 경로 설정과 경로 매개변수 추출하기

  • : 사용하여 동적 경로 설정
<Route path="/product/:productId">
  <ProductDetail />
<Route>
  • 경로 매개변수 추출 : 사용자 지정 훅 useParams 사용
import { useParams } from 'react-router-dom';
...
const params = useParams();
console.log(params.productId); // p1
...

👉🏻 url이 /product/p1일 경우, params에 productId가 키고 값이 p1인 객체가 저장된다!

● 중첩 라우트 작업하기

  • 원하는 곳 어디에서나 라우트 정의가 가능하다.

📌 V5

👾 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>가 렌더링 된다!

📌 V6

👾 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

  • url이 route에 설정한 path들 중 일치하는 것이 없을 경우, 이동할 경로를 설정하여 리디렉션한다.
  • <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>

● useHistory

📌 V5 useHistory의 메서드와 프로퍼티

  • push('url') : 인수로 받은 path에 해당하는 화면을 전환하는 메서드, 뒤로가기를 통해 전 화면으로 이동 가능함.
  • replace('url') : Redirect처럼 인수로 받은 path에 해당하는 화면을 전환하는 메서드, 뒤로가기를 통해 전 화면으로 이동하지 못함!
  • location: 현재 페이지의 url정보를 포함한 객체를 반환하는 프로퍼티

👾 NewTodo.js

export default function todoForm() {
  const history = useHistory();
  
  const addTodoHandler = (todoData)=>{
    history.push('/todos'); // url이 /todos로 변경되어 화면전환 됨.
  }
  return <TodoForm onAddTodo={addTodoHandler}></TodoForm>
}

📌 V6 useNavigate

  • 이 탐색 기능은 다른 곳을 탐색하도록 실행되게 함.
  • useEffect훅에서 사용하거나 HTTP요청이 끝날때 사용한다.
  • 두번째 인자로 옵션 설정 가능하다.
const navigate = useNavigate();
navigate('/welcome');
or
navigate('/welcome',{replace: true}); // 특정 페이지로 이동
navigate('-1'); // 이전 페이지로 이동

● Prompt컴포넌트를 활용한 경고창 표시하기

🚨 V6부터는 사용하지 못한다!!

📌 V5 Prompt

: 이 컴포넌트는 우리가 다른 곳으로 이동할 때 자동으로 감지함. 그리고 특정 조건이 충족되면 떠나기 전 경고를 표시해 줌.

  • 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매칭을 바꾸지 않음!! 쿼리 매개변수 데이터에 접속하여 로드된 페이지의 '행동'을 바꿈.

✔️ useLocation

: 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>
  )
}

● useRouteMatch

  • URL뿐만 아니라 리액트 라우터가 알고 있는 내부에서 관리되는 데이터
  • match객체엔 path, url, isExact, params 키와 값이 있다.
  • path는 실제 경로가 아닌 리액트 라우터가 정의한 경로
cons match = useRouteMatch();
console.log(match); // {path: "/todos/:todoId", url: "todos/t1", isExact: false, params: {todoId: "t1"}}
profile
즐겁게 개발하자 쥬야호👻

0개의 댓글