본문 바로가기
항해99

리액트 숙련주차4

by Hyeon_E 2023. 6. 28.

[ React Router Dom - 소개, hooks, children ]

▶ react-router-dom

페이지를 구현할 수 있게 해주는 패키지

 react-router-dom을 이용하면 여러 페이지를 가진 웹을 만들 수 있음

 

▶ react-router-dom 설치하기

패키지 설치

터미널에 입력하여 패키지 설치

 

yarn add react-router-dom

 

▶ react-router-dom 사용하기

사용방법 순서

  1. 페이지 컴포넌트 생성
  2. Router.js 생성 및 router 설정 코드 작성
  3. App.js에 import 및 적용
  4. 페이지 이동 테스트

 

페이지 컴포넌트 생성

src 폴더에 pages라는 폴더를 만들고 그 안에 페이지를 만듬

 

Router.js 생성 및 route 설정 코드 작성

브라우저에 우리가 URL을 입력하고 이동했을 때 우리가 원하는 페이지 컴포넌트로 이동하게끔 만드는 부분

URL 1개당 페이지 컴포넌트를 매칭해주는 것. 이 한개의 URL을 Route라고 함

그리고 Route들을 설정하는 코드는 Router.js 라는 파일을 별도로 분리해서 많이 작성하곤 함

src안에 shared 라는 폴더를 생성해주고 그 안에 Router.js 파일을 생성해줌

 

Router.js 생성 및 route 설정 코드 작성

Router.js 생성

import React from "react";
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import 함
import { BrowserRouter, Route, Routes } from "react-router-dom";

// 2. Router 라는 함수를 만들고 아래와 같이 작성
// BrowserRouter를 Router로 감싸는 이유는 SPA의 장점인 브라우저가 깜빡이지 않고
// 다른 페이지로 이동할 수 있게 만들어줌
const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
      </Routes>
    </BrowserRouter>
  );
};

export default Router;

 

