본문 바로가기

[React] React로 Router 기능을 담은 SPA 구현해보기 + useNavigate

[React] React로 Router 기능을 담은 SPA 구현해보기 + useNavigate

컴포넌트 위주로 간단히 구현한 결과물

 

기본적으로 Router을 활용한 페이지 전환 + useNavigate를 이용하여 '뒤로 가기' 기능을 구현해보았다.

깃헙 링크


 

File Structure

 

보다 관리하기 편한 파일 구조(File Structure)를 가지기 위해 폴더별로 구분하는 것을 추천한다. React Twittler SPA의 파일구조는 아래와 같다. 페이지를 표시하는 컴포넌트는 Tweets, About, MyPage 이다. 

/
├── /Twittler React SPA
│   ├── README.md
│   ├── /public                     # create-react-app이 만들어낸 파일, yarn/npm start로 실행할 시에 쓰임
│   └── /src                        # React 컴포넌트가 들어가는 폴더
│        ├── static                 # dummyData가 들어가는 폴더
│        │    └── dummyData.js
│        ├── Pages                  # 페이지를 표시하는 컴포넌트가 들어가는 폴더
│        │    ├── About.css
│        │    ├── About.js
│        │    ├── MyPage.css
│        │    ├── MyPage.js
│        │    ├── Tweets.css
│        │    └── Tweets.js
│        ├── App.css
│        ├── App.js
│        ├── Footer.js
│        ├── index.js
│        └── Sidebar.js
├  package.json
└ .gitignore

 

./static/dummyData.js

연습용 더미 데이터는 ‘./static/dummyData.js’에 저장되어 있다. src/Pages/MyPage.js, src/Pages/Tweets.js 파일 최상단에 dummyTweets가 import 된다. 이렇게 import와 export 구문을 이용해서 각 폴더에 나누어진 페이지를 표시하는 컴포넌트들을 연결한다.

//** MyPage.js 최상단

import React from "react";
import { dummyTweets } from "../static/dummyData";
import "./MyPage.css";
//** 예시
  {
    id: 1,
    username: 'kimcoding',
    picture: `https://randomuser.me/api/portraits/women/1.jpg`,
    content: '모든 국민은 인간으로서의 ... ',
    createdAt: '2019-02-24T16:17:47.000Z',
    updatedAt: '2019-02-24T16:17:47.000Z'
  }
  
  // **하단
  export { dummyTweets };

 

parameter 형식 
id 숫자 고유한 아이디
username 문자열 사용자 이름
picture 문자열 프로필 사진의 URL 주소
content 문자열 사용자가 작성한 트윗 내용
createdAt 문자열 UTC 형태의 시간 정보
updatedAt 문자열 UTC 형태의 시간 정보

 

 


 

상세 컴포넌트 구현하기

 

컴포넌트 그리기

  • App 에서 왼쪽은 Sidebar 컴포넌트, 오른쪽은 페이지 전환을 위한 공간이다.
  • Sidebar에는 4개의 버튼이 있다. (home, mypage, about, 뒤로 가기)
  • 전환될 페이지는 총 3개이며 각각 footer가 달려있다.
    (위 그림에서 '페이지가 전환되는 부분'에 구현된다. '페이지가 전환되는 부분'의 하위요소가 아니다. 편의상 그려놓음)

 

* 필요한 부분만 정리하여 부분 포스팅

 

* 컴포넌트 경로에 연결하기

  • App 루트 컴포넌트(App.js)
    • import 를 이용하여 Tweets, MyPage, About 등 필요한 컴포넌트를 불러온다.
import React from 'react';
import './App.css';
import './global-style.css';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Sidebar from './Sidebar'; 🟣
import Tweets from './Pages/Tweets'; 🟣
import MyPage from './Pages/MyPage'; 🟣
import About from './Pages/About'; 🟣

