원지의 개발
article thumbnail
728x90

SPA

  • 하나의 페이지로 만들어진 어플리케이션
  • 작은 규모 만드는게 특화되어 있음
  • SPA는 html파일을 브라우저 측에서 로드하고, 필요한 데이터는 API와 ajax통신을 이용(비동기)해서 처리
  • 브라우저에서 사용자가 상호작용 하면 필요한 부분만 업데이트 해서 처리 ▶ 기존은 전체 렌더링
  • 멀티플랫폼 Android, IOS에 대응하여 웹뷰로 처리하는 목적으로도 사용됨

SPA의 단점

  • 앱의 규모가 커지면, JS파일도 너무 커져서 로딩이 오래걸리게 됨
  • 브라우저에서 렌더링이 완료되기까지 비어있는 화면이 나오게 됨
  • 그래서 규모가 큰 어플리케이션은 SSR(서버사이드 렌더링) 방식으로 처리함 (웹팩 설정 필요)
    vs 리액트는 클라이언트 = CSR

라우팅

  • 브라우저의 주소상태에 따라 다양한 화면을 보여주도록 처리하는 것

사용이유?

  • create-react-app로 프로젝트를 생성하게 되면 기본적으로  SPA에 CSR(클라이언트 렌더링)이므로 하나의 페이지만 사용하게 됨
  • 그래서 SPA이지만 라우터를 활용해서 사용자로 하여금 여러 페이지가 존재하는 것처럼 느껴지게 할 수 있음

Router (라우터)

새 프로젝트 생성

라우터 설치

1. 라우터를 적용할 프로젝트 생성

npm create react-app 프로젝트명

2. 라우터 설치

npm add react-router-dom

3. 프로젝트 시작

npm start

라우터 적용

  1.  라우터를 사용할때는 index.js에서 App컴포넌트를 <BrowserRouter>로 감싸줌
  2.  페이지 컴포넌트 만들기 = 각각의 컴포넌트 만들기
  3.  App컴포넌트에서 <Routes>를 사용해서 분기하기
    라우터들 - 이 주소 path로 가면 이게 보여지게 하겠다
  4.  주소요청을 통해 확인하기 = Route를 이용해 주소별로 컴포넌트 연결
 

index.js

....
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>

);

 

App.js

  • /요청은 Home컴포넌트가 나타남
  • /user요청은 User컴포넌트가 나타남
  • /info요청은 Info컴포넌트가 나타남
function App() {

  return (
    <Routes>
        <Route path='/' element={<Home/>}/>
        <Route path='/user' element={<User/>}/>
        <Route path='/info' element={<Info/>}/>
    </Routes>
  );
}

export default App;

App.js

import logo from './logo.svg';
import './App.css';
import { Route, Routes} from 'react-router-dom';
import Home from './component/Home';
import User from './component/User';
import Info from './component/Info';
import Board from './component/Board';
import BoardContent from './component/BoardContent';
import Header from "./layout/Header";
import MyPage from './component/MyPage';

function App() {

  /* 

    p.403

    라우터 적용방법
    1. index.js에서 브라우저 라우터로 app을 감싸줍니다.
    2. 각각의 컴포넌트를 만듭니다.
    3. Route를 이용해서 주소별로 컴포넌트 연결합니다.


    Link 컴포넌트
    - a태그를 대체합니다.
    - to속성에 "라우터 주소" 적어서 이동하면 됩니다.

    쿼리스트링 ?키=값
    - useLocation()
    - useSearchParams()

    URL 파라미터
    - 라우터를 설정 /경로/:값
    - useParams() 값을 받습니다.
  */

  return (

    <Routes> {/* 라우터들 */}
      {/* 여기는 라우터 설정들, 이 주소 path로 가면 이게 보여지게 하겠다 */}

      {/* 중첩라우터 - 공통부분처리 (Header에 가서 Outlet 컴포넌트 표기) */}
      <Route element={<Header/>}>
        <Route path='/' element={<Home/>}/> {/* 각각은 라우트 태그 이용 */}
        <Route path='/user' element={<User/>}/> {/* 쿼리스트링 */}
        <Route path='/info/:num' element={<Info/>}/> {/* URL파라미터 */}
      </Route>

      {/* 그냥 이렇게 하면 각기 다른 페이지 2개, 각각 다른 화면이 보입니다. 
        <Route path='/board' element={<Board/>}/>
        <Route path='/board/:num' element={<BoardContent/>}/> 
      */}

      {/* 중첩라우터 - 공통부분처리 (Board에 가서 Outlet 컴포넌트 표기) */}
      <Route path='/board' element={<Board/>}>{/* 공통으로 서용하는 부분 */}
        <Route path=':num' element={<BoardContent/>}/> 
      </Route>

      {/* navigate 컴포넌트 */}
      <Route path='/mypage' element={<MyPage/>}/>

    </Routes>

  );
}

