리액트 라우터(React Router) - URL Parameters, Query Parameters (+URLSearchParams)

Noma·2021년 4월 23일
11
post-custom-banner

이전 포스팅들과 파일 구조가 연결됩니다. (App, Home, Topics, Topic...)

이번엔 Route의 경로에 특정 값을 넣는 법을 알아보자. 방법은 두 가지인데, parameter를 사용하는 것과, query를 사용하는 것이 있다.

1. URL Parameters (Feat. useParams)

간혹 우리는 localhost:3000/topics에서 토픽 전체에 대한 정보가 아니라, switch라는 특정 주제만 보여주고 싶은 경우가 있다.

그럴 때에는 localhost:3000/topics/switch와 같이, topics라는 URL 뒤에 id를 넣어주면 된다. 이를 파라미터라고 하는데 흔히 파라미터는 특정 아이디 혹은 이름을 사용해 무언가 조회할 때 사용 한다.

URL parameter를 실습하기 위해 아래와 같이 index.js와 app.jsx를 수정하고,

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app';
import { BrowserRouter  as Router} from 'react-router-dom';

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
);
//app.jsx
import React from 'react';
import { Route, Switch } from 'react-router';
import { NavLink } from 'react-router-dom';
import './app.css';
import Home from './components/home';
import Topics from './components/topics';

function App() {
  return (
    <>
        <ul>
          <li>
              <NavLink exact to="/">Home</NavLink>
          </li>
          <li>
              <NavLink to="/topics">Topics</NavLink>
          </li>
      </ul>
      <Switch>
         <Route path="/" exact>
           <Home />
         </Route>
         <Route path="/topics">
           <Topics />
         </Route>
      </Switch>
    </>
  );
}

export default App;

위에서 쓰인 Home, Topics 컴포넌트를 생성해주자.

//home.jsx
import React from 'react';

const Home = (props) => {
    return (
        <div>
            <h1>Home</h1>
            <p>이 곳은 홈 화면입니다.</p>
        </div>
    );
}
export default Home;
//topics.jsx
import React from 'react';
import { Route, Switch } from 'react-router-dom';

const Topics = (props) => {
    return (
        <div>
            <h1>Topics</h1>
            <ul>
                <li><NavLink to="/topics/route">Route</NavLink></li>
                <li><NavLink to="/topics/link">Link</NavLink></li>
                <li><NavLink to="/topics/switch">Switch</NavLink></li>
            </ul>
            <Switch>
                <Route path="/topics/route">
                    Route is...
                </Route>
                <Route path="/topics/link">
                    Link is...
                </Route>
                <Route path="/topics/switch">
                    Switch is...
                </Route>
            </Switch>
        </div>
    );
};
export default Topics;

출력하면 다음과 같이 Route 안에 Route가 중첩되어 렌더링 되는 것을 볼 수 있다.

위의 topics.jsx를 살펴보면 여기서 보여줄 토픽이 3가지여서 Route가 3번 반복됨을 알 수 있다. 하지만 보여줄 페이지가 이보다 훨씬 많다면 Route를 그만큼 반복해서 써야할까?

Route를 하나만 쓰고 /topics/ 뒤에 오는 값을 변수처럼 받아(:topicId) 해당하는 화면(<Topic />)을 보여줄 수 있으면 Route를 반복하지 않고도 해결할 수 있을 거다.

Topics 컴포넌트를 아래와 같이 바꾸고, 파라미터에 따라 보여줄 Topic 컴포넌트를 생성해주자.

//topics.jsx
import React, { useState } from 'react';
import { NavLink, Route } from 'react-router-dom';
import Topic from './topic';

const Topics = (props) => {
  // 렌더링에 쓰이는 데이터는 따로 분리해 state로 선언
    const [topics, setTopics] = useState([
        { id: 'route', title: 'Route', description: 'Route is ...' },
        { id: 'link', title: 'Link', description: 'Link is ...' },
        { id: 'switch', title: 'Switch', description: 'Switch is ...' },
    ]);
    return (
        <div>
            <h1>Topics</h1>
            <ul>
              //배열형태의 state에 map을 이용하여 NavLink에 정보를 뿌려준다.
                {topics.map(topic => <li key={topic.id}>
                    <NavLink to={`/topics/${topic.id}`}>{topic.title}</NavLink>
                </li>)}
            </ul>
        // 추후 Navlink에 해당하는 태그를, 예를 들어 Switch를 클릭하면 
        //:topicId로 switch가 들어오게 된다.
            <Route path="/topics/:topicId">
		 // 하위 컴포넌트에 토픽 정보가 들은 state 전달
                <Topic topics={topics} />
            </Route>
        </div>
    );

};
export default Topics;

Topic 안에서 Route의 topicId가 무엇인지 알아낼 수 있어야, Topic 컴포넌트가 그에 해당하는 내용을 보여줄 수 있다. 이때 사용할 수 있는 것이 useParams이라는 훅이다. 이것을 사용해 아래와 같이 Topic 컴포넌트를 만들어 보자.

//topic.jsx
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router';