const App = () => {
  return (
    <div>
      <BrowserRouter>
        <div className="App">
          <main>
            <Sidebar /> 🟣
            <section className="features">
              <Routes>
                <Route path="/" element={<Tweets />} /> 🟣
                <Route path="/about" element={<About />} /> 🟣
                <Route path="/mypage" element={<MyPage />} /> 🟣
              </Routes>
            </section>
          </main>
        </div>
      </BrowserRouter>
    </div>
  );
};

export default App;
import React from 'react';
// TODO - import문을 이용하여 react-router-dom 라이브러리의 Link 컴포넌트를 불러옵니다.
import { Link } from 'react-router-dom';

const Sidebar = () => {
  return (
    <section className="sidebar">
      {/* TODO : About 메뉴 아이콘과 Mypage 메뉴 아이콘을 작성하고 Link 컴포넌트를 이용하여 경로(path)를 연결합니다. */}
      <Link to="/">
        <i className="far fa-comment-dots"></i> 🟣
      </Link>
      <Link to="/about">
        <i className="far fa-question-circle"></i> 🟣
      </Link>
      <Link to="/mypage">
        <i className="far fa-user"></i> 🟣
      </Link>
    </section>
  );
};

export default Sidebar;

 

* My Page 에 특정 유저의 글만 보이게 하기

  • MyPage 컴포넌트(MyPage.js)
    • kimcoding이 작성한 트윗만 보여야 한다.
onst MyPage = () => {
  const filteredTweets = dummyTweets;
  const filteredTweet = filteredTweets.filter((tweet) => tweet.username === 'kimcoding')

  const Kimposts = filteredTweet.map((post) => 

        ( <li className="tweet" key={post.id}>
            <div className="tweet__profile">
              <img src={filteredTweets[0].picture} />
            </div>
            <div className="tweet__content">
              <div className="tweet__userInfo">
                <span className="tweet__username">{post.username}</span>
                <span className="tweet__createdAt">{post.createdAt}</span>
              </div>
              <div className="tweet__message">{post.content}</div>
            </div>
          </li>                                             
        ) 
      
        )


  return (
    <section className="myInfo">
      <div className="myInfo__container">
        <div className="myInfo__wrapper">
          <div className="myInfo__profile">
            <img src={filteredTweets[0].picture} />
          </div>
          <div className="myInfo__detail">
            <p className="myInfo__detailName">
              {filteredTweets[0].username} Profile
            </p>
            <p>28 팔로워 100 팔로잉</p>
          </div>
        </div>
      </div>
      
      <ul className="tweets__mypage"> {Kimposts}     
      </ul>
      <Footer />

    </section>
    
  );
};

나의 경우 : filter와 map 함수로 구현했다. 