export default App;

Link를 이용해서 다른페이지로 이동하기

  • link태그는 화면에서 a태그로 생성됨
  • to 속성에는 연결할 요청주소를 적음
  • 어느 컴포넌트든 사용 가능

/로 연결되는 Home.js

//.....
return (
	<ul>
		<li><Link to='/user'>회원페이지</Link></li>
	</ul>
)

Home.js

import { Link } from "react-router-dom";


const Home = () => {


    return (
        <div>
            <h3>홈 화면</h3>

            <ul>
                <li><Link to="/user">회원페이지</Link></li>
                <li><Link to="/user?id=aa123&age=1">회원페이지</Link></li>

                <li><Link to="/info/1">info페이지</Link></li>
                <li><Link to="/info/2">info페이지</Link></li>
                <li><Link to="/info/3">info페이지</Link></li>

                {/* 중첩 라우터 */}
                <li><Link to="/board">board페이지</Link></li>

                {/* navigate 컴포넌트 */}
                <li><Link to="/mypage">마이 페이지</Link></li>


            </ul>
        </div>
    )
}

export default Home;

쿼리스트링 or URL파라미터

  • 같은 컴포넌트라도 전달되는 데이터에 따라서 다른 내용을 보여줘야 하는 경우가 있음
    EX) 글 상세
  • 이 때 URL주소의 매개변수를 전달 할 수 있고, 컴포넌트는 그 값을 받아서 사용 할 수 있음

쿼리스트링

  • 쿼리스트링은 주소의 ? 뒤에 키=값 의 형태로 넘어가는 매개값
  • 별도의 라우터 설정은 필요x
  • 컴포넌트에서는 useLocation() 훅 or useSearchParams() 훅을 사용 해서 쿼리스트링을 받을 수 있습니다.
/경로?키=값&키=값

링크에서 (home.js)

//.....
return (
	<ul>
		<li><Link to='/user?id=aa123&key=1'>회원페이지(쿼리파람)</Link></li>
	</ul>
)

 

컴포넌트에서 (user.js)

1. useLocation 훅

const location = useLocation();   

2. useSearchParams 훅

배열 반환 [값을 조회하거나 수정하는 get set, 쿼리스트링을 업데이트하는 함수]
  • 배열을 반환
  • 첫 번째 요소는 쿼리파라미터를 조회하거나 수정하는 get, set이 담긴 객체
  • 두 번째 요소는 쿼리파라미터를 객체로 업데이트 하는 함수 반환
const [객체, function] = useSearchParams()

User.js

import { useLocation, useSearchParams } from "react-router-dom";



const User = () => {

    //쿼리스트링으로 넘어오는 값을 받기
    //1. useLocation 훅
    // const location = useLocation();
    // console.log(location); //객체 안에 쿼리스트링 값은 분해해서 사용

    //2. useSearchParams 훅
    //배열 반환 [값을 조회하거나 수정하는 get set, 쿼리스트링을 업데이트하는 함수]
    const [obj, setObj] = useSearchParams();
    console.log(obj); //객체 - 객체의 get, set메서드 사용
    console.log(setObj); //함수

    let id = obj.get("id"); //request.getParameter("id")
    let age = obj.get("age");

    console.log(id); //aa123
    console.log(age); //1

    //쿼리스트링 강제로 변경하기
    const handleClick = () => {
        let num = parseInt(age) + 1; //age값에 1더함, age는 string으로 넘어오나 봄?
        setObj({id: '변경', age: num});
    }

    return (
        <div>
            <h3>유저 화면</h3>

            쿼리스트링으로 넘어온 id: {id}<br/>
            쿼리스트링으로 넘어온 age: {age}<br/>

            <button onClick={handleClick}>쿼리스트링 강제 수정</button>

        </div>
    )
}

export default User;
  • link에 들어간 키=값을 가져오기 위해서 useSearchParams 훅 사용해서 배열 생성
  • 배열에는 객체와 함수가 들어간는데 객체.get("키")로 새로운 변수에 담기
  • 콘솔로 찍어보면 값 확인 가능
  • 버튼 클릭시 쿼리스트링을 강제로 변경하기 위해서 가져온 age에 1을 더하는데 age는 문자열로 가져오므로 parseInt 해줌
  • setObj 함수로 id, age값 저장