const Topic = ({ topics }) => {
  // parameter에 대한 정보를 갖고 있는 객체가 params로 할당된다.
    const params = useParams();
  // Topic 컴포넌트에서 보여줄 정보를 state로 선언
  // 초기값은 없는 id가 올 경우 보여줄 Not Found 화면 정보로 정의
    const [topic, setTopic] = useState({
        title: 'Sorry',
        description: 'Not Found',
    });
  
    useEffect(() => {
        for (let i = 0; i < topics.length; i++) {
          // params.topicId는 아래 '🔍참고'속 내용을 사용한 것
            if (topics[i].id === params.topicId) {
                setTopic(topics[i]);
                break;
            }
        }
    }, [topics, params]);

    return (
        <>
            <h3>{topic.title}</h3>
            <p>{topic.description}</p>
        </>
    );
}

export default Topic;

🔍 참고
Topics -> Link 클릭 후 (useParams() 값이 담긴)params를 출력해보면 다음과 같다.

이처럼 URL Parameter를 알고 싶을 땐 useParams를 이용하면 된다.

2. Query Parameters (Feat. useLocation)

다음으로 localhost:3000/topics?version=new와 같은 방식을 다뤄보자. 이는(여기선, ?version=new) 쿼리 또는 쿼리 스트링이라고 불리는 것으로, 어떤 키워드를 검색하거나 페이지에 옵션을 전달할 때 사용 한다.

실습을 위해 위에서 사용했던 코드를 조금만 고쳐보자.

// topic.jsx
import React, { useEffect, useState } from 'react';
import { useLocation, useParams } from 'react-router';

const Topic = ({ topics }) => {
    const params = useParams(); 
    const location = useLocation(); //바뀐 부분
    const [topic, setTopic] = useState({
        title: 'Sorry',
        description: 'Not Found',
    });
    useEffect(() => {
        for (let i = 0; i < topics.length; i++) {
            if (topics[i].id === params.topicId) {
                setTopic(topics[i]);
                break;
            }
        }
    }, [topics, params]);

    return (
        <>
            <h3>{topic.title}</h3>
            <p>{topic.description}</p>
            {location && <p>{location.search}</p>} //바뀐 부분
        </>

    );
}

위 코드의 변수 location(useLocation의 리턴값이 할당된)를 콘솔에 출력해보면 다음과 같다.

따라서 query string(?이하의 string)만 가져오기 위해선 location의 search를 이용하면 된다. 여기선 결과를 확인해 보기 위해 location 값이 존재할 경우(location&&) p태그에 해당 쿼리를 출력하도록 하였다.

따라서 결과는 다음과 같다.

하지만 이처럼 '?'는 아무런 쓸모가 없다. 이 '?'를 제외하고 string 만을 받아와야 활용하기에 좋다.

이럴때는 yarn add qs를 통해 쿼리 스트링을 객체로 변환해주는 라이브러리 를 사용하면 된다.

라이브러리가 다 받아지면 package.json에서 확인이 가능하고,

상단에서 import 후 사용하면 된다.

import QueryString from 'qs';

QueryString.parse()의 첫 번째 인자로 변환할 쿼리스트링을 넣어 주고, 두 번째로는 ignoreQueryPrefix:ture라는 객체를 넣어주자.

그러면 쿼리스트링 앞에 붙는 접두사?를 빼고 쿼리스트링을 "객체"로 변환해 준다.

const queryData = QueryString.parse(location.search, { ignoreQueryPrefix: true });
console.log(queryData);

콘솔에 출력해보면 다음과 같다.

이로써 쿼리스트링의 key와 value를 분리해서 활용할 수 있게 되었다.

주의
?show=true?user?datail=1과 같이 쿼리스트링에 true나 숫자를 넣어도, location.search값은 "문자열"로 바뀌게 된다.

즉 'true', '1'이 되므로, parseInt()를 통해 숫자로 바꿔주든 ==='1'로 비교해주든 이 점을 유의해서 사용하도록 하자.

➕ URLSearchParams

이는 javascript에서 url의 쿼리 스트링을 읽거나 수정할 때 사용하는 것이다.

📍__예시 : URL이 localhost:3000/search/?keyword=홍길동&type=1인 경우

const location=useLocation();
const sch= location.search;
console.log(sch);

sch 출력값은 ?keyword=홍길동&type=1이다.

이를 URLSearchParams 객체로 아래와 같이 변환해주자. 그리고 get을 통해 key가 'keyword'인 value를 변수 keyword에 담아 출력하면,

const params=new URLSearchParams(sch);
const keyword=params.get('keyword');
console.log(keyword);

홍길동이 출력된다.

만약 keyword 값을 변경해주고 싶으면 set을 이용해 새로운 값을 설정해 주면 된다. 이후 toString()으로 변환해 출력하면,

params.set('keyword','임꺽정');
url=params.toString();
console.log(url);

?keyword=임꺽정&type=1이 나온다.

📚 Reference

profile
오히려 좋아
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 1월 4일

감사합니다. 덕분에 잘 배우고갑니다 !

답글 달기