const Kimposts = filteredTweet.map((post) => 

        ( <li className="tweet" key={post.id}>
            <div className="tweet__profile">
              <img src={filteredTweets[0].picture} />
            </div>
            <div className="tweet__content">
              <div className="tweet__userInfo">
.
.
.
            </div>
          </li>                                             
        ) 
        )


  return (
.
.
.
.     
 <ul className="tweets__mypage"> {Kimposts} </ul>
.
.
 <ul className="tweets">


        {  dummyTweets.map((tweet) => {
          return (
            <li className="tweet" id={tweet.id} key={tweet.id}>
              <div className="tweet__profile">
                <img src={tweet.picture} />
              </div>
              <div className="tweet__content">
                <div className="tweet__userInfo">
.
.
.
              </div>
            </li>
          );}
        )}


      </ul>
내가 한 방법
1. 리턴문 밖에 map 함수를 돌려서 변수에 할당
2. 리턴문 안에서 {} 안에 변수 넣음

* 화살표 함수이기에 return 과 {}를 빼도 된다.
* () 도 빼도 된다... !!!

페어 분과 함께 리팩토링한 방법
1. 리턴문 안에서 {} 안에 map 함수 구현 

* 화살표 함수이기에 return 과 {}를 빼도 된다.
* () 도 빼도 된다... !!!

 


 

Advanced Challenge

useNavigate 를 이용하여 (앞)뒤로 가기 기능 만들기

1. 내가 한 방법  :  포스팅 제일 상단에 있는 gif

- 우선 뒤로 가기 버튼만 구현했다. 앞으로 가는 버튼은 숫자 하나만 바꿔주면 되기 때문에 구현 자체는 쉽다. 다만 앞으로 가는 버튼을 트위터에 굳이 앞으로 갈 기능을 넣을 필요성을 못 느꼈다.

 

1. BackBtn 폴더를 만들고 아래와 같이 useNavigate를 적용한 버튼을 만들어서 export 한 후,

import { useNavigate } from "react-router-dom";
import React from "react";
import "./BackBtn.css";

function BackBtn() {

  const navigate = useNavigate();
  const back = () => navigate(-1)
  // const forth = () => navigate(1) 🟣// 앞으로 가는 버튼은 숫자를 -1에서 1로만 바꿔주면 된다.

  return (

  <div className ="bckWrapper">
    <button className ='bck' onClick={back}>Back</button>
    {/* <button className ='bck' onClick={forth}>Forth</button> */}
  </div>
  
  );
}

export default BackBtn;

2. 사이드바 버튼 아래에 넣어줬다.

import React from 'react';
import { Link } from 'react-router-dom';
import BackBtn from './BackBtn';
const Sidebar = () => {
  return (
    <section className="sidebar">
        <Link to = "/" > <i className="far fa-comment-dots"></i> </Link>
        <Link to = "/about" > <i className="far fa-question-circle"></i> </Link>
        <Link to = "/mypage" > <i className="far fa-user"></i> </Link>
        <BackBtn />  🟣 // 여기에 뒤로가기 버튼 추가!
    </section>
    
  );
};

export default Sidebar;

 

2.  레퍼런스  :   Advanced Challenge 에 첨부되어 있는 gif)

더보기

App.js

const App = () => {
  const navigate = useNavigate();

 🟣 //TODO - 앞으로 가기 기능을 구현할 핸들러를 만든다.
  const handleGo = () => {
    navigate(1);
  };

  🟣 //TODO - 뒤로 가기 기능을 구현할 이벤트 핸들러를 만든다.
  const handleGoBack = () => {
    navigate(-1);
  }

  return (
    <div>
      🟣 {/* TODO - BrowserRouter 컴포넌트를 index.js로 옮긴다. */}
        <div className="App">
          <main>
            <Sidebar />
            <section className="features">
              🟣 {/* TODO - 적당한 곳에 버튼을 만든다. */}
              <div className="button-wrapper">
                <button onClick={handleGoBack} >Back</button>
                <button onClick={handleGo}>Forward</button>
              </div>
              <Routes>
                <Route path="/" element={ <Tweets />} />
                <Route path="/about" element={ <About />} />
                <Route path="/mypage" element={ <Mypage />} />
              </Routes>
            </section>
          </main>
        </div>
    </div>
  );
};

🟣 // TODO : App 컴포넌트를 withRouter로 감싸준다.
export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

//TODO : App.js에 있는 BrowserRoter를 index.js로 옮긴다.
import { BrowserRouter as Router } from 'react-router-dom';

//Router 라는 이름을 붙이고 App 컴포넌트를 감싼다.
ReactDOM.render(
    <Router>
        <App />
    </Router>, 
    document.getElementById('root')
);

Sidebar.js

import React from 'react';
// TODO - import문을 이용하여 react-router-dom 라이브러리의 NavLink 컴포넌트를 불러온다.
import { NavLink } from 'react-router-dom';

const Sidebar = () => {
  return (
    <section className="sidebar">
      {/* TODO : Link 컴포넌트를 NavLink 컴포넌트로 바꾸고 active한 효과를 준다. */}
      <NavLink to="/" exact="true" activeClassName="active-link">
        <i className="far fa-comment-dots"></i>
      </NavLink>
      <NavLink to="/about" activeClassName="active-link">
        <i className="far fa-question-circle"></i>
      </NavLink>
      <NavLink to="/mypage" activeClassName="active-link">
        <i className="far fa-user"></i>
      </NavLink>
    </section>
  );
};

export default Sidebar;

 

 

 

 


 

리액트! 재밌어!

728x90
⬆︎