URL파라미터

  • 라우터에 추가적인 설정이 필요 ( /주소/:키:키:키 )
  • URL파라미터는 주소 뒤에 /경로//값 의 형태로 넘어가는 매개값
  • 컴포넌트에서는 useParams() 훅을 사용하여 URL파라미터를 받을 수 있음

라우터에서 (App.js) - key = num

function App() {

  return (
    <Routes>
        .....
        <Route path='/info/:num' element={<Info/>}/>
    </Routes>
  );
}

export default App;

링크에서 (Home.js) - 값 = 1, 2, 3

//.....
return (
	<ul>
		<li><Link to='/info/1'>1번 info</Link></li>
		<li><Link to='/info/2'>2번 info</Link></li>
		<li><Link to='/info/3'>3번 info</Link></li>
	</ul>
)

컴포넌트 (Info.js)

useParams() 훅

let param = useParams();

/param에는 link로 넘어온 키가 담겨있음

import { useParams } from "react-router-dom";



const Info = () => {

    const data = {
        1: {name: "리사", subject: "리액트라우터"},
        2: {name: "제니", subject: "리액트props"},
        3: {name: "지수", subject: "리액트state"},
        4: {name: "로제", subject: "리액트일지도..?"},
    }

    //useParams();
    let param = useParams();
    //console.log(param); //{num: '1'}, {num: '2'}, {num: '3'}나옴, URL주소에 값을 키로 받습니다.
    console.log(param.num); //1, 2, 3 나옴

    // const obj = data[param.num];
    // console.log(obj); //번호에 맞는 객체가 나옴
    const {name, subject} = data[param.num];

    return (
        <div>
            <h3>인포 화면</h3>
            {name}님의 데이터 {subject}
        </div>
    )
}

export default Info;
  • App.js에서 key값을 num으로 설정해 주고
  • Home.js에서 key의 값을 1, 2, 3으로 url에 연결
  • Info.js에서는 data에 1, 2, 3에 맞는 객체를 설정해서 useParams() 훅으로 받음
    이 훅은 url주소에 값을 키로 받기 때문에 콘솔에 찍어보면 키: 값 이 나옴
  • (주석 부분) data를 찍어보면.... 이 부분 이해가 잘 안감
const obj = data[param.num]; // const obj = data[1];
    console.log(obj); //번호에 맞는 객체가 나옴, {name: '리사', subject: '리액트라우터'}
    const {name, subject} = data[param.num];


중첩라우터로 공통 부분 처리하기

  • 글페이지가 있고, 글에 따른 상세화면이 있다고 가정했을 때 라우터의 설정은 아래 처럼 보여질 수  있음
  • 아래 설정은 두 라우터가 다르기 때문에 각각 다른 화면이 보임
<Routes>
	<Route path='/board' element={<Board/>}/>
	<Route path='/board/:num' element={<BoardContent/>}/>
</Routes>

1) 게시글 목록 + 게시글

  • 만약 Board 목록 페이지를 공통으로 사용하고 상세페이지를 서브로 보여주도록 처리하려면 중첩라우터를 활용
  • 중첩라우터로 적용되면 부모컴포넌트에서 <Outlet> 컴포넌트를 활용해서 하위 라우터를 보여지게 할 수 있음
    ▶ 결국 게시글 누르면 밑에 댓글 있고, 그 아래에 또 게시물 목록 뜨게끔 (ex 커뮤니티)

라우터에서 (App.js)

<Routes>
{/* 중첩라우터 - 공통부분처리 (Board에 가서 Outlet 컴포넌트 표기) */}
    <Route path='/board' element={<Board/>}>{/* 공통으로 서용하는 부분 */}
		<Route path=':num' element={<BoardContent/>}/>
	</Route>
</Routes>

상위 컴포넌트에서 (Board.js) - <Outlet/>

 

const Board = () => {

    return (
        <div>
            <h3>게시글목록</h3>
            <ul>
                <li><Link to='/board/1' >글1</Link></li>
                <li><Link to='/board/2' >글2</Link></li>
                <li><Link to='/board/3' >글3</Link></li> 
             </ul>

            {/* Oulet컴포넌트가 사용된 자리에 중첩된 내용이 보여지게됩니다 */}
            <Outlet/>
        </div>
    )
}

export default Board;

하위 컴포넌트에서 (BoardContent.js)

const BoardContent = () => {

    let {num} = useParams()

    return (
        <div>
            <h3>글 상세페이지</h3>
            {num}번 글입니다      
        </div>
    )
}

export default BoardContent;

2) Header 목록

