Today I Learned
- React 숙련 강의 수강
- Redux 특강 복습
React 숙련 강의
react-router-dom이란?
페이지 이동을 구현할 수 있게 해주는 패키지. react-router-dom을 이용하면 SPA 기반인 리액트 프로젝트 안에서 여러 개의 페이지를 구현할 수 있다.
사용방법 순서
- 페이지 컴포넌트 생성
- Router.js 생성 및 router 설정 코드 작성
- App.js에 import 및 적용
- 페이지 이동 테스트
react-router-dom 사용하기
페이지 컴포넌트 생성
pages 폴더를 만들어서 다음과 같이 컴포넌트를 생성한다.
Router.js 생성 및 route 설정 코드 작성
// src /shared/Router.js
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import한다.
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
// 2. Router 라는 함수를 만들고 아래와 같이 작성한다.
// BrowserRouter를 Router로 감싸는 이유는,
// SPA의 장점인 브라우저가 깜빡이지 않고 다른 페이지로 이동할 수 있게 만들어주기 때문이다.
const Router = () => {
return (
<BrowserRouter>
<Routes>
{/*
Routes안에 이렇게 작성합니다.
Route에는 react-router-dom에서 지원하는 props들이 있습니다.
path는 우리가 흔히 말하는 사용하고 싶은 "주소"를 넣어주면 됩니다.
element는 해당 주소로 이동했을 때 보여주고자 하는 컴포넌트를 넣어줍니다.
*/}
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
브라우저에 URL을 입력하고 이동했을 때 원하는 페이지 컴포넌트로 이동하게끔 만드는 부분이다. URL 1개당 페이지 컴포넌트를 매칭해주는 것인데 이 한 개의 URL을 Route라고 한다. 보통 Route들을 설정하는 코드는 Router.js 라는 파일을 별도로 분리해서 작성한다. src 안에 shared 라는 폴더를 추가하고, 그 안에 Router.js 파일을 생성하고 위와 같이 코드를 작성한다.
App.js에 Router.js import하기
// src /App.js
import Router from "./shared/Router";
function App() {
return <Router />;
}
export default App;
Router.js를 App 컴포넌트에 넣어주는 이유는 최상위에 존재하는 컴포넌트가 App이기 때문이다. 즉 어떤 컴포넌트를 화면에 띄우든, 항상 App.js를 거쳐야 한다. 그래서 path 별로 분기가 되는 Router.js를 App.js에 위치시키고 서비스를 이용하는 모든 사용자가 항상 App.js → Router.js 거치도록 코드를 구현하는 것이다.
react-router-dom Hooks이란?
react-router-dom에서도 리액트에서 사용하는 useState Hook 같이 유용하게 사용할 수 있는 hook을 제공한다.
useNavigate
import { useNavigate } from "react-router-dom";
const Home = () => {
const navigate = useNavigate();
return (
<button
onClick={() => {
navigate("/works");
}}
>
works로 이동
</button>
);
};
export default Home;
어떤 버튼이나 컴포넌트를 눌렀을 때 페이지로 이동하도록 사용하는 Hook이다. 버튼의 클릭 이벤트 핸들러에 위와 같이 코드를 작성하면 버튼을 클릭했을 때 우리가 보내고자 하는 path로 페이지를 이동시킬 수 있다. 꼭 버튼이 아니더라도 컴포넌트의 클릭 이벤트 핸들러를 통해서도 활용할 수 있다. 사용 방법은 navigate를 생성하고, navigate(’보내고자 하는 url’) 을 작성하면 페이지를 이동시킬 수 있다.
useLocation
import { useLocation } from "react-router-dom";
const Works = () => {
const location = useLocation();
console.log("location :>> ", location);
return (
<div>
<div>{`현재 페이지 : ${location.pathname.slice(1)}`}</div>
</div>
);
};
export default Works;
react-router-dom을 사용하면, 현재 위치하고 있는 페이지의 여러가지 정보를 추가적으로 얻을 수 있다. 이 정보들을 이용해서 페이지 안에서 조건부 렌더링에 사용하는 등 여러 가지 용도로 활용할 수 있다.
Link
import { Link, useLocation } from 'react-router-dom';
const Works = () => {
return (
<div>
<Link to="/contact">contact 페이지로 이동하기</Link>
</div>
);
};
export default Works;
Link는 Hook은 아니지만 꼭 알아야 할 API 중에 하나다. Link 는 html 태그 중에 a 태그의 기능을 대체하는 API로서 만약 JSX에서 a 태그를 사용해야 한다면, 반드시 Link를 사용해서 구현해야 한다. 왜냐하면 a 태그를 사용하면 페이지를 이동하면서 브라우저가 새로고침(refresh)되기 때문이다. 브라우저가 새로고침 되면 모든 컴포넌트가 다시 렌더링 돼야 하고, 또한 리덕스나 useState를 통해 메모리상에 구축해놓은 모든 상태값이 초기화된다. 이것은 곧 성능에 악역향을 줄 수 있고, 불필요한 움직임이 된다.
props.children의 용도
// src/shared/Layout.js
function Header() {
return (
<div style={{ ...HeaderStyles }}>
<span>Sparta Coding Club - Let's learn React</span>
</div>
);
}
function Footer() {
return (
<div style={{ ...FooterStyles }}>
<span>copyright @SCC</span>
</div>
);
}
function Layout({ children }) {
return (
<div>
<Header />
<div style={{...layoutStyles}}>
{children}
</div>
<Footer />
</div>
);
}
export default Layout;
코드를 이렇게 작성하면 어느 페이지를 가든 Header와 Footer는 항상 고정적으로 나타난다. children props를 가지고 페이지 레이아웃을 만들어 개별적으로 존재하는 Header, Footer, Page를 합성하여 개발자가 의도하는 UI를 만들어주는 Layout 컴포넌트를 만드는 것이다.
Dynamic Route란?
Dynamic Route란 동적 라우팅이라고도 하는데 path에 유동적인 값을 넣어서 특정 페이지로 이동할 수 있게 구현하는 방법을 말한다.
- react-router-dom을 통해 Dynamic Route를 설정할 수 있다.
- Dynamic Route를 설정할때는 :id로 설정하고, id 값은 useParams을 이용해서 각 컴포넌트에서 조회할 수 있다.
Dynamic Routes와 useParam 설정하기
Dynamic Route 설정하기
// src/pages/Works.js
const Works = () => {
return <div>Works</div>;
};
export default Work;
Works 페이지에 여러개의 Work가 있고, 그것을 클릭했을 때 각각의 상세페이지로 이동할 수 있게 구현하고자 한다. 일단 Work 컴포넌트를 추가한다.
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
{/* 아래 코드를 추가해주세요. 👇 */}
<Route path="works/:id" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
이전과는 다르게 path에 works/:id 라고 path가 들어간다다. :id 라는 것이 바로 동적인 값을 받겠다는 의미다. 그래서 works/1 로 이동해도 <Work /> 로 이동하고, works/2, works/3 …. works/100 모두 <Work /> 로 이동하게 해 준다.
그리고 :id 는 useParams 훅에서 조회할 수 있는 값이 된다.
query parameter 조회하기
<Route path="works/:id" element={<Work />} />
Dynamic Routes를 사용하면 element에 설정된 같은 컴포넌트를 렌더링하게 된다. 하지만 useParam을 이용하면 같은 컴포넌트를 렌더링하더라도 각각의 고유한 id값을 조회할 수 있다.
// src/pages/Works.js
import { Link, useParams } from 'react-router-dom';
const data = [
{ id: 1, todo: '리액트 배우기' },
{ id: 2, todo: '노드 배우기' },
{ id: 3, todo: '자바스크립트 배우기' },
{ id: 4, todo: '파이어 베이스 배우기' },
{ id: 5, todo: '넥스트 배우기' },
{ id: 6, todo: 'HTTP 프로토콜 배우기' },
];
function Works() {
return (
<div>
{data.map((work) => {
return (
<div key={work.id}>
<div>할일: {work.id}</div>
<Link to={`/works/${work.id}`}>
<span style={{ cursor: 'pointer' }}>➡️ Go to: {work.todo}</span>
</Link>
</div>
);
})}
</div>
);
}
export default Works;
// src/pages/Work.js
import { useParams } from 'react-router-dom';
const data = [
{ id: 1, todo: '리액트 배우기' },
{ id: 2, todo: '노드 배우기' },
{ id: 3, todo: '자바스크립트 배우기' },
{ id: 4, todo: '파이어 베이스 배우기' },
{ id: 5, todo: '넥스트 배우기' },
{ id: 6, todo: 'HTTP 프로토콜 배우기' },
];
function Work() {
const param = useParams();
const work = data.find((work) => work.id === parseInt(param.id));
return <div>{work.todo}</div>;
}
export default Work;
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';
import Contact from '../pages/Contact';
import Works from '../pages/Works';
import Layout from './Layout';
import Work from '../pages/Work';
const Router = () => {
return (
<BrowserRouter>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
<Route path="works/:id" element={<Work />} />
</Routes>
</Layout>
</BrowserRouter>
);
};
export default Router;
주의할 점: Works 컴포넌트와 Work 컴포넌트가 별개로 존재한다.
path의 있는 id 값을 조회할 수 있게 해주는 useParams라는 Hook을 사용한다. 만약에 works/1로 이동하면 1 이라는 값을 주고, works/100으로 이동하면 100 이라는 값을 사용할 수 있게 해 준다.
const param = useParams();
const work = data.find((work) => work.id === parseInt(param.id));
param이 어떻게 생긴 애인지 궁금해서 콘솔에 찍어 봤다. 딱 id 값만 들어가 있다. 궁금한 점이 param은 data라는 배열에서 id 값만 빼온 것일까? 그리고 find 안에서 왜 param.id 값에 parseInt를 써야 하는 걸까?
parseInt(param.id)로 작성해야 하는 이유
typeof(param.id)를 콘솔에 찍어보면 string이 나온다. 그런데 각 객체의 id 값은 숫자이기 때문에 find 메서드로 id 값을 비교해서 위해서 param.id 또한 숫자형으로 변환하는 것이다.
useParams() 라는 함수를 사용하면 현재 /:id 파라미터 자리에 유저가 입력한 값을 가져올 수 있다.
-> 이 부분을 보면 배열에서 id 값을 가져오는 게 아니라 주소창에 찍히는 id 값을 가져오는 거 같다.
http://localhost:3001/works/3인 경우 맨 뒤의 3이 param.id
Programmers 문제 풀기
영어가 싫어요
function solution(numbers) {
let answer = ''
let answer2 = ''
answer += numbers.replaceAll('one', '1')
answer += numbers.replaceAll('two', '2')
answer += numbers.replaceAll('three', '3')
answer += numbers.replaceAll('four', '4')
answer += numbers.replaceAll('five', '5')
answer += numbers.replaceAll('six', '6')
answer += numbers.replaceAll('seven', '7')
answer += numbers.replaceAll('eight', '8')
answer += numbers.replaceAll('nine', '9')
answer += numbers.replaceAll('zero', '0')
for (const num of answer) {
if (!isNaN(num)) {
answer2 += num
}
}
return +answer2
}
answer라는 빈 문자열을 만들고 각 숫자마다 replacsAll을 돌려서 만들어진 문자열을 계속 이어 붙인다. 그리고 answer에서 숫자인 것들은 골라서 answer2에 추가한다. 일단 여기까지 코드를 작성했는데 이렇게 했을 때 문제점은 숫자를 1에서부터 오름차순으로만 가져올 수 있다는 것이다.
테스트 3의 경우에는 통과했지만 테스트4는 정답과 반대로 정렬된 숫자를 출력하고 있다. 여기서 도저히 진행이 되지 않아서 결국 이 문제를 풀었던 팀원 분의 도움을 받았다.
function solution(numbers) {
let array = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
for (const i in array) {
numbers = numbers.replaceAll(array[i], i)
}
return parseInt(numbers)
}
내가 생각했던 방법을 완전 갈아엎어야 하는 방법이었다. 숫자를 배열로 만들어서 이것을 반복문으로 돌린다! 나도 반복문을 돌릴 생각을 하긴 했었지만 문자열인 numbers를 돌릴 생각만 했고 그래서 이걸 도대체 어디서 끊고 어떻게 반복문을 돌려야 하는 걸까 고민을 많이 했었다. 그런데 배열을 새로 만드는 방법이 있었다니... 배열을 작성할 때 주의할 점은 index가 일치해야 하기 때문에 "zero"부터 시작해야 한다는 점이다.
또 마지막까지 헤맸던 부분이 for문 안에 작성하는 코드였다. 처음에는 answer = numbers.replaceAll(...)라고 작성했었는데 정답이 이상하게 나와서 어디가 문제인지 이것저것 수정했었다. 그런데 생각해보니 number를 계속 수정해가며 replaceAll을 돌려야 하는 거였다.
콘솔로 for문이 도는 과정을 찍어 보면 이렇다.
function solution(numbers) {
numbers = numbers.replaceAll('one', '1')
numbers = numbers.replaceAll('two', '2')
numbers = numbers.replaceAll('three', '3')
numbers = numbers.replaceAll('four', '4')
numbers = numbers.replaceAll('five', '5')
numbers = numbers.replaceAll('six', '6')
numbers = numbers.replaceAll('seven', '7')
numbers = numbers.replaceAll('eight', '8')
numbers = numbers.replaceAll('nine', '9')
numbers = numbers.replaceAll('zero', '0')
return parseInt(numbers)
}
근데 답안을 작성하면서 생각한 게 내가 원래 작성했던 방식으로도 문제를 풀 수 있는 거였다. 그동안 문제가 풀리지 않았던 이유는 answer라는 비어있는 새로운 문자열을 만들어서 거기에 replaceAll을 돌렸기 때문이었다. 답이 나오려면 계속 numbers 안에서 수정해가야 하는 거였다.🙁 근데 내 답안은 for문을 하나하나 풀어서 쓴 것이기 때문에 팀원 분의 답안이 더 보기 편한 거 같다.
OX퀴즈
function solution(quiz) {
for (const x of quiz) {
console.log(x)
}
}
배열에 for문을 돌려서 식을 하나씩 꺼낸 다음에 문자열을 숫자형으로 변환해서 그 값이 true이면 O를 false이면 X를 출력하도록 하고 싶은데 "3 - 4 = -3" 이렇게 비교 연산자가 들어간 문자열을 숫자로 바꿀 수 있는 방법을 모르겠다. 그냥 문자열에서 따옴표만 제거하는 방법도 해봤는데 안 됐다.
function solution(quiz) {
let answer = [];
for (const x of quiz) {
// if (x) {
if (x == true) {
answer.push("X")
} else {
answer.push("O")
}
}
return answer
}
이렇게 하면 정답과 얼추 비슷한 형태의 배열이 나오긴 하지만 전혀 작동을 못하는 코드라고 보면 된다ㅋㅋ 나는 문자열을 숫자형으로 변환해 편하게 가고 싶었지만 튜터님께 여쭤본 결과 그런 방법은 없고 숫자를 하나씩 꺼내서 계산해야 하는 거 같다. 내가 너무 쉽게 생각했던 거지 ㅎㅎ
회고
오늘 드디어 리액트 숙련 강의를 다 보고 오후에는 지난주에 있었던 리덕스 특강 2교시를 다시 봤다. 그때는 리덕스 강의를 다 듣기 전이었기 때문에 나중에 다시 들으려고 2교시는 10분 정도 듣다가 나왔었는데 오늘 다시 들으니 확실히 이해가 잘 안 된다. 그리고 숙련 강의를 며칠에 걸쳐서 듣느라 중간중간 과정이 끊긴 느낌이었다면 특강은 한 시간 동안 리덕스로 코드를 작성하는 과정을 한번에 볼 수 있어서 전체적인 흐름을 정리하기에 좋은 거 같다.
이제 드디어 내일부터 숙련 과제를 진행하려고 한다! 이미 입문 과제 수행하면서 만들어 놓은 결과물이 있기 때문에 이걸 리덕스에 맞춰서 수정하면 될 거 같다. 내일부터 또 재밌는 하루가 되겠구만😤