route 설정 코드 작성

 

 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>
        {/* 
            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;

 

 

App.js에 Router.js import 해주기

import React from "react";
import Router from "./shared/Router";
function App() {
  return <Router />;
}
export default App;

 

Router.js를 App 컴포넌트에 넣어주는 이유는 프로젝트에서 가장 최상위에 존재하는 컴포넌트가 App.js이기 때문

즉 우리가 어떤 컴포넌트를 화면에 띄우던 항상 App.js를 거쳐야만 함. 그래서 path별로 분기가 되는 Router.js 를 App.js에 위치시키고 서비스를 이용하는 모든 사용자가 항상 App.js → Router.js 거치도록 코드를 구현해주는 것

 

페이지 이동 테스트

프로젝트를 실행시키고 영상처럼 브라우저에 paty를 입력하면 각 페이지로 이동됨

 

▶ react-router-dom Hooks

react-router-dom Hooks

react-router-dom에서도 유용하게 사용할 수 있는 hook을 제공함

 

useNavigate

다른 페이지로 보내고자 할 때 사용할 수 있음

위에서 테스트 했을 때 우리가 브라우저에 직접 path를 입력해서 페이지를 이동했지만 브라우저에 path를 직접 입력해서 페이지를 이동하게 만드는 서비스는 거의 없을 것. 보통 어떤 버튼을 누르면 페이지로 이동하거나 또는 어떤 컴포넌트를 눌렀을 때 페이지를 이동하게 만듬. 이럴때 사용하는 훅. navigate를 생성하고 navigate(’보내고자 하는 url’)을 통해 페이지를 이동 시킬 수 있음

 

// src/pages/home.js
import { useNavigate } from "react-router-dom";
const Home = () => {
  const navigate = useNavigate();
  return (
    <button
      onClick={() => {
        navigate("/works");
      }}
    >
      works로 이동
    </button>
  );
};
export default Home;

 

useLocation

react-router-dom을 사용하면 현재 위치하고 있는 페이지의 여러가지 정보를 추가적으로 얻을 수 있음

이 정보들을 이용해서 페이지 안에서 조건부 렌더링에 사용하는 등 여러가지 용도로 활용할 수 있음

 

// src/pages/works.js
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;

 

Link

Link 는 훅이 아니지만 꼭 알아야 할 API. Link는 html 태그중에 a 태그의 기능을 대체하는 API

만약 JSX에서 a 태그를 사용해야 한다면 반드시 Link 를 사용해서 구현해야 함

왜냐하면 a 태그를 사용하면 페이지를 이동하면서 브라우저가 새로고침(refresh)되기 때문

 

브라우저가 새로고침 되면 모든 컴포넌트가 다시 렌더링되야 하고 또한 리덕스나 useState를 통해 메모리상에 구축해놓은 모든 상태값이 초기화됨. 이것은 곧 성능에 악역향을 줄 수 있고 불필요한 움직임입니다.

 

import { Link, useLocation } from 'react-router-dom';

const Works = () => {
  const location = useLocation();
  console.log('location :>> ', location);
  return (
    <div>
      <div>{`현재 페이지 : ${location.pathname.slice(1)}`}</div>
      <Link to="/contact">contact 페이지로 이동하기</Link>
    </div>
  );
};

export default Works;

 

페이지를 이동시키고자 할때 useNavigate 또는 Link를 사용할 수 있음

구현할때 더 적합한 방식으로 API를 활용하면 됨. 이 밖에도 URL의 query를 알 수 있는 useParams 등 많은 Hook이 있음

 

▶ children 용도

공식 문서에는 props.children에 대해 아래와 같이 설명하고 있음

어떤 컴포넌트들은 어떤 자식 엘리먼트가 들어올지 미리 예상할 수 없는 경우가 있습니다. 범용적인 ‘박스’ 역할을 하는 Sidebar 혹은 Dialog와 같은 컴포넌트에서 특히 자주 볼 수 있습니다

여기서 말하는 범용적인 “박스” 역할을 하는 컴포넌트란 크게 봤을 때 Layout 역할을 하는 컴포넌트

children props를 찾아보면 composition이라는 말을 많이 보게 되는데 composition은 합성이라는 의미가 있음

 

// src/shared/Layout.js

import React from 'react';

const HeaderStyles = {
  width: '100%',
  background: 'black',
  height: '50px',
  display: 'flex',
  alignItems: 'center',
  paddingLeft: '20px',
  color: 'white',
  fontWeight: '600',
};
const FooterStyles = {
  width: '100%',
  height: '50px',
  display: 'flex',
  background: 'black',
  color: 'white',
  alignItems: 'center',
  justifyContent: 'center',
  fontSize: '12px',
};
const layoutStyles = {
  display: 'flex',
  flexDirection: 'column',
  justifyContent: 'center',
  alignItems: 'center',
  minHeight: '90vh',
}
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;
//src/shared/Router.js

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';

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 />} />
        </Routes>
      </Layout>
    </BrowserRouter>
  );
};

export default Router;

 

▶ 정리

  • react-router-dom을 이용하면 SPA 기반인 리액트 프로젝트 안에서 여러개의 페이지를 구현할 수 있음
  • Router.js에 Route 설정에 관련된 코드를 작성하고 최상위 컴포넌트인 App.js에서 import 해서 사용
  • react-router-dom는 여러가지 훅을 제공

 

[ React Router Dom - Dynamic Route, useParam ]

▶ 상세 페이지 구현

Dynamic Route

Dynamic Route는 동적 라우팅이라고도 말하는데 path에 유동적인 값을 넣어서 특정 페이지로 이동하게끔 구현하는 방법

react-router-dom에서 지원하는 Dynamic Routes 기능을 이용해서 간결하게 동적으로 변하는 페이지를 처리할 수 있음

 

Dynamic Route 설정

Router.js로 이동해서 Dynamic Routes를 설정

 

// src/pages/Works.js

import React from "react";

const Works = () => {
  return <div>Works</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";

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, works/2 ... works/100 모두 <Work /> 로 이동하게 해줌. :iduseParams 훅에서 조회할 수 있는 값이 됨

 

▶ Dynamic Routes와 useParam

query parameter 조회하기

Dynamic Routes를 사용하면 element에 설정된 같은 컴포넌트를 렌더링하게 됨

 

<Route path="works/:id" element={<Work />} />

 

위에 예제 코드에서도 Work 페이지 컴포넌트를 동일하게 렌더링됨

하지만 useParam을 이용하면 같은 컴포넌트를 렌더링하더라도 각각의 고유한 id 값을 조회할 수 있음

useParams path의 있는 id 값을 조회할 수 있게 해주는 훅

 

// src/pages/Works.js
import React from 'react';
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 React from 'react';
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;

 

▶ 정리

  • react-router-dom을 통해 Dynamic Route를 설정할 수 있음
  • Dynamic Route를 설정할때는 :id로 설정하고 id 값은 useParams을 이용해서 각 컴포넌트에서 조회할 수 있음

 

[ 비동기 프로그래밍 입문 ]

▶ 비동기 프로그래밍 개요

개념

동기적(synchronous) 방식

동기적 프로그래밍은 코드는 모드 동기적으로 실행됨

앞선 코드가 끝나야만 다음 코드가 수행됨. 일반적인 프로그래밍 세계에서는 코드는 모드 동기적으로 실행

 

// (1)
const a = 1;

// (2)
const b = 2;

// (3)
console.log(`a의 값은 ${a}입니다.`);

// (4)
alert(`hello ${b}!`);

// 항상 코드는 (1) -> (4)의 순서대로 실행이 됩니다.

 

 

비동기(asynchronous)적 방식

실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식

  • setTimeout, addEventListner 등
  • 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드
  • 대표적으로 서버 통신과 관련된 로직들 포함

 

콜백지옥

비동기적 프로그래밍을 하다 보면 예기치 못한 상황 즉, 콜백지옥 있음

 

//0.5초 주기마다 커피 목록을 수집하고 출력
//각 콜백은 커피 이름을 전달하고 목록에 이름을 추가
//문제점 : 들여쓰기, 값 전달의 순서가 아래 -> 위

setTimeout(function (name) {
	var coffeeList = name;
	console.log(coffeeList);
    
	setTimeout(function (name) {
		coffeeList += ', ' + name;
		console.log(coffeeList);

		setTimeout(function (name) {
			coffeeList += ', ' + name;
			console.log(coffeeList);

			setTimeout(function (name) {
				coffeeList += ', ' + name;
				console.log(coffeeList);
			}, 500, '카페라떼');
            
		}, '카페모카');
        
	}, '아메리카노');
    
}, '에스프레소');

 

콜백지옥과 콜백지옥의 문제점

  • 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬 수준인 경우
  • 주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생
  • 가독성 낮고 수정이 어려움

 

▶ Promise 객체

개념

mds 공식문서에서는 이렇게 설명하고 있음

Promise 객체는 비동기 작업을 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다

 

이름이 약속인 이유

비동기 작업이라는 것은 수행의 제어권을 제 3자에게 넘겨준 후 작업이 완료되면 안내를 받아 제어권을 이양받는 식의 일 처리 방법

  1. localhost:3000 에서 잘 놀고 있다가 버튼을 클릭해서 네이버 서버에 날씨 정보를 달라고 API 문서에 적혀있는대로 요청함
  2. 그럼 그 API 문서에 적혀있는 약속대로 약 0.3sec가 지나고 나니 데이터가 뿅 하고 들어옴

위 과정에서 localhost에 있을 때의 모든 제어권은 나에게 있다가 네이버에 api 요청을 하는 순간 제어권은 네이버에게 넘어감. 그 이후 데이터를 전달받는 순간 다시 제어권은 나에게 넘어옴

비동기처리는 이처럼 약속에 의해 움직임. 그 약속에 관련된 사항들이 모두 Promise 객체에 담기는 것

 

promise 객체에 담기는 주요한 상태정보

  1. 대기 : pending, localhost에서 네이버에 요청한 직후. 아직 성공(resolve) 또는 실패(rejected)되지 않은 상태
  2. 이행 : fulfilled, 네이버가 정상적으로 데이터를 localhost한테 전달을 성공적으로 해준 상태
  3. 거부 : rejected, 어떠한 이유인지는 모르겠지만 네이버가 localhost에게 데이터를 전달을 못해준 경우

 

Promise 객체 핸들링 방법

promise 객체가 갖는 주요한 상태정보 3가지에 따라 적절한 처리를 해줘야 함

 

# then ~ catch

// http://api.naver.com/weather/today 로 요청을 한다고 가정

axios.get('http://api.naver.com/weather/today')
.then(response => {
	console.log('정상처리 되었습니다 : ' + response);
})
.catch(error => {
	console.log('오류가 발생하였습니다 : ' + error);
})
.finally(()=>{
	console.log('항상 실행되는 부분입니다!');
});

 

# async / await

const getWeather = async () => {
	try {
		const response = axios.get('http://api.naver.com/weather/today');
		console.log('정상처리 되었습니다 : ' + response);
	} catch (error) {
		console.log('오류가 발생하였습니다 : ' + error);
	}
}

 

[ REST(Path Variable vs Query Parameter) ]

▶ REST API

개념

REpresentational State Transfer의 약자로서 어떤 자원에 대해 CRUD를 진행할 수 있게 HTTP Method(GET, POST, PUT, DELETE)를 사용하여 요청을 보내는 것. 이 때 요청을 위한 자원은 특정한 형태로 표현됨

“URI를 통해 정보의 자원을(only 자원만을) 표현하고 자원의 행위는 HTTP Method로 명시한다” 정도로 이해하면 됨

 

  • 자원(Resource) : URI
  • 행위(Verb) : HTTP Method
  • 표현(Representations)
GET /users/3/profile

 

위에 요청을 보면 누구라도 user 중에서 3번 아이디를 갖고 있는 사람의 프로필을 줘라는 것을 알 수 있음

이것이 바로 REST API의 장점

 

예시

// bad
GET /members/delete/1
// good
DELETE /members/1

// bad
GET /members/show/1
// good
GET /members/1

// bad
GET /members/insert/2
// good
POST /members/2
//규칙

http://example.com/posts     (O)
http://example.com/posts/    (X)
http://example.com/post      (X)
http://example.com/get-posts (X)
--> URI는 명사를 사용하고 소문자로 작성
--> 명사는 복수형을 사용
--> URI의 마지막에는 /를 포함하지 않음

http://example.com/post-list  (O)
http://example.com/post_list  (X)
--> URI에는 언더바가 아닌 하이픈을 사용

http://example.com/post/assets/example  (O)
http://example.com/post/assets/example.png  (X)
--> URI에는 파일의 확장자를 표시하지 않음

 

RestFul하다는 것

REST API의 까다로운 조건을 만족시킨 통신 설계 상태를 말함

누가 보더라도 이해하기 쉽고 사용하기 쉬운 REST API라면 “RestFul하구나!” 라고 할 수 있음

 

RestFul하지 못하다는 것

  • CRUD의 기능을 모두 POST method로만 이용하는 경우
  • URI에 행위(method)에 대한 부분이 들어가는 경우(/classes/createPeople)

 

▶ Path Variable vs Query Parameter

개요

REST한 개발을 하다 보면 GET method를 이용하여 데이터를 가져올 때 등의 경우에서 Path Variable과 Query Parameter에 대한 이야기는 항상 같이 나옴

 

Path Variable

/users/10

 

특징

  • 이름에서도 유추할 수 있듯 경로 자체에 변수(10)를 사용한 방법
  • 전체 데이터 또는 특정 하나의 데이터를 다룰 때 처럼 리소스를 식별하기 위해 사용됨

 

Query Parameter

/users?user_id=10

 

특징

  • 데이터를 정렬하거나 필터링 하는 경우 더 적합함

 

예시를 통해 비교해보기

/users # Fetch a list of users
/users?occupation=programer # Fetch a list of programer user
/users/123 # Fetch a user who has id 123

 

[ JSON ]

▶ 개념

JSON : JavaScript Object Notation

자바스크립트 객체 문법에 토대를 둔, 문자 기반의 데이터 교환 형식

 

▶ 구조

일반적인 JSON 구조는 자바스크립트 객체 리터럴 작성법을 따름

자바스크립트의 원시 자료형인 문자열, 숫자, 불리언을 가질 수 있고 중첩된 계층 구조 또한 가질 수 있음

 

{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    },
    {
      "name": "Eternal Flame",
      "age": 1000000,
      "secretIdentity": "Unknown",
      "powers": [
        "Immortality",
        "Heat Immunity",
        "Inferno",
        "Teleportation",
        "Interdimensional travel"
      ]
    }
  ]
}

 

자바스크립트 객체 형태와 완전히 같지는 않고 비슷한 것 뿐.  작은 따옴포(’’)가 아닌 끝 따옴표(””)만이 허용됨

 

메서드

JSON → 문자열 형태 → 서버 - 클라이언트 간 데이터 전송 시 사용

하지만 다음 두 경우를 위해 파싱(parsing) 과정이 필요함

  • JS 객체를 JSON 형태로 전송
  • JSON 형태를 JS 객체 형태로 사용

# stringify()

: 자바스크립트 객체 → JSON 문자열 변환. 네트워크를 통해 객체를 JSON 문자열로 변환할 때 주로 사용함

 

console.log(JSON.stringify({ x: 5, y: 6 }));
// Expected output: "{"x":5,"y":6}"

console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)]));
// Expected output: "[3,"false",false]"

console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }));
// Expected output: "{"x":[10,null,null,null]}"

console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)));
// Expected output: ""2006-01-02T15:04:05.000Z""

 

# parse()

: JSON 문자열 → 자바스크립트 객체 변환. 네트워크 통신의 결과를 통해 받아온 JSON 문자열을 프로그램 내부에서 사용하기 위해 JS 객체로 변환할 때 사용

 

const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);

console.log(obj.count);
// Expected output: 42

console.log(obj.result);
// Expected output: true

 

▶ JSONPlaceholder

가짜 서버(= fake server, mock API server)

 

API를 통해 JSON 기반 DB 통신을 일으키기

 

fetch('https://jsonplaceholder.typicode.com/posts')
  .then((response) => response.json())
  .then((json) => console.log(json));
//App.jsx
import "./App.css";
import { useEffect, useState } from "react";
function App() {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((response) => {
        console.log();
        console.log("response.json()", response);
        return response.json();
      })
      .then((json) => {
        console.log("json", json);
        setData([...json]);
      });
  }, []);

  return (
    <div>
      {data.map((item) => {
        return (
          <div
            style={{
              border: "1px solid black",
              margin: "3px",
            }}
            key={item.id}
          >
            <ul>
              <li>userId : {item.userId}</li>
              <li>id : {item.id}</li>
              <li>title : {item.title}</li>
              <li>body : {item.body}</li>
            </ul>
          </div>
        );
      })}
    </div>
  );
}

export default App;

 

다음과 같은 결과를 얻을 수 있음

 

'항해99' 카테고리의 다른 글

리액트 심화주차2  (0) 2023.07.04
리액트 심화주차1  (3) 2023.07.04
리액트 숙련주차 3  (0) 2023.06.27
리액트 숙련주차 2  (1) 2023.06.27
리액트 숙련주차 1  (0) 2023.06.27

댓글