라우터에서 (App.js)

 <Routes> {/* 라우터들 */}
      {/* 여기는 라우터 설정들, 이 주소 path로 가면 이게 보여지게 하겠다 */}

      {/* 중첩라우터 - 공통부분처리 (Header에 가서 Outlet 컴포넌트 표기) */}
      <Route element={<Header/>}>
        <Route path='/' element={<Home/>}/> {/* 각각은 라우트 태그 이용 */}
        <Route path='/user' element={<User/>}/> {/* 쿼리스트링 */}
        <Route path='/info/:num' element={<Info/>}/> {/* URL파라미터 */}
      </Route>
  • 공통으로 사용할 Header를 Route로 묶어서 넣기

컴포넌트에서 (Header.js)

import { Fragment } from "react";
import { Outlet, useNavigate } from "react-router-dom";

import styled from './Header.module.css';


const Header = () => {

    //useNavigate 훅 - 함수반환
    let nav = useNavigate();

    const goHome = () => {
        nav('/'); //경로
    }

    const goBack = () => {
        nav(-1); //뒤로가기, +1은 앞으로 가기
    }

    return (
        <Fragment>
            <header className={styled.wrap}>
                <h3>Header 파일</h3>
                <ul className={styled.wrap_list}>
                    <li>목록</li>
                    <li>목록</li>
                    <li>목록</li>
                </ul>
                <div>
                    <button onClick={goHome}>홈화면</button>
                    <button onClick={goBack}>뒤로가기</button>
                </div>
            </header>
            <section>
                {/* 헤더 하위의 라우터 표현 */}
                <Outlet/>
            </section>
        </Fragment>
    )
}

export default Header;
  • header, section으로 나누고, section부분에 헤더 하위의 라우터를 표현하는 Outlet 적기

라우터의 부가적인 기능

NavLink 컴포넌트 - 링크를 대체

1. NavLink 컴포넌트

  • 링크에서 사용하는 경로가 라우터의 경로와 일치하면 특정스타일을 적용 (활성화 기능)
  • 토글메뉴, 애니메이션 했던 것처럼 사용

2023.01.03 - [클라이언트/JavaScript] - [JavaScript] 이벤트 객체

2. useNavigate() 훅 vs Navigate 컴포넌트 

useNavigate() 훅

  • JS의 history객체를 대신함
  • 이벤트에서 사용

Navigate 컴포넌트 

  • 리다이렉트기능 
  • 렌더링시에 사용

 

컴포넌트에서 (Board.js)

const Board = () => {

    return (
        <div>
            <h3>게시글목록</h3>
            <ul>
                {/* 
                <li><Link to='/board/1' >글1</Link></li>
                <li><Link to='/board/2' >글2</Link></li>
                <li><Link to='/board/3' >글3</Link></li> 
                */}
                <li><NavLink to='/board/1' style={
                    ({isActive}) => {
                    return isActive ? myStyle : undefined  
                    }
                }>글1</NavLink></li>
                <li><NavLink to='/board/2' style={({isActive}) => (isActive ? myStyle : undefined  )}>글2</NavLink></li>
                <li><NavLink to='/board/3' style={({isActive}) => (isActive ? myStyle : undefined  )}>글3</NavLink></li> 
            
             </ul>

            {/* Oulet컴포넌트가 사용된 자리에 중첩된 내용이 보여지게됩니다 */}
            <Outlet/>
        </div>
    )
}

export default Board;

 

useNavigate() 훅

  • useNavigate훅은 특정 event가 발생할 때,  url을 조작할 수 있는 함수를 제공
  • react v6 에서 useHistory 가 변화한 것 (JS의 history객체를 대신 합니다) => histroy가 navigate로 바뀐거임
let navigator = useNavigate();
import { Fragment } from "react";
import { NavLink, Outlet, useNavigate } from "react-router-dom";

const Header = () => {
    //useNavigate() 훅
    let nav = useNavigate();

    const goHome = () => {
        nav('/내가원하는 주소') 
    }
    return (
    	<button onClick={goHome}>이동</button>
    )

}
export default Header;

  JS의 history를 다루는 기능도 제공

- history.go(-1) 뒤로가기

- history.go(1)  앞으로가기

- 주소 넣으면 원하는 주소로 가기

  ...
  let nav = useNavigate();
  
  return(
      <>
          <button onClick={() => nav(-1)}>
            Go back
          </button>
          <button onClick={() => nav(1)}>
            Go forward
          </button>
      <>
  )

컴포넌트에서 (MyPage.js)

import { Navigate, useNavigate } from "react-router-dom";


