1) SPA란?
: Single Page Application이라고 한다. 말 그대로 서버에서 주는 html이 1개 뿐인 어플리케이션이에요.
전통적인 웹사이트는 페이지를 이동할 때마다 서버에서 html, css, js(=정적자원들)을 내려준다면,
SPA는 딱 한번만(처음 로딩 할 때) 정적자원을 받아옵니다.
- 왜 굳이 html을 하나만 줄까?
→ 많은 이유가 있지만, 그 중 제일 중요한 건 사용성 때문입니다.
페이지를 이동할 때마다 서버에서 주는 html로 화면을 바꾸다보면 상태 유지가 어렵고, 바뀌지 않은 부분까지 새로 불러오니까 비효율적이거든요. (예를 들어서 사용자가 회원가입 하다가 적었던 내용이 날아갈 수도 있고, 블로그 같은 경우, 페이지마다 새로 html을 받아오면 바뀐 건 글 뿐인데 헤더와 카테고리까지 전부 다시 불러와야 합니다.)
- 단점은 없나?
→ 단점도 있어요. SPA는 딱 한 번(처음 로딩 할 때) 정적자원을 내려받다보니, 처음에 모든 컴포넌트를 받아옵니다.
즉, 사용자가 안들어가 볼 페이지까지 전부 가지고 옵니다.
게다가 한 번에 전부 가지고 오니까 아주아주 많은 컴포넌트가 있다면 첫 로딩 속도가 느려집니다.
2) 라우팅이란?
: 페이지에서 페이지로 이동하는 방법
- SPA는 주소를 어떻게 옮길 수 있을까?
html은 딱 하나를 가지고 있지만, SPA도 브라우저 주소창대로 다른 페이지를 보여줄 수 있어요.
이렇게 브라우저 주소에 따라 다른 페이지를 보여주는 걸 라우팅이라고 부릅니다.
- 전부 직접 구현하나요?
→ 이미 만들어진 라우팅 라이브러리가 있습니다! -> react-router-dom 패키지
우리는 리액트 사용자들이 가장 많이 쓰는 라우팅 라이브러리(react-rounter-dom 패키지)를 가져와서 사용해볼거예요.
리액트에서 라우팅 처리하기
1) react-rounter-dom 설치
: react-router-dom의 6버전과 5버전은 사용 방법이 많이 달라요! 설치할 때 꼭! @5.2.1을 추가하시어 5버전으로 설치해주세요. (강의는 5버전을 기준으로 합니다. 🙂)
yarn add react-router-dom@5.2.1
+ react-router-dom 공식 문서
https://v5.reactrouter.com/web/guides/primary-components
2) 페이지를 전환 해보자!!
(1) index.js에 BrowserRouter 적용하기
+ BrowserRouter란 페이지가 실제로 주소창을 참고하여 이동 되도록 해주는 역할
(즉, 주소창을 보고 내 컴포넌트를 분기(나누다) 할 수 있게 해주는 친구)
import React from 'react';
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 이부분이 index.html에 있는 div#root에 우리가 만든 컴포넌트를 실제로 랜더링하도록 연결해주는 부분입니다.
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
BrowserRouter(브라우저라우터)는 웹 브라우저가 가지고 있는 주소 관련 정보를 props로 넘겨주는 친구입니다.
현재 내가 어느 주소를 보고 있는 지 쉽게 알 수 있게 도와줘요.
(2) 세부 화면 만들기(이동할 페이지 컴포넌트)
-> Home.js
import React from "react";
const Home = (props) => {
return (
<div>메인 화면이에요.</div>
)
}
export default Home;
+ Cat.js / Dog.js도 text만 다르고 다른 건 같음
(3) App.js에서 Route 적용하기
+ Route란 분기점을 알려주는 역할 (실제로 페이지를 분기 해주는 친구)
- Route 사용방법 1: 넘겨줄 props가 없을 때
<Route path="주소[/home 처럼 /와 주소를 적어요]" component={[보여줄 컴포넌트]}/>
- Route 사용방법 2: 넘겨줄 props가 있을 때
(우리 버킷리스트 앱은 App.js에서 list를 props로 넘겨주죠! 그럴 땐 이렇게 쓰면 됩니다!)
<Route path="주소[/home 처럼 /와 주소를 적어요]" render={(props) => (<BucketList list={this.state.list} />)} />
- Route 사용방법 3 : 최신 버전
<Dog dog_name="saebom"></Dog>
console.log(props.dog_name)
-> App.js에 실제로 적용 해보기!
import React from 'react';
import logo from './logo.svg';
import './App.css';
// Route를 먼저 불러와줍니다.
import { Route } from "react-router-dom";
// 세부 페이지가 되어줄 컴포넌트들도 불러와주고요!
import Home from "./Home";
import Cat from "./Cat";
import Dog from "./Dog";
class App extends React.Component {
constructor(props){
super(props);
this.state={};
}
render(){
return (
<div className="App">
{/* 실제로 연결해볼까요! */}
<Route path="/" component={Home} />
<Route path="/cat" component={Cat} />
<Route path="/dog" component={Dog} />
</div>
);
}
}
export default App;
(4) exact 적용하기
: exact가 없을 때는 포함하면 다 보여줘! 였는 데 exact를 붙이면 path랑 완전히 똑같으면(===) 보여줘라.
import React from 'react';
import logo from './logo.svg';
import './App.css';
// Route를 먼저 불러와줍니다.
import { Route } from "react-router-dom";
// 세부 페이지가 되어줄 컴포넌트들도 불러와주고요!
import Home from "./Home";
import Cat from "./Cat";
import Dog from "./Dog";
class App extends React.Component {
constructor(props){
super(props);
this.state={};
}
render(){
return (
<div className="App">
{/* 실제로 연결해볼까요! */}
<Route path="/" exact exact component={Home} />
<Route path="/cat" exact component={Cat} />
<Route path="/dog" exact component={Dog} />
</div>
);
}
}
export default App;
(5) URL 파라미터 사용하기
- 웹사이트 주소에는 파라미터와 쿼리라는 게 있어요.
-> 파라미터 : /cat/nabi
-> 쿼리 : /cat?cat_name=nabi&cat_age=1
- 파라미터 주는 방법 (동적 라우팅)
//App.js
...
// 파라미터 주기
<Route path="/cat/:cat_name" component={Cat}/>
...
- 파라미터 사용 방법
import React from 'react'
import { useParams } from 'react-router';
const Cat = (props)=>{
// react에 hook 중 useParams를 사용해서
// 데이터를 불러 올 수 있다.
const cat_name = useParams();
console.log(cat_name)
console.log(props)
return (
<div onClick={()=>{
props.history.push("/")
}}>고양이 화면 입니다.</div>
);
}
export default Cat;
(6) 링크 이동 시키기
: 매번 주소창을 찍고 페이지를 돌아다닐 순 없겠죠! react-router-dom으로 페이지를 이동하는 방법을 알아봅시다!
- <Link/> 사용하기
: 링크 컴포넌트는 html 중 a 태그와 비슷한 역할을 해요. 리액트 내에서 페이지 전환을 도와줍니다.
<Link to="주소">[텍스트]</Link>
-> App.js에 메뉴를 넣어보자!
→ 우리가 만든 메뉴처럼 <route> 바깥에 있는 돔요소는 페이지가 전환되어도 그대로 유지됩니다. (편리하죠!)
// Route를 먼저 불러와줍니다.
// Link 컴포넌트도 불러왔어요.
import { Route, Link } from "react-router-dom";
// 세부 페이지가 되어줄 컴포넌트들도 불러와주고요!
import Home from "./Home";
import Cat from "./Cat";
import Dog from "./Dog";
function App() {
return (
<div className="App">
<div>
<Link to="/">Home으로 가기</Link>
<Link to="/cat">Cat으로 가기</Link>
<Link to="/dog">Dog으로 가기</Link>
</div>
{/* 실제로 연결해볼까요! */}
<Route path="/" exact>
<Home />
</Route>
<Route path="/cat" component={Cat}>
{/* <Cat /> */}
</Route>
<Route path="/dog">
<Dog />
</Route>
</div>
);
}
export default App;
- history 사용하기
: Link 컴포넌트를 클릭하지 않고 페이지를 전환하는 방법 두 가지를 알아봅시다!
(1) props로 history 객체를 받아 이동하기
import React from "react";
const Dog = (props) => {
// props의 history 객체를 살펴봅시다.
console.log(props);
// 그리고 history.push('/home')으로 페이지 이동도 해봐요!
return (
<div
onClick={() => {
props.history.push("/home");
}}
>
강아지 화면이에요.
</div>
);
};
export default Dog;
(2) useHistory 훅을 사용해서 이동하기
: 꼭 props에서 받아오지 않아도, useHistory 훅을 사용하면 간단히 history 객체에 접근할 수 있어요!
페이지 이동할 때 써먹으면 엄청 편하겠죠.
import React from "react";
import { useHistory } from "react-router-dom";
const Home = (props) => {
let history = useHistory();
return (
<>
<div>메인 화면이에요.</div>
<button
onClick={() => {
history.push("/cat");
}}
>
cat으로 가기
</button>
</>
);
};
export default Home;
퀴즈 - 버킷리스트 상세 페이지 만들고 이동 시키기
👻 힌트
1. Detail.js라는 파일 하나를 만들고 <Detail/> 컴포넌트를 만드세요!
2. 어떤 버킷 리스트 항목을 눌러도 그 페이지로 가게 해볼거예요!
3. history.{}를 써봐요! ({}엔 뭐가 들어갈까요?)
4. 꼭 코드스니펫을 복사해서 써주세요.
5. 버킷리스트 컴포넌트에서는 useRef를 쓰는 대신 element에 직접 onClick을 줘서 해봅시다!
-> App.js
import React from "react";
import styled from "styled-components";
import { Route } from "react-router-dom";
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import Detail from "./Detail";
function App() {
const [list, setList] = React.useState(["영화관 가기", "매일 책읽기", "수영 배우기"]);
const text = React.useRef(null);
const addBucketList = () => {
// 스프레드 문법! 기억하고 계신가요? :)
// 원본 배열 list에 새로운 요소를 추가해주었습니다.
setList([...list, text.current.value]);
}
console.log(list);
return (
<div className="App">
<Container>
<Title>내 버킷리스트</Title>
<Line />
{/* 컴포넌트를 넣어줍니다. */}
{/* <컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/> */}
<Route
path="/"
exact
render={(props) => (
<BucketList list={list}/>
)}
/>
<Route path="/detail" component={Detail} />
</Container>
{/* 인풋박스와 추가하기 버튼을 넣어줬어요. */}
<Input>
<input type="text" ref={text} />
<button onClick={addBucketList}>추가하기</button>
</Input>
</div>
);
}
const Input = styled.div`
max-width: 350px;
min-height: 10vh;
background-color: #fff;
padding: 16px;
margin: 20px auto;
border-radius: 5px;
border: 1px solid #ddd;
`;
const Container = styled.div`
max-width: 350px;
min-height: 60vh;
background-color: #fff;
padding: 16px;
margin: 20px auto;
border-radius: 5px;
border: 1px solid #ddd;
`;
const Title = styled.h1`
color: slateblue;
text-align: center;
`;
const Line = styled.hr`
margin: 16px 0px;
border: 1px dotted #ddd;
`;
export default App;
-> Detail.js
// 리액트 패키지를 불러옵니다.
import React from "react";
const Detail = (props) => {
return <h1>상세 페이지입니다!</h1>;
};
export default Detail;
-> BucketList.js
// 리액트 패키지를 불러옵니다.
import React from "react";
import styled from "styled-components";
import { useHistory } from "react-router-dom";
const BucketList = (props) => {
let history = useHistory();
console.log(props);
const my_lists = props.list;
return (
<ListStyle>
{my_lists.map((list, index) => {
return (
<ItemStyle
className="list_item"
key={index}
onClick={() => {
history.push("/detail");
}}
>
{list}
</ItemStyle>
);
})}
</ListStyle>
);
};
const ListStyle = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
`;
const ItemStyle = styled.div`
padding: 16px;
margin: 8px;
background-color: aliceblue;
`;
export default BucketList;
-> index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
+ 라우팅, 조금 더 꼼꼼히 쓰려면?
1) 잘못된 주소 처리하기
: exact로 중복 주소를 처리하는 방법은 이미 배웠습니다! 이번엔 우리가 미리 정하지 않은 주소로 들어온 경우를 다뤄볼게요. (저는 버킷리스트 프로젝트에서 진행합니다!)
-> 정해지지 않은 주소로 접근 했을 때 주소 처리 하는 방법(Switch)
-> NotFound.js 파일을 만들어 빈 컴포넌트를 만듭니다.
import React from "react";
const NotFound = (props) => {
return <h1>주소가 올바르지 않아요!</h1>;
};
export default NotFound;
-> App.js에서 불러옵니다.
import NotFound from "./NotFound";
-> App.js에 Switch를 추가해주고.
...
import { Route, Switch } from "react-router-dom";
...
return (
<div className="App">
...
<Switch>
<Route
path="/"
exact
render={(props) => (
<BucketList
list={this.state.list}
history={this.props.history}
/>
)}
/>
<Route path="/detail" component={Detail} />
</Switch>
...
</div>
);
+ Switch란?
<Switch> 는 첫번째로 매칭되는 path 를 가진 컴포넌트를 렌더링 시킨다.
이것이 exact, path 와 다른 점은 첫번째 매칭만 본다는 것이다.
https://baeharam.netlify.app/posts/react/why-switch-is-needed
-> NotFound 컴포넌트를 Route에 주소(path) 없이 연결하면 끝!
...
<Switch>
<Route
path="/"
exact
render={(props) => (
<BucketList
list={this.state.list}
history={this.props.history}
/>
)}
/>
<Route path="/detail" component={Detail} />
<Route component={NotFound} />
</Switch>
...
+ <NotFound/>에 뒤로가기 버튼을 달아보자!
-> NotFound.js에서 useHistory를 가져오는 게 먼저!
import { useHistory } from "react-router-dom";
-> 버튼을 만들고 주고
import React from "react";
import { useHistory } from "react-router-dom";
const NotFound = (props) => {
return (
<div>
<h1>주소가 올바르지 않아요!</h1>
<button>뒤로가기</button>
</div>
);
};
export default NotFound;
-> useHistory를 사용해서 뒤로가기를 만들어요!
import React from "react";
import { useHistory } from "react-router-dom";
const NotFound = (props) => {
let history = useHistory();
return (
<div>
<h1>주소가 올바르지 않아요!</h1>
<button
onClick={() => {
history.push('/');
}}
>
뒤로가기
</button>
</div>
);
};
export default NotFound;
'항해 중 > 3주차 리액트 기초반' 카테고리의 다른 글
3주차 - 리덕스, 리덕스를 통한 리액트 상태관리 (0) | 2021.11.18 |
---|---|
3주차 개인 과제 (0) | 2021.11.16 |
3주차 - Event Listener (0) | 2021.11.16 |
2주차 - 숙제 (0) | 2021.11.15 |
2주차 - State 관리 (0) | 2021.11.15 |
댓글