const MyPage = () => {

    
        // 첫번째 렌더링 과정에서는 사용할 수 없음
        // 렌더링 과정에 nav를 쓰지말라는 경고 후 작동이 안됩니다.
        let nav = useNavigate();
        let loginYN = false; //로그인 여부
        if(loginYN === false) { //아직 로그인이 안되어 있다
            nav('/');
        }

    return (
        <div>
            권한 있는 사람만 접근이 가능함
        </div>
    )
}

export default MyPage;
  • 첫번째 렌더링 과정에서는 사용할 수 없음 (잘 이해 안감)
  • React.useEffect()에서 navigate()를 호출해야 함
  • 이 코드 결과로 렌더링 과정에 nav를 쓰지말라는 경고 후 작동이 안됨

컴포넌트에서 (Header.js) - 앞으로 가기, 뒤로 가기

import { Fragment } from "react";
import { Outlet, useNavigate } from "react-router-dom";

import styled from './Header.module.css';


const Header = () => {

    //useNavigate 훅 - 함수반환
    let nav = useNavigate();

    const goHome = () => {
        nav('/'); //경로
    }

    const goBack = () => {
        nav(-1); //뒤로가기, +1은 앞으로 가기
    }

    return (
        <Fragment>
            <header className={styled.wrap}>
                <h3>Header 파일</h3>
                <ul className={styled.wrap_list}>
                    <li>목록</li>
                    <li>목록</li>
                    <li>목록</li>
                </ul>
                <div>
                    <button onClick={goHome}>홈화면</button>
                    <button onClick={goBack}>뒤로가기</button>
                </div>
            </header>
            <section>
                {/* 헤더 하위의 라우터 표현 */}
                <Outlet/>
            </section>
        </Fragment>
    )
}

export default Header;

Header.module.css

* {margin: 0; padding: 0; list-style: none;}

.wrap {
    background-color: black;
    display: flex;
    justify-content: space-between; /* 수평 정렬 */
    align-items: center; /* 한줄일 때 수직 정렬 */
    color: white;
    padding: 20px;
}

.wrap .wrap_list {
    display: flex;
    justify-content: space-around;
    flex-basis: 300px; /* header의 아이템이여서 flex-basis 사용가능 */
}

Navigate 컴포넌트

  • <Navigate> 컴포넌트는 렌더링 될 때 현재 위치를 변경
  • useNavigate()훅과 비슷해보이지만 useNavigate()훅은 렌더링 과정에 사용할 수 없음
  • NavLink는 링크의 경로가 라우터의 경로와 일치하면 특정스타일을 적용해 줌
  • NavLink는 style속성을 제공함
  • style속성에는 실행시킬 함수를 작성
  • 이 함수에 매개변수로 {isActive : boolean}객체를 넣어주는데, 활성화 여부를 표시가 가능
  • 사용은 반드시 {isActive}변수로 구조분해할당 해야 함

컴포넌트에서 (Board.js)

const Board = () => {

    return (
        <div>
            <h3>게시글목록</h3>
            <ul>
                {/* 
                <li><Link to='/board/1' >글1</Link></li>
                <li><Link to='/board/2' >글2</Link></li>
                <li><Link to='/board/3' >글3</Link></li> 
                */}
                <li><NavLink to='/board/1' style={
                    ({isActive}) => {
                    return isActive ? myStyle : undefined  
                    }
                }>글1</NavLink></li>
                <li><NavLink to='/board/2' style={({isActive}) => (isActive ? myStyle : undefined  )}>글2</NavLink></li>
                <li><NavLink to='/board/3' style={({isActive}) => (isActive ? myStyle : undefined  )}>글3</NavLink></li> 
            
             </ul>

            {/* Oulet컴포넌트가 사용된 자리에 중첩된 내용이 보여지게됩니다 */}
            <Outlet/>
        </div>
    )
}

export default Board;

컴포넌트에서 (MyPage.js)

import { Navigate, useNavigate } from "react-router-dom";


const MyPage = () => {

    //권한이 없으면 리다이렉트
    //replace={true}를 설정하면 기록을 남기지 않습니다. MyPage에 들어갔던 이력은 사라지게 됨
    let loginYN = false;
    if(loginYN === false) {
        return <Navigate to='/' replace={true}/>
    }


    return (
        <div>
            권한 있는 사람만 접근이 가능함
        </div>
    )
}

export default MyPage;
  • replace={true} 기록을 남기지 않음

출처: 쌤 티스토리


오늘 하루

더보기

기억에 남는 부분

 

어려운 부분

 

문제 해결 부분

 

728x90
profile

원지의 개발

@원지다

250x250