[React] React 기본정리 part2

쇼핑몰 프로젝트 : 프로젝트 생성 & Bootstrap 설치

이번 시간부터는 또 다른 프로젝트를 하나 만들어보겠습니다.

있어보이는 쇼핑몰 프로젝트인데 일단 프로젝트 생성부터 새롭게 시작해봅시다.

그리고 CSS 덜짜기 위해 React Bootstrap 이라는 라이브러리를 설치해서 이용할 예정입니다.

0 . 프로젝트 설치가 오래걸린다면 이번엔 yarn 이라는걸 이용해봅시다

구글에 yarn 1.22 이거 검색하시면 설치사이트가 하나 나옵니다.

거기서 1.22 이상의 버전의 installer를 다운받아 설치 + 컴퓨터 재시작까지 하시면 됩니다.

맥이든 윈도우든 설치과정은 똑같습니다.

(주의) 2.X 버전은 아직 불안정해서 1.X 버전으로 설치하는걸 추천드립니다

yarn이 뭐하는 애냐면 npm이나 npx를 대체해서 이용할 수 있게 도와주는 친구입니다.

npm보다 훨씬 빠른 라이브러리 설치속도와 안정성을 자랑합니다.

일단 설치해놓으면 npx create-react-app 어쩌구 하실 때 자동으로 yarn이 구동되어 빨라집니다.

누군가가 npm install 어쩌구 하라고 하면 그거 대신 yarn add 어쩌구 라고 사용하시면 더 빠르게 설치할 수 있으며

npm run start 로 미리보기 띄울 때도 yarn start 이걸로 대체할 수 있습니다.

에러) 맥유저의 경우 “권한이 없어요” 에러가 뜬다면 sudo라는 단어를 앞에 붙여서 해보시길 바랍니다.

기타 에러는 1강 혹은 구글검색을 참고합니다.

본격적으로 새로운 프로젝트 생성을 해보도록 합시다.

20220427_103626

1 . 작업폴더를 VScode 에디터로 오픈한 뒤에 하단 터미널을 열어줍니다.

▲ 그럼 위처럼 작업폴더명이 터미널에 잘 뜨죠? 그럼 이제 리액트 프로젝트 설치 명령어를 입력해봅시다.

2 . 터미널에 입력합니다.

npx create-react-app shop

그럼 이제 shop이라는 이름의 프로젝트 (하위폴더)가 생성됩니다. 1분 기다리면 됩니다.

3 . shop이라는 폴더를 VScode 에디터로 오픈한 뒤에 코딩을 시작합니다.

설치 다 됐으면 폴더오픈하고 코딩해야죠? 유튜브보던건 이제 끄도록 합시다

4 . App.js에 필요없는 HTML들은 지우고 div 하나만 남겨두고 시작합시다.

5 . 그리고 터미널에서 yarn start를 눌러서 미리보기를 띄워봅시다.

CSS 쌩코딩하기 귀찮으니 Bootstrap 라이브러리를 설치해서 이용하기

설치하시면 버튼디자인 메뉴디자인 직접 하실 필요없이

Bootstrap 홈페이지에 있는 예제코드만 복붙하시면 메뉴, 버튼, 3분할 레이아웃 등 원하는 UI들을 쉽게 생성가능합니다.

Bootstrap은 원조라이브러리고

이걸 리액트에 맞게 변형한 React Bootstrap을 설치합시다.

react bootstrap이라고 구글 검색하면 맨 처음에 나오는 사이트로 들어갑시다.

그리고 get started 혹은 introduction 메뉴로 들어갑니다.

20220427_111312

▲ 사이트에서 시키는대로 인스톨해주도록 합시다.

터미널켜고

npm install react-bootstrap bootstrap

입력해주시면 됩니다. 명령어 맨날 바뀌니까 사이트 들어가보셔야합니다.

yarn 으로 빠르게 설치하려면

yarn add react-bootstrap bootstrap

입력해주시면 됩니다.

때에 따라 특정 스타일을 사용할 때 Bootstrap CSS 파일을 요구하는 경우가 있습니다.

그럼 그냥 사이트에 있는 CSS 파일을 index.html 파일의 < head > 태그 안에 복붙해주시면 됩니다.

20220427_111737

▲ 사이트 내에 이걸 찾아 복붙하시면 됩니다. 아까랑 같은 페이지의 CSS라는 항목에 있을듯여

Bootstrap 설치가 잘 되었는지 테스트해보려면

1 . getbootstrap.com 들어가신 후에 Documentation 탭으로 들어갑니다.

2 . 원하는 레이아웃을 검색합니다. button을 한번 검색해봅시다.

3 . 그 중에 원하는 버튼 HTML을 내 App.js에 복붙합니다. 전 강의에서 파란버튼을 복붙했습니다.

4 . 미리보기에서 버튼이 예쁘게 파란색으로 뜨면 설치 성공입니다.

이제 버튼 말고도 필요한 레이아웃을 바로바로 복붙하면 개발이 편해지겠죠?


평화로운 쇼핑몰 레이아웃 디자인시간

★ 리액트 17버전 이상에선 public 폴더안에 있는 이미지를 CSS파일에서 /image.jpg 이렇게 첨부할 수 없습니다.

이럴 경우 이미지를 src 폴더로 옮겨서 ./image.jpg 이렇게 첨부하십시오.

강의에서 사용하는 이미지 URL 모음

https://codingapple1.github.io/shop/shoes1.jpg

https://codingapple1.github.io/shop/shoes2.jpg

https://codingapple1.github.io/shop/shoes3.jpg

오늘은 React Bootstrap을 이용한 레이아웃 디자인 시간입니다.

Bootstrap은 CSS 라이브러리인데, 설치하면 미리 이쁘게 디자인 된 버튼, 메뉴 들을 갖다쓸 수 있습니다.

그니까 이제 복사 붙여넣기만 하면 이쁜 웹 UI 개발 끝이라는 겁니다.

오늘은 이걸 이용해서 쇼핑몰 메인페이지에 평화롭게 디자인만 좀 넣고 마무리해보도록 합시다.

(그리고 React Bootstrap에서 코드 복붙하실 땐 항상 상단에 컴포넌트 명 import 해오는거 잊지맙시다.)

상단메뉴 (Navbar) 만들기

react bootstrap 공식사이트로 들어가신 뒤

Component 메뉴로 들어가신 다음 navbar라고 검색하면 간지나는 navbar 예제들이 많이 보입니다.


20220427_115808

▲ 마음에 드는 navbar의 HTML 예제 코드를 여러분 원하는 곳에 붙여넣기만 해주시면 navbar 개발 끝입니다.

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
    </div>
  )
}

▲ 그래서 전 App.js 이곳에 붙여넣어봤습니다.

하지만 여기까지 하면 에러가 나고 작동하지않습니다.

왜냐면 붙여넣으실 때는 붙여넣은 컴포넌트를 상단에 import를 먼저 해오셔야합니다.

import { Navbar } from 'react-bootstrap';

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
    </div>
  )
}

▲ 이렇게 해야된다고합니다.

아무튼 붙여넣었을 때 Navbar> 말고도 < Nav >, < NavDropdown> 이런 여러가지 컴포넌트들도 같이 딸려왔으니

이런 것도 함께 import 해오시길 바랍니다.


import { Navbar,Nav,NavDropdown,Form,Button,FormControl } from 'react-bootstrap';

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
    </div>
  )
}

▲ 이렇게하면 이제 navbar UI 붙여넣기 성공입니다.

이제 navbar 붙여넣은 HTML중에 필요없어보이는건 제거하고 수정할건 수정하시면 됩니다.

className=”“으로 커스텀 스타일도 넣으실 수 있고요.

그건 알아서 할 수 있으니 스킵하도록 합시다.

스타일링을 알아서 못한다면 CSS 공부가 우선입니다.

대문 Jumbotron 만들기

큰 그림위에 글씨 + 버튼있는 그런 UI도 쌩코딩할 필요는 없습니다.

Jumbotron이라고 미리 마련이 되어있어서 그거 붙여넣으시면 됩니다.

검색 ㄱㄱ 후 붙여넣으십시오.

(bootstrap 5, react-bootstrap 2버전 이상에선 Jumbotron이라는 항목이 없습니다 그냥 큰 div박스랑 배경 알아서 css 짜서 넣으셈)

import { Navbar,Nav,NavDropdown,Form,Button,FormControl, Jumbotron } from 'react-bootstrap';

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)
    </div>
  )
}

▲ Navbar 밑에 붙여넣기해야 이쁘겠죠? 그리고 < Jumbotron > 이렇게 대문자들이 보이면

그건 react-bootstrap 컴포넌트들이기 때문에 전부 위에서 import 잘 해오시면 됩니다.

그리고 글 수정하고 사진넣고 해보시면 됩니다.

배경사진을 넣으시려면

<Jumbotron className="background">

이런 식으로 클래스 하나 넣어주시고

CSS 파일로 가서 .background{} 안에 스타일 작성하면 되겠죠?

(App.css 파일)

.background {
  background-image : url(./background.jpg);
  background-position : center;
}

이런 식으로 CSS를 작성하면 배경이 생깁니다.

근데 이미지 경로를 작성하실 때 src폴더 안에 있는 이미지들을 사용할 땐 언제나 ./ 이게 현재경로입니다.

(강의에서 쓴 이미지파일은 하단 첨부파일 참조)

(참고)

src 폴더에 있는 파일들은 리액트 앱을 발행했을 때 저절로 압축이 되고 파일명이 변경되는데

public 폴더에 있는 파일은 리액트 앱을 발행했을 때 사이트 루트경로에 그대로 남아있습니다.

그래서 / 이렇게 경로를 입력해도 잘 먹습니다.

그래서 public 폴더에 있는 이미지들은

<img src="/image.jpg" />

이렇게 쓰셔도 첨부가능합니다.

★ 리액트 17버전 이상에선 public 폴더안에 있는 이미지를 CSS파일에서는 /image.jpg 이렇게 첨부할 수 없습니다. CSS파일에 작성할 이미지들은 src폴더 쓰셈

상품 레이아웃 만들기

상품이미지를 가로로 3개 진열하고 싶습니다. 모바일사이즈에선 세로 1열로요.

이것도 float flex 이런거 생각할 필요없이 Bootstrap 문법을 사용하면 됩니다.

import { Navbar,Nav,NavDropdown,Form,Button,FormControl } from 'react-bootstrap';

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          <div className="col-md-4">안녕</div>
          <div className="col-md-4">안녕</div>
          <div className="col-md-4">안녕</div>
        </div>
      </div>
    </div>
  )
}

▲ 그대로 따라치시면 사이트가 정확히 가로로 3분할이 됩니다.

이제 col-md-4라는 div박스 안에다가 이미지넣고 글넣고 쇼핑몰의 상품처럼 디자인하시면 됩니다.

(참고) Bootstrap grid 문법인데, flexbox라는 CSS 속성을 사용합니다. 그래서 IE+11 환경에서만 동작할 수 있음

(참고2) 원래 이것도 react-bootstrap 사이트에서 layout이라고 찾아서 복붙하셔야합니다.

import { Navbar,Nav,NavDropdown,Form,Button,FormControl } from 'react-bootstrap';

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          <div className="col-md-4">
            <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
            <h4>상품명</h4>
            <p>상품정보</p>
          </div>
          <div className="col-md-4">
            <img src="https://codingapple1.github.io/shop/shoes2.jpg" width="100%" />
            <h4>상품명</h4>
            <p>상품정보</p>
          </div>
          <div className="col-md-4">
            <img src="https://codingapple1.github.io/shop/shoes3.jpg" width="100%" />
            <h4>상품명</h4>
            <p>상품정보</p>
          </div>
        </div>
      </div>
    </div>
  )
}

▲ 상품처럼 이미지, 글을 넣어봤습니다.

이미지 경로는 미리 호스팅해둔 이미지니까 그냥 똑같이 쓰셔도 무방합니다.

10분만에 메인페이지가 완성되었습니다 짝짝짝


코드가 넘나 길어진다면 import / export 사용해보기

오늘의 숙제

1 . 오늘 만들었던 상품리스트를 이번엔 <컴포넌트>로 만들어서 첨부해보세요

2 . <컴포넌트>에 실제 상품명이 뜨도록 {데이터바인딩} 완료해보십시오. <컴포넌트>3개가 필요하겠군요.

3 . 컴포넌트 3개를 map 반복문을 돌려봅시다

실제 쇼핑몰 데이터를 여기다가 박아넣고 싶은데 쇼핑몰 상품 데이터가 좀 길어서 문제입니다.

변수나 함수나 자료형을 다른 파일로 저장해둔 뒤에 불러오는 방법을 알아봅시다.

import / export 문법인데 한번 배워두시면 이제 모든걸 다 외부파일로 모듈화하실 수 있습니다.

강의에서 사용하는 상품 데이터 3개

[
  {
    id : 0,
    title : "White and Black",
    content : "Born in France",
    price : 120000
  },

  {
    id : 1,
    title : "Red Knit",
    content : "Born in Seoul",
    price : 110000
  },

  {
    id : 2,
    title : "Grey Yordan",
    content : "Born in the States",
    price : 130000
  }
]

하나하나의 상품 정보를 object {} 자료형에 담았으며 이거 3개를 하나의 array에다가 담았을 뿐입니다.

실제 서버에서 보내준 쇼핑데이터라고 생각하고 이걸 HTML에 박아넣어보도록 합시다.

하지만 너무 길어서 이걸 data.js 같은 파일을 만들어서 안에 담은 뒤에 App.js에서 data.js에 있던걸 불러와보도록 합시다.

그러려면 일단 import / export 문법부터 알아야합니다.


data.js App.js 이렇게 변수, 혹은 데이터를 보내려면

1 . 일단 data.js에서 원하는 데이터를 export 하시고

2 . App.js에서는 data.js를 import 하시면 됩니다.

export default 문법

(data.js 파일)

var 중요데이터 = 'Kim';
export default 중요데이터;

src 폴더에 App.js와 나란히 data.js를 만들었습니다.

이 파일에서 중요한 변수를 export하고 싶을 땐 export default라는 문법을 쓰시고 우측에 배출을 원하는 변수를 담아줄 수 있습니다.

  • 변수명, 함수명, 자료형 전부 배출가능합니다.

  • 파일마다 export default 라는 키워드는 하나만 사용가능합니다.

끝입니다.

import 문법

(App.js 파일)

import 중요데이터 from './data.js';

App.js에서 data.js 에서 배출한 변수를 쓰고싶다면

import 변수명 from ‘경로’

라고 작성해서 다른 파일에서 배출한 변수를 갖다쓸 수 있습니다.

위의 예제코드에서 중요데이터라는 변수를 출력해보면 ‘Kim’이 나오죠? 성공!

  • 변수명이라는 부분은 자유롭게 작명하실 수 있습니다.

  • 경로쓰실 때 ./ 라는 것은 현재경로입니다.

export {} 문법

여러개의 변수들을 내보내고싶으면 export default 말고 이런 문법을 씁니다.

(data.js 파일)

var name1 = 'Kim';
var name2 = 'Park';
export { name1, name2 }

그럼 원하는 변수명, 함수명을 내보낼 수 있습니다.

아까와는 다르게 꼭 변수나 함수명이 있어야합니다.

import {} 문법

export {} 이걸로 내보낸 변수들을 갖다쓰고 싶으면 import {} 문법을 씁니다.

(App.js 파일)

import { name1, name2 } from './data.js';

이렇게 export 했던 변수명을 원하는 것만 골라서 써주시면 됩니다.

  • 이 경우는 작명이 불가능하고 export 했던 변수명 그대로 쓰셔야합니다.

그러니까 export default + import

아니면 export {} + import

둘 중 마음에드는걸로 쓰시면 되겠습니다.

쇼핑몰 데이터를 state로 저장하고 싶은데 너무 길다 다른파일로 빼자

위에 적혀있던 긴 array 데이터를 App.js의 state로 만들고 싶습니다.

근데 너무 길어서 data.js라는 파일에 보관한 뒤에 App.js로 가져오고 싶습니다. 어떻게 하면 될까요?

(state 이름은 shoes로 합시다)

(필요한건 다 배웠으니 혼자 해보시면 됩니다)


해보신 후 저랑 비슷하게 했는지 펼쳐봅니다

일단 data.js 에 데이터를 다 저장하고 export default 로 내보내줍니다.

(data.js 파일)

export default [
  {
    id : 0,
    title : "White and Black",
    content : "Born in France",
    price : 120000
  },

  {
    id : 1,
    title : "Red Knit",
    content : "Born in Seoul",
    price : 110000
  },

  {
    id : 2,
    title : "Grey Yordan",
    content : "Born in the States",
    price : 130000
  }
]

변수에 저장했다가 export default 변수명 하셔도 되는데

변수만들기 귀찮으면 그냥 자료를 그대로 export default 뒤에 집어넣으셔도 됩니다.

그럼 이제 App.js에서 import 어쩌구를 작성해서 이 데이터를 가져와봅시다.

(App.js 파일)

import React, {useState} from 'react';
import Data from './data.js';

function App(){

  let [shoes, shoes변경] = useState(Data);

  return (
    <div> HTML 많은 곳 </div>
  )
}

이렇게 import 해왔습니다. export default로 내보낸 데이터는 자유롭게 작명이 가능하니

데이터라고 이름짓고 그걸로 state를 만들어버렸지뭐에얌


상품데이터를 HTML에 데이터바인딩하기

여러분이 방금 import 해온건 상품 3개의 데이터입니다.

각각 상품의 제목, 설명, 가격 이런 것들이 들어가있습니다. 이걸 알맞은 자리에 데이터바인딩 해보도록 합시다.

(App.js 파일)

function App(){
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          <div className="col-md-4">
            <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
            <h4>{ shoes[0].title }</h4>
            <p>{ shoes[0].content } & { shoes[0].price }</p>
          </div>
          <div className="col-md-4">
            <img src="https://codingapple1.github.io/shop/shoes2.jpg" width="100%" />
            <h4>상품명</h4>
            <p>상품정보</p>
          </div>
          <div className="col-md-4">
            <img src="https://codingapple1.github.io/shop/shoes3.jpg" width="100%" />
            <h4>상품명</h4>
            <p>상품정보</p>
          </div>
        </div>
      </div>
    </div>
  )
}

▲ 첫째 상품의 제목, 설명부분에

shoes라는 state에 보관하던 실제 데이터를 박아넣어봤습니다.

이런 식으로 쭉~ 하면 실제 데이터를 사용자에게 보여줄 수 있겠군요.

그리고 집가서 상단에 있는 오늘의 숙제 마무리해보시길 바랍니다.


숙제 해설 : 상품목록 Component화 + 반복문

저번시간 내드린 숙제를 해결해보도록 합시다.

그냥 상품리스트들을 컴포넌트로 만들고, 데이터바인딩하고

필요하면 반복문을 돌려보는 숙제였습니다. 다 배웠던 것들이니 스무스하게 시작해봅시다.

일단 터미널창에 뜨는 warning부터 해결합시다.

노란색 warning은 그냥 잔소리입니다.

“이미지에 alt=”” 넣어주세요”

“변수 선언만하고 안쓰셨어요”

이런 잔소리가 귀찮다면

페이지 맨 위에 /eslint-disable/ 이라는 코드를 추가합니다.

이제 잔소리 안뜹니다.

숙제1-1. 상품레이아웃 컴포넌트화하기

(App.js 파일)

function App(){

  let [shoes, shoes변경] = useState(Data);
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          <Card />
          <Card />
          <Card />
        </div>
      </div>
    </div>
  )
}

function Card(){
  return (
    <div className="col-md-4">
      <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
      <h4>{ shoes[0].title }</h4>
      <p>{ shoes[0].content } & { shoes[0].price }</p>
    </div>
  )
}

▲ col-md-4 라는 div박스들을 컴포넌트로 만들어서 첨부했습니다.

컴포넌트 만드는건 많이 해본 것이니 자세한 설명은 필요없겠죠?

근데 에러가납니다. function Card(){} 여기 안에 shoes라는 변수가 없다고 에러가 뜨네요.

당연합니다. 현재 shoes 라는 state 변수는 App 컴포넌트에 있지 Card 컴포넌트에는 없으니까요.

App이라는 컴포넌트에 있는 state를 Card에서 쓰고싶으면 props로 전송해주어야합니다.

shoes라는 state를 props로 전송하려면

props 전송은 2단계를 밟아주면 된다고 했습니다.

function App(){

  let [shoes, shoes변경] = useState(Data);
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          <Card shoes={shoes} />
          <Card shoes={shoes} />
          <Card shoes={shoes} />
        </div>
      </div>
    </div>
  )
}

function Card(props){
  return (
    <div className="col-md-4">
      <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
      <h4>{ props.shoes[0].title }</h4>
      <p>{ props.shoes[0].content } & { props.shoes[0].price }</p>
    </div>
  )
}

▲ 이렇게 쓰면 되겠군요

1 . Card>를 첨부할 때 shoes={shoes}로 전송하는 문법을 썼고

2 . Card 컴포넌트 안에 shoes를 갖다쓸 땐 props.shoes 라고 사용했습니다.

완성입니다. 그럼 일단 카드들이 정상적으로 출력이 됩니다.

Q. 근데 왜 다들 똑같은 상품명이 뜨는거죠?

A. Card 컴포넌트에서 데이터바인딩할 때 똑같은 데이터만 데이터바인딩 해놨으니까요.


이 문제를 해결해봅시다. 각각 < Card >마다 다른데이터를 데이터바인딩시키면 되겠죠뭐

숙제 1-2. 각각의 Card 컴포넌트마다 다른 데이터 전송해주기

그러면 각각 카드마다 다른 상품명을 보여줄 수 있으니까요.

그럼 props 전송시 이런 식으로 전송해주면 되지않을까요?

function App(){

  let [shoes, shoes변경] = useState(Data);
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          <Card shoes={shoes[0]} />
          <Card shoes={shoes[1]} />
          <Card shoes={shoes[2]} />
        </div>
      </div>
    </div>
  )
}

▲ 각각의 Card마다 shoes[0], shoes[1] … 이런 식으로 전송해주면 되겠구만요.

그니까 shoes라는 [ {}, {}, {} ] 이렇게 생긴 데이터를 다 전송하는게 아니라

shoes 안에 있던 하나의 {} 오브젝트만 각각 전송하자는 겁니다.

각각 {} 오브젝트에는 각각 다른 상품명이 들어가있고요.

그럼 이제 Card라는 컴포넌트에 작성해놨던 데이터바인딩 방법도 바꾸면 됩니다.

function Card(props){
  return (
    <div className="col-md-4">
      <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
      <h4>{ props.shoes.title }</h4>
      <p>{ props.shoes.content } & { props.shoes.price }</p>
    </div>
  )
}

▲ 아까 위에서 shoes[0] 이라는 데이터를 shoes라는 이름으로 props로 전송했기 때문에

props.shoes[0].title 이게 아니라

props.shoes.title 이렇게 써주시면 제목 등이 정상적으로 출력됩니다.

별거아니지만 숙제1의 교훈은:

  • 같은 컴포넌트라고 항상 같은 내용만 보여줄 수 있는게 아닙니다.

  • props 등을 이용해 각각 다른 내용을 전송해주면 됩니다.

  • 그럼 같은 컴포넌트라고 해도 각각 다른 내용이 출력되는 컴포넌트를 만드실 수 있습니다.

숙제2. Card 컴포넌트 반복문 돌리기

굳이 안해도될 것 같지만

진정한 개발자라면 반복되는 코드를 봤을 때 반복문 돌리고 싶어 안달 나야합니다.

function App(){

  let [shoes, shoes변경] = useState(Data);
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          {
            shoes.map((a,i)=>{
             return <Card shoes={shoes[i]} />
            });
          }
        </div>
      </div>
    </div>
  )
}

▲ 그래서 Card> 3개를 map 반복문으로 돌려보았습니다.

(shoes라는 state 갯수만큼 돌려야하니까 shoes에 map을 붙였습니다)

map 반복문이 기억이 안나면 이전 강의를 참고하도록 합니다.

map 반복문 안엔 2개의 파라미터가 들어갈 수 있는데 (a, i 이렇게 써놓은거요)

왼쪽거는 shoes라는 array에 있던 하나하나의 데이터를 의미하고,

i는 반복문 돌면서 1씩 증가하는 정수입니다. 0,1,2 … 이런 식으로 변하는 변수겠네요.


상품 이미지들 데이터바인딩하기

현재 모든 상품이미지들은 Card 컴포넌트 안에 < img src=”~~~/shoes1.jpg” > 이렇게 하드코딩되어있습니다.

근데 각각 컴포넌트마다 shoes1.jpg / shoes2.jpg / shoes3.jpg 라는 데이터로 데이터바인딩하고 싶은데 어떻게 하면 될까요?

힌트는.. 위에서 배웠던 교훈을 생각해보시면 됩니다.

컴포넌트마다 각각 다른 내용을 보여주고 싶으면 props로 다른 정보를 전송해주면 된다고했죠?

그럼

<img/> 경로에 "shoes1.jpg"

라고 하드코딩되어있던 걸 이렇게 바꾸면 되겠군요.

<img src={ ~~~ shoes반복문돌때마다1,2,3으로변하는변수.jpg} />

이렇게요.

Q. 그런 변수가 있나요?

Card> 근처에 있습니다. Card> 반복문 돌릴 때 map 안에 i라는 변수가 있다고 했습니다. 0,1,2라고 변하는 변수였죠?

비슷하니까 이걸 활용하면 되겠군요.

<img src={ 'https://codingapple1.github.io/shop/shoes' + i + '.jpg' } width="100%"/>

▲ 그리고 글자 중간에 변수를 넣고 싶으면 이런 식으로 쓰면 되고요.

근데 i 라는 변수는 App이라는 부모 컴포넌트가 가지고 있는 변수기 때문에

하위 컴포넌트에서 사용하고싶으면 이것도 똑같이 props로 전송해주어야 갖다쓸 수 있습니다.

전송해봅시다.

function App(){

  let [shoes, shoes변경] = useState(Data);
  return (
    <div className="App">
      <Navbar></Navbar> (그 사이트에 있던 navbar 예제 어쩌구)
      <Jumbotron></Jumbotron> (그 사이트에 있던 jumbotron 예제 어쩌구)

      <div className="container">
        <div className="row">
          {
            shoes.map((a,i)=>{
             return <Card shoes={shoes[i]} i={i} />
            });
          }
        </div>
      </div>
    </div>
  )
}

function Card(){
  return (
    <div className="col-md-4">
      <img src={ 'https://codingapple1.github.io/shop/shoes' + (props.i+1) + '.jpg' } width="100%"/>
      <h4>{ props.shoes.title }</h4>
      <p>{ props.shoes.content } & { props.shoes.price }</p>
    </div>
  )
}

▲ props 전송하시려면

1 . i={i} 이렇게 전송해주시고 2. props.i 이렇게 갖다쓴다고 배워봤습니다.

근데 i는 0,1,2가 된댔는데 우리가 필요한 숫자는 1,2,3 이잖아요

그래서 (props.i + 1) 이런 변수를 집어넣은 것입니다.


현강에서 많이 들어본 질문

Q. 왜 이런 짓거리를 나에게 시키는 것이죠?

A. 요즘 프론트엔드 개발자의 주된 역할은

1 . 서버에서 데이터받아온 후 2. 그걸로 HTML을 예쁘게 만들어 보여주는 역할입니다.

그걸 리액트로 연습하고 있는 것이고요.

하지만 아직 서버에서 받아온 데이터가 없으니 data.js에 있는 데이터가지고 연습하고 있는 것일 뿐입니다.

Q. Card> 이거 굳이 반복문 안돌려도 되지않을까요?

A. shoes라는 데이터가 항상 3개라면 굳이 반복문 안써도 됩니다.

근데 shoes라는 데이터가 3개가 아니라 매번 달라지고 그렇다면 반복문을 돌리는게 매우 좋습니다.

반복문은 ‘데이터 갯수만큼’ HTML을 보여줄 수 있으니까요.

지금 강의에서도 shoes라는 데이터 갯수만큼 반복문을 돌리고 있습니다.

Q. 왜 터미널/콘솔창에 워닝이 뜨죠?

A. 워닝은 항상 메세지를 읽어보면 바로 해결책이 나오는데

map 쓰셨다면 key={} 이것도 꼭 사용해주셔야한다고 배웠었죠? 그래서 그럴겁니다.

{} 여기 안에는 반복문이 돌면서 0,1,2가 되는 변수를 적어주시면 된다고 했고요.

그러니까 < Card key={i} > 이런 코드를 추가해주시면 되겠네요.


React Router 1 : 셋팅과 기본 라우팅

(수정1) 라이브러리 설치시 npm install react-router-dom@5.2.0 이걸로 설치합시다.

(수정2) 설치 후 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(
  <React.StrictMode>
    <BrowserRouter>
    <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

여러가지 페이지를 만들고 싶다면 라우터를 이용합니다.

react-router-dom이라는 공식 라이브러리를 설치해서 이용하시면 됩니다.

오늘은 메인페이지와 상세페이지 이렇게 두개의 페이지만 만들어볼겁니다.

일단 설치와 셋팅부터 하도록 합시다.

설치/셋팅부터 합시다

터미널을 여시고

npm install react-router-dom@5.2.0
yarn add react-router-dom@5.2.0

둘 중 하나 입력해서 설치해주시면 됩니다. yarn 으로 시작하는건 당연히 저번에 yarn을 설치해야 사용할 수 있습니다.

그리고 index.js 파일에 방문합니다.

이 파일은 App.js에 있는 < App > 컴포넌트를 index.html에 꽂아주세요~ 뭐 이런 작업을 시키는 파일이라고 보시면 됩니다.

근데 여기다가 다음 코드들을 추가합니다.

import 어쩌구 많은곳;

import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  </React.StrictMode>
  document.getElementById('root')
);

▲ 당연히 다 지우고 복붙하는게 아니라 기존코드와의 차이점만 잘 복붙하시길 바랍니다.

BrowserRouter를 import 해오고 BrowserRouter>라는 태그를 추가했습니다.

그럼 이제 라우팅할 준비가 끝났습니다.

근데 셋팅할 때 BrowserRouter> 말고 HashRouter> 라는 태그도 이용할 수 있습니다.

사용방법은 위의 복붙했던 코드를 Browser라는 단어대신 전부 Hash라고 바꿔주시면 되는데

이게 뭐냐면

  • HashRouter를 복붙하시면 사이트 방문시 URL 맨 뒤에 /#/이 붙은채로 시작합니다.

  • BrowserRouter를 복붙하시면 사이트 방문시 # 그런거 없이 깔끔해집니다.

이게 차이점입니다.


Q. URL이 #기호로 드러워지는 HashRouter를 왜쓰죠?

A. 원래는 브라우저 주소창에 뭔가 페이지를 입력하면 서버에게 특정 페이지좀 보여달라는 요청이 됩니다.

근데 우리는 요청할 서버가 없고 그냥 리액트가 라우팅을 담당하고 있습니다.

그래서 잘못하면 있지도 않은 페이지를 서버에 요청을 해서 404 Page Not Found 이런 에러도 뜨고 그럴 수 있습니다.

실수로 서버에게 요청하지 않게 하려면 안전하게 #을 붙여주어야합니다.

왜냐면 브라우저 주소창에서 # 뒤에 붙은 것들은 절대 서버로 요청되지 않으니까요.

Q. 그럼 BrowserRouter는 안좋은거네요?

A. 그건 아니고 BrowserRouter를 쓰시려면 그냥 서버에서 셋팅만 잘해주시면 됩니다.

“이런 경로로 들어오는 요청은 404 보내지 말구요~ 전부 리액트가 라우팅하게 해주세요~”

“이 경로로 들어오는 요청은 그냥 리액트 메인페이지로 보내주세요~”

이런 식으로 API를 짜놓으면 됩니다. 물론 서버지식이 없다면 이건 몰라도됩니다.

라우팅을 해봅시다 (페이지나누기)

오늘의 목표는 이겁니다.

(1) / 여기로 접속하면 메인페이지를 보여주고 싶습니다.

(2) /detail로 접속하면 상세페이지를 보여주고 싶습니다.

근데 이런거 하려면 일단 하는 법부터 알아봅시다.

라우팅하려면 1. 여러가지 태그들 import가 필요합니다.

(App.js)

import 많은 곳;
import { Link, Route, Switch } from 'react-router-dom';

function App(){
  return (
    <div>
      HTML 잔뜩있는 곳

    </div>
  )
}

▲ 상단에 Route 라는 태그를 import 해오십시오. Link, Switch는 다음시간에 쓸거라 미리 해놨습니다.

라우팅하려면 2. 원하는 곳에

<Route><Route/>

태그를 작성합니다.

라우팅하려면 3. Route>안에 path와 path 방문시 보여줄 HTML 을 적으시면 됩니다.

(App.js)

import 많은 곳;
import { Link, Route, Switch } from 'react-router-dom';

function App(){
  return (
    <div>
      HTML 잔뜩있는 곳
      <Route path="/">
        <div>메인페이지인데요</div>
      </Route>
      <Route path="/detail">
        <div>상세페이지인데요</div>
      </Route>
    </div>
  )
}

▲ 그래서 이렇게 적었습니다.

이제 브라우저 주소창에 입력해보십시오.

/ 으로 접속하면 “메인페이지인데요~”

/detail로 접속하면 “상세페이지인데요~” 가 보입니다.

라우팅쉽습니다. 이게 끝입니다.

이제 “메인페이지인데요~” 라는 div박스말고 메인페이지에 해당하는 HTML을 전부 담고 하시면 멋진 사이트가 완성되겠군요.

참고로 이렇게도 작성가능합니다.

<Route path="/어쩌구" component={Card} ></Route>

그러면 /어쩌구 라는 경로로 접속했을 때 Card 라는 컴포넌트를 이 자리에 보여줍니다.

그러니 컴포넌트를 가지고 있으면 조금 더 간단하겠죠?

<Route path="/어쩌구"> <Card/> </Route>

물론 이렇게 쓰는것도 똑같은 기능을 합니다. (더 쉬움)

React-Router 특징 :

  • 각각 페이지마다 다른 HTML 파일을 보여주는게 아닙니다.

HTML 내부의 내용을 갈아치워서 다른 페이지처럼 흉내내는 것일 뿐입니다.

나는 /detail로 접속했는데 왜 상세페이지, 메인페이지 둘다 보여주죠?

왜냐면 /detail이라고 적으면 /라는 경로도 포함되어있으니까요.

그래서 / 경로로 접속했다고 생각하고 메인페이지

/detail 경로도 접속했다고 생각하고 상세페이지 둘다 보여주는 것입니다.

(그냥 리액트 라우터는 원래 이렇게 동작합니다)

그런게 싫으시면 / 경로에 exact라는 속성을 부여해주시면 됩니다.

<Route exact path="/">
  <div>메인페이지에요</div>
</Route>

그럼 / 경로와 정확히 일치할 때만 메인페이지를 보여줍니다.

메인페이지/상세페이지 Route 에 내용 채우기

지금은 / 경로 Route에선 “메인페이지에요~” 라는 임시 글자만 보여주고 있는데

여기에 진짜로 메인페이지에 해당하는 HTML들을 옮겨보도록 합시다.

< Navbar >은 모든페이지에 보여야하니 그대로 냅두고,

< Jumbotron > 이랑 카드3개 레이아웃을 메인페이지 Route에 잘라내기 붙여넣기 하시면 되겠군요.

(App.js)

import 많은 곳;
import { Link, Route, Switch } from 'react-router-dom';

function App(){
  return (
    <div>
      <Navbar></Navbar> (상단메뉴 레이아웃)

      <Route path="/">
        <Jumbotron></Jumbotron> (Jumbotron 대문 레이아웃)
        <>상품3개 카드 레이아웃</>
      </Route>

      <Route path="/detail">
        <div>상세페이지인데요</div>
      </Route>
    </div>
  )
}

▲ 그래서 전 이렇게 구성해봤습니다. 여러분 맘대로 하시면 됩니다.

그리고 상세페이지에 들어갈 레이아웃은 제가 마련해봤습니다.

▼ 이것을 상세페이지로 사용하십시오. /detail로 접속하면 이게 보여야합니다.

<div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
        </div>
        <div className="col-md-6 mt-4">
          <h4 className="pt-5">상품명</h4>
          <p>상품설명</p>
          <p>120000원</p>
          <button className="btn btn-danger">주문하기</button>
        </div>
      </div>
</div>

라우터의 Link 태그, Switch 태그, history의 역할에 대해 알아봅시다.

그리고 그 전에 Detail 페이지에 있던 내용을 Component로 만들어봅시다.

근데 App.js 내용이 너무 길어질까봐 다른 파일에 Detail 컴포넌트를 저장해놓고 App.js 까지 import 해오도록 합시다.

일단 상세페이지 내용을 담을 Detail> 컴포넌트를 만들자

저번시간 /detail 방문시 보여지는 HTML 내용을 길게 복붙한적이 있을겁니다.

그게 너무 길어서 컴포넌트화를 해보도록 합시다. 컴포넌트 이름은 Detail/> 이라고 합시다.

근데 컴포넌트를 App.js에 만들지 말고 다른 파일에 저장해둔 뒤 App.js까지 import 해오는 방법을 사용합시다.

(Detail.js 파일)

import React from 'react';

function Detail(){
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
        </div>
        <div className="col-md-6 mt-4">
          <h4 className="pt-5">상품명</h4>
          <p>상품설명</p>
          <p>120000원</p>
          <button className="btn btn-danger">주문하기</button>
        </div>
      </div>
  </div>
  )
};

export default Detail

▲ 그래서 1 . src 폴더 내에 Detail.js 파일을 만들고

2 . 컴포넌트 생성하는 코드를 담았습니다. 근데 이거 쓰시려면 맨 위에 항상 import React를 해오셔야합니다.

3 . 맨 마지막줄에 Detail 이라는 함수를 export 해주었습니다.

(App.js 파일)

import 많은 곳;
import Detail from './Detail.js';
function App(){
  return (
    <div>
      <Navbar></Navbar> (상단메뉴 레이아웃)

      <Route path="/">
        <Jumbotron></Jumbotron> (Jumbotron 대문 레이아웃)
        <>상품3개 카드 레이아웃</>
      </Route>

      <Route path="/detail">
        <Detail/>
      </Route>
    </div>
  )
}

▲ 그럼 이제 다른 파일에서 자유롭게 Detail 컴포넌트를 가져다쓸 수 있습니다.

위는 App.js 에서 이 파일을 가져와서 컴포넌트를 자유롭게 활용해본 예제입니다.

이제 배웠으니 컴포넌트가 길어지면 얼마든지 다른 파일로 빼서 저장해두십시오.

(import/export 문법이랑 import React 이것만 잘 기억하시면 됩니다.)

Link 태그로 페이지 이동버튼 만들기

메인페이지, 상세페이지 이동버튼을 만들어봅시다.

페이지 상단 메뉴에 만들면 좋을 것 같아서 상단메뉴(Navbar)를 찾아갑니다.

(App.js 파일)

function App(){
 return (
   <div>
     <Navbar>
        <Nav.Link> <Link>Home</Link> </Nav.Link>
        <Nav.Link> <Link>Detail</Link> </Nav.Link>
     </Navbar>
     <나머지HTML/>
   </div>
 )
}

▲ Navbar 컴포넌트 안에 Nav.Link 라는 컴포넌트에 여러가지 버튼들이 있습니다.

페이지 이동버튼으로 바꾸길 원하는 글자들에 Link> 태그를 감싸보시길 바랍니다.

(Link 태그는 이전 시간에 ‘react-router-dom’과 함께 상단에 import 해온 컴포넌트입니다)

(App.js 파일)

function App(){
 return (
   <div>
     <Navbar>
        <Nav.Link> <Link to="/">Home</Link> </Nav.Link>
        <Nav.Link> <Link to="/detail">Detail</Link> </Nav.Link>
     </Navbar>
     <나머지HTML/>
   </div>
 )
}

▲ 그 다음에 to 라는 속성을 이용해서 경로를 적어주시면 페이지 이동버튼이 완성됩니다.

  • Link 태그를 사용하고

  • to 속성을 이용해 경로만 지정해주면 됩니다.

어찌보면 a> 태그 만드는 것과 매우 유사합니다.

그럼 Detail이라는 글자를 누르면 /detail 경로

Home이라는 글자를 누르면 / 경로로 이동합니다.

다른 방법으로 페이지 이동기능 만들기

Link> 꼭 이런 버튼 말고도

코드 실행 중간중간에 페이지를 이동시키고 싶은 경우도 많습니다.

그럴 경우엔 페이지 이동 함수를 사용하시면 됩니다.

예시를 위해 Detail 페이지(컴포넌트)에 뒤로가기 버튼을 하나 만들어봅시다.

그런거 만들고 싶으시면 useHistory() 라는 함수를 하나 import 해오시면 됩니다.

(Detail.js 파일)

import React from 'react';
import { useHistory } from 'react-router-dom';

function Detail(){

 let history = useHistory();
 return (
   <div className="container">
     <div className="row">
       <div className="col-md-6">
         <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
       </div>
       <div className="col-md-6 mt-4">
         <h4 className="pt-5">상품명</h4>
         <p>상품설명</p>
         <p>120000원</p>
         <button className="btn btn-danger">주문하기</button>
         <button className="btn btn-danger">뒤로가기</button>
       </div>
     </div>
 </div>
 )
};

export default Detail

▲ 1. import 를 저렇게 해오시고 2. let history 라는 변수에 그 함수를 저장하시면 됩니다.

useHistory()는 여러분의 코딩생활을 편하게 해주는 일종의 Hook입니다. (useState 이런거랑 비슷한겁니다)

그럼 이제 history 라는 변수엔 큰 object {} 자료가 하나 저장이 되어있습니다.

그 object 안에는 페이지 이동 내역 + 유용한 함수가 저장되어있습니다.

(Detail.js 파일)

import React from 'react';
import { useHistory } from 'react-router-dom';

function Detail(){

 let history = useHistory();
 return (
   <div className="container">
     <div className="row">
       <div className="col-md-6">
         <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
       </div>
       <div className="col-md-6 mt-4">
         <h4 className="pt-5">상품명</h4>
         <p>상품설명</p>
         <p>120000원</p>
         <button className="btn btn-danger">주문하기</button>
         <button onClick={()=>{ history.goBack() }} className="btn btn-danger">뒤로가기</button>
       </div>
     </div>
 </div>
 )
};

export default Detail

history에 저장된 여러 자료들 중 가장 유용한건 goBack()이라는 함수입니다.

이 함수를 실행하면 페이지가 뒤로갑니다.

▲ 그래서 뒤로가기 버튼을 눌렀을 때 goBack() 함수를 실행하도록 코드를 짜본겁니다.

그럼 이제 뒤로 잘 가죠?d

Q. 이런걸 어케알고 코드를 짜는거죠?

A. 이런 라이브러리 사용법은 찾아서 읽거나 검색해봐야 알 수 있습니다. 저도 찾아본거임

그럼 커스텀 페이지로 이동하는 기능을 만들고 싶으면

라이브러리 사용법에 따라 push() 함수를 꺼내쓰시면 됩니다.

(Detail.js 파일)

import React from 'react';
import { useHistory } from 'react-router-dom';

function Detail(){

 let history = useHistory();
 return (
   <div className="container">
     <div className="row">
       <div className="col-md-6">
         <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
       </div>
       <div className="col-md-6 mt-4">
         <h4 className="pt-5">상품명</h4>
         <p>상품설명</p>
         <p>120000원</p>
         <button className="btn btn-danger">주문하기</button>
         <button onClick={()=>{ history.push('/') }} className="btn btn-danger">뒤로가기</button>
       </div>
     </div>
 </div>
 )
};

export default Detail

▲ push() 라는 함수를 쓰시고 안에 경로를 입력하시면 그 경로로 이동합니다.

이런 기능도 유용하겠죠?

Switch 컴포넌트에 대해 알아보자

맨 처음 라우터 import 어쩌구 해왔을 때 { Link, Router, Switch } 이렇게 3개의 컴포넌트를 import 해왔었습니다.

그 중 Switch> 라는건

매치되는 Route> 들을 전부 보여주지 말고 한번에 하나만 보여주세요~ 기능을 만들고 싶을 때 씁니다.

예를 들기 위해 이거 하나만 따라적어봅시다.

(App.js 파일) 1225

function App(){
 return (
   <div>
     <나머지HTML/>
     <Route exact path="/">
       어쩌구
     </Route>
     <Route path="/detail">
       <Detail/>
     </Route>
     <Route path="/:id">
       <div>새로 만든 route입니다</div>
     </Route>
   </div>
 )
}

▲ 기존 Route> 들 있던 곳에 새로운 Route> 하나만 맨 밑에 하나 추가했습니다.

새로운 Route는 path를 /:id 라고 적었는데, 이게 뭐냐면 URL 파라미터라는 문법인데

그냥 / 슬래시 뒤에 모든 문자가 오면 이 Route로 안내해주세요~를 뜻합니다. (다음 시간에 배울 예정)

암튼 아무 문자나 넣어도 이 경로로 이동을 시켜주라고 코드를 짜놓았습니다.

Q. 그럼 /detail로 이동하면 어떤게 보이죠?

A.

(1) <Detail> (2) <div>새로 만든 route입니다</div> 이거 둘다 보여줍니다.

(왜냐면 리액트 라우터는 그냥 URL 매치되는 것들 전부 다 보여준다니깐요)

이런걸 방지하고 싶다, 그냥 한번에 하나의 Route>만 보여주고 싶다 그러면

Route>들을 위에서 import 해온 Switch> 태그로 감싸면 됩니다.

(App.js 파일)

function App(){
 return (
   <div>
     <나머지HTML/>
     <Switch>
       <Route exact path="/">
         어쩌구
       </Route>
       <Route path="/detail">
         <Detail/>
       </Route>
       <Route path="/:id">
         <div>새로 만든 route입니다</div>
       </Route>
     </Switch>
   </div>
 )
}

▲ 다 감싸면 이제 여러개의 Route가 매칭이 되어도 맨 위의 Route 하나만 보여줍니다.

이걸 응용하시면 이전시간에 겪었던 / 경로 문제도 exact 쓰지않고 해결할 수 있습니다.

답은 안알랴줌이니까 잘 생각해보십시오.


React Router 3 : URL 파라미터로 상세페이지 100개 만들기

라우터의 중요한 기능인 URL 파라미터에 대해 알아봅시다.

URL 뒤에 뭘 적든간에 이리로 안내해주세요~ 라는 뜻으로 사용할 수 있는 일종의 URL 작명법입니다.

이걸 이용하시면 상세페이지 여러개를 한번에 만들어낼 수 있습니다.

그래서 우리도 상품 3개에 해당하는 각각의 상세페이지를 만들어주도록 합시다.

일단 Detail> 컴포넌트에 실제 상품명 데이터바인딩좀 해봅시다

App 컴포넌트에 있던 shoes 라는 state에 상품 정보들이 쭉 저장되어있습니다.

그 중에 첫째 상품의 데이터를 Detail 컴포넌트에 데이터바인딩해보도록 합시다.

그냥 shoes[0].title 이런걸 Detail>안에다가 박아넣으면 됩니까?

됩니다 ㄱㄱ

는 아니고 당연히 props 문법을 써야 Detail>은 부모가 가진 state를 사용할 수 있습니다.

20220427_143408

그러니 shoes 라는 state 변수를 props로 전송해서 보내주신 후에 데이터바인딩하시면 됩니다.

props 전송하는 법이 기억나면 직접하시면 되고 까먹었다면 하단을 보도록 합시다.

어떻게 까먹을수가

(App.js 파일)

import 많은 곳;

function App(){
  return (
    <div>
      <Navbar></Navbar> (상단메뉴 레이아웃)

      <Route path="/">
        <Jumbotron></Jumbotron> (Jumbotron 대문 레이아웃)
        <>상품3개 카드 레이아웃</>
      </Route>

      <Route path="/detail">
        <Detail shoes={shoes}/>
      </Route>
    </div>
  )
}

▲ 1. 여기서 Detail shoes={shoes}/> 이렇게 원하는 state를 골라 전송하고

(Detail.js 파일)

import React from 'react';

function Detail(props){
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
        </div>
        <div className="col-md-6 mt-4">
          <h4 className="pt-5">{props.shoes[0].title}</h4>
          <p>{props.shoes[0].content}</p>
          <p>{props.shoes[0].price}원</p>
          <button className="btn btn-danger">주문하기</button>
        </div>
      </div>
  </div>
  )
};

export default Detail

▲ 2. Detail 컴포넌트 안에선 props 등록하고 자유롭게 보낸 변수나 state를 쓰시면 됩니다.


여기서 매우 중요한 질문 :

Q. 그냥 애초에 shoes라는 state같은걸 Detail 컴포넌트에다가 만들면 되는거 아닙니까? 그럼 props 귀찮게 안써도 될텐데

A. 좋은 방법입니다. 근데 React, Angular, Vue 이런거 쓸 때 항상 염두에 두셔야하는게

데이터는 항상 위에서 아래로 흘러야합니다.

만약에 그냥 < Detail >안에 state를 만들었다고 칩시다.

그리고 App>안에 Detail> & Detail2> 컴포넌트가 있다고 칩시다.

그럼 Detail2>에서 Detail>안에 있는 state가 필요하면 어떡합니까?

App>으로 state를 올려보냈다가 다시 Detail2>로 props로 전송하나요?

딱봐도 귀찮고 어렵습니다.

그래서 상위컴포넌트가 중요 데이터를 다 가지고 있어야합니다.

그리고 하위컴포넌트는 데이터를 항상 props로 받아서 써야합니다.

이것이 좋은 관습입니다. 왜냐면 안그러면 데이터를 역방향으로 전달시킨다면 props보다 훨씬 귀찮은 문제들이 생기니까요.

그러니 state 만들 땐 state를 필요로하는 컴포넌트들 중 가장 최상위 컴포넌트에 보관하시길 바랍니다.

다른 컴포넌트에서 안쓰는 데이터는 그냥 아무 컴포넌트에 만드셔도 되는데

다양한 곳에서 쓸 것 같은 중요한 데이터는 항상 상위 컴포넌트, 혹은 귀찮으면 그냥 최상위 컴포넌트인 App 컴포넌트에 보관하도록 합시다.

상세페이지 3개 만들기

상세페이지는 어떻게 만들거냐면, 일단 URL 주소부터 생각하시면 됩니다.

/detail/0으로 접속하면 0번째 상품의 상세페이지

/detail/1으로 접속하면 1번째 상품의 상세페이지

/detail/2으로 접속하면 2번째 상품의 상세페이지

가 출현하게 만들겁니다.

(왜냐면 일반적인 블로그, 쇼핑몰들도 이렇게 숫자로 페이지들을 구분하니까요.)

그럼 Route>를 여러개 만들면 되겠죠?

(App.js 파일)

function App(){
  return (
    <div>
      <나머지HTML/>
        <Route path="/detail/0">
          <Detail shoes={shoes}/>
        </Route>
        <Route path="/detail/1">
          <Detail shoes={shoes}/>
        </Route>
        <Route path="/detail/2">
          <Detail shoes={shoes}/>
        </Route>
    </div>
  )
}

▲ 이런 식으로 3개의 페이지를 만들었습니다. 완성!

하지만 이거 너무 반복적이죠? 반복문을 돌리고 싶은 충동도 생기고요.

근데 URL 만드실 땐 반복문은 안쓰고 보통 URL 파라미터 문법을 이용해 축약을 시켜줍니다.

(App.js 파일)

function App(){
  return (
    <div>
      <나머지HTML/>
        <Route path="/detail/:id">
          <Detail shoes={shoes}/>
        </Route>
    </div>
  )
}

▲ 저번시간에 잠깐 했던 : 이거 콜론기호를 쓰시면 되는데, 이게 뭔뜻이냐면

:id 자리에 아무 문자나 입력하면 Detail> 컴포넌트를 보여주세요~ 입니다.

그럼 여러분 이제 /detail/1234 아무거나 입력해도 Detail> 컴포넌트를 보여줍니다.

  • id라는 부분은 함수 파라미터처럼 자유롭게 작명해주시면 됩니다.

  • 파라미터는 2개 3개 몇개든 추가할 수 있습니다. /detail/:id/:name 이런 식도 가능합니다.

그럼 /detail/0 혹은 /detail/1 이렇게 접속하면 이제 상세페이지가 잘 뜨죠?

근데 항상 같은 상품명이 뜨는게 문제입니다. 이걸 해결해봅시다.

각각 URL 접속시 상품명을 다르게 보여줘야 하는데..

지금은 어떤 URL로 접속하든 항상 0번째 상품명만 보이죠?

/detail/0으로 접속하면 0번째 상품명

/detail/1으로 접속하면 1번째 상품명

이 보여야합니다.

그럼 Detail 페이지에서 데이터바인딩을 이렇게 하면 되겠죠 뭐

(Detail.js 파일)

import React from 'react';

function Detail(props){
  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
        </div>
        <div className="col-md-6 mt-4">
          <h4 className="pt-5">{props.shoes[:id자리에 있던숫자].title}</h4>
          <p>{props.shoes[:id자리에 있던숫자].content}</p>
          <p>{props.shoes[:id자리에 있던숫자].price}원</p>
          <button className="btn btn-danger">주문하기</button>
        </div>
      </div>
  </div>
  )
};

export default Detail

▲ 그래서 :id 자리에 입력한 숫자를 저기에 집어넣고 싶습니다.

그런게 되냐고요? 라우터 라이브러리 사용법을 찾아보면 나올 것 같습니다.

찾아보면 useParams() 라는 훅을 사용하면 된다고하네요. 그래서 사용해보겠습니다.

(Detail.js 파일)

import React from 'react';
import { useHistory, useParams } from 'react-router-dom';

function Detail(props){

 let { id } = useParams();
 return (
   <div className="container">
     <div className="row">
       <div className="col-md-6">
         <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
       </div>
       <div className="col-md-6 mt-4">
         <h4 className="pt-5">{props.shoes[:id자리에 있던숫자].title}</h4>
         <p>{props.shoes[:id자리에 있던숫자].content}</p>
         <p>{props.shoes[:id자리에 있던숫자].price}원</p>
         <button className="btn btn-danger">주문하기</button>
       </div>
     </div>
 </div>
 )
};

export default Detail

▲ 1. 맨 위에서 import를 이용해 useParams를 가져왔고 2. 그걸 변수에 저장했습니다.

useParams() 라는 함수는 현재 URL에 적힌 모든 파라미터를 {파라미터1,파라미터2} 이런 식으로 저장해주는 고마운 함수입니다.

그걸 destructuring 문법을 이용해서 따로따로 변수로 빼서 저장한 것이고요.

그래서 id라는 변수는 :id 자리에 있던 숫자를 의미합니다.

그러니까 /detail/1로 접속하면 id라는 변수는 1이 되고

/detail/100 으로 접속하면 id라는 변수는 이제 100이 되는 겁니다.

이것이 우리가 찾던 그 변수군요. 그걸 아까 필요했던 곳에 집어넣도록 합시다.

그럼 Detail.js는 최종적으로 이렇게 되겠군요

(Detail.js 파일)

import React from 'react';
import { useHistory, useParams } from 'react-router-dom';

function Detail(props){

 let { id } = useParams();
 return (
   <div className="container">
     <div className="row">
       <div className="col-md-6">
         <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
       </div>
       <div className="col-md-6 mt-4">
         <h4 className="pt-5">{props.shoes[id].title}</h4>
         <p>{props.shoes[id].content}</p>
         <p>{props.shoes[id].price}원</p>
         <button className="btn btn-danger">주문하기</button>
       </div>
     </div>
 </div>
 )
};

export default Detail

그래서 이것이 리액트에서 상세페이지 만드는 패턴이라고 보면 되겠습니다.

숙제 : 자료의 순서가 변경되면 상세페이지도 고장나는 문제

그니까 지금 /detail/0으로 접속하면 0번째 상품이 보입니다. (White and Black 이라는 상품)

근데 메인페이지나 다른 페이지에서 상품 순서를 가격순으로 변경하는 기능을 만들어버렸다고 가정합시다.

그래서 shoes라는 상품 데이터가 가격 순으로 변경이 되었습니다.

그럼 0번째 상품이 Red Knit 이라는 상품으로 변합니다.

그럼 이제 /detail/0으로 접속하면 0번째 상품, 즉 Red Knit 이라는 상품이 보이게 되는 것입니다.

이런 일종의 버그같은 것을 어떻게 해결하면 좋을까요?


그냥 바로 힌트드림

  • Detail.js에 데이터바인딩하실 때 0번째 상품의 제목을 여기 보여주세요~ 라고 썼는데 이게 아니라

상품의 영구번호가 0인 상품의 제목을 여기 보여주세요~ 하시면 됩니다.

영구번호는 shoes라는 상품 데이터안에 함께 저장되어있습니다.

이걸 자바스크립트 문법을 이용해 열심히 구현해보시고 답을 펼쳐보시길 바랍니다.


딱 1시간 드립니다. 1시간 해보셨다면 펼쳐봐도 봐드림

(shoes라는 상품데이터는 이렇게 생김)

[
  {
    id : 0,
    title : "White and Black",
    content : "Born in France",
    price : 120000
  },

  {둘째상품},

  {셋째상품}
]

현재 shoes라는 상품데이터들 안엔 {id : 0} 이런 영구번호가 있습니다.

그럼 현재 /:id 자리에 입력한 값과 영구번호가 같은 {상품데이터}를 찾아서

데이터바인딩해주면 되는게 아닐까요.

그럼 Detail.js는 최종적으로 이렇게 되겠군요

(Detail.js 파일)

import React from 'react';
import { useHistory, useParams } from 'react-router-dom';

function Detail(props){

  let { id } = useParams();
  let 찾은상품 = props.shoes.find(function(상품){
    return 상품.id == id
  });

  return (
    <div className="container">
      <div className="row">
        <div className="col-md-6">
          <img src="https://codingapple1.github.io/shop/shoes1.jpg" width="100%" />
        </div>
        <div className="col-md-6 mt-4">
          <h4 className="pt-5">{찾은상품.title}</h4>
          <p>{찾은상품.content}</p>
          <p>{찾은상품.price}원</p>
          <button className="btn btn-danger">주문하기</button>
        </div>
      </div>
  </div>
  )
};

export default Detail

find() 라는 ES6 신문법이 있습니다. Array 안에서 원하는 자료를 찾고싶을 때 사용합니다.

filter() 함수, 그냥 반복문 이런거 쓰셔도 전혀상관없습니다.

1 . find()는 array 뒤에 붙일 수 있으며, 안에 콜백함수가 들어갑니다.

2 . 콜백함수 내의 파라미터는 (제가 상품이라고 적은거) array 안에 있던 하나하나의 데이터를 의미합니다.

3 . return 오른쪽엔 조건식을 적을 수 있습니다. 이게 참인 데이터만 새로운 변수에 저장해줍니다.

4 . 조건식엔 그리고 그걸 현재 URL의 /:id에 적힌 값과 상품의 영구번호 (상품.id)가 같은지 비교하고 있는 겁니다.

그래서 /detail/0으로 접속시 찾은상품이라는 변수를 출력해보시면 아마 영구번호가 id : 0인 데이터가 나올겁니다.

/detail/1로 접속시 찾은상품이라는 변수는 영구번호가 id : 1인 데이터일겁니다.

그래서 찾은상품이라는 변수를 이용해서 상품명, 가격 HTML 부분에 데이터바인딩을 했을 뿐입니다.

성공!

지금은 프론트엔드에서 모든 데이터를 다루고 있어서 어려운 + 반복문스러운 find() 함수를 사용한 것이지만

실제 개발할 땐 그냥 서버에 id : 0인 상품데이터를 Ajax로 요청하는 경우가 많을겁니다.

그럼 저렇게 find() 어쩌구를 쓰는게 아니라 ajax 요청하는 코드가 들어가있겠고

ajax 요청을 성공하면 {} 중괄호 안에 깔끔하게 상품데이터가 하나만 딱 들어올 것 같군요.


styled-components를 이용한 class없는 CSS스타일링

컴포넌트가 많은 경우 스타일링을 하다보면 불편함이 생기는데

1 . class 만들어놓은걸 까먹고 중복해서 또 만들거나

2 . 갑자기 다른 이상한 컴포넌트에 원하지않는 스타일이 적용되거나

3 . CSS 파일이 너무 길어져서 수정이 어렵거나

이런 경우가 있습니다.

그래서 컴포넌트 제작할 때 스타일을 바로 입혀서 컴포넌트를 만들어버릴 수도 있는데

styled-components라는 인기 라이브러리를 설치하여 이용하시면 됩니다.

물론 호불호가 갈릴 수 있습니다.

일단 설치부터 해봅시다

터미널을 여시고

yarn add styled-components 혹은

npm install styled-components

해주시면 됩니다.

그리고 사용하고 싶은 컴포넌트 맨위에 무언가를 import 해오셔야합니다.

import styled from 'styled-components'

Detail.js 파일 위에 ▲ 위처럼 입력해서 import 해오십시오.


기본적인 사용법

이 라이브러리를 이용하시면 컴포넌트를 만들 때 스타일을 미리 주입해서 만들 수 있습니다.

제가 한번 예시로 padding : 20px인 div박스를 styled-components를 이용해 만들어보겠습니다.

import React, { useState } from 'react';
import styled from 'styled-components';

let 박스 = styled.div`
  padding : 20px;
`;

function Detail(){
  return (
    <div>
      <HTML 많은 곳/>
      <박스></박스>
    </div>
  )
}

1 . div박스를 하나 만들고 싶으면 저렇게 styled.div 라는걸 사용하시면 됩니다. p태그 만들려면 styled.p 이런 식입니다.

2 . 그리고 그 오른쪽에 backtick 기호를 이용해서 기본 스타일을 다 넣어주시면 됩니다.

3 . 그리고 그걸 변수로 저장하면 컴포넌트가 완성됩니다. 원하는 곳에 사용하시면 됩니다.

그럼 최종적으로 padding : 20px인 div박스를 저렇게 만들 수 있습니다.

class 이런거 선언 필요없이 <컴포넌트>를 하나 만들었죠?

따로 css 파일을 건들 필요도 없고요. 그래서 사용합니다.

한번 더 해보도록 합시다.

이번엔 font-size : 25px인 h4 태그를 만들고 싶으면 어떻게할까요?

아까랑 똑같이 하시면 됩니다.

import React, { useState } from 'react';
import styled from 'styled-components';

let 박스 = styled.div`
  padding : 20px;
`;
let 제목 = styled.h4`
  font-size : 25px;
`;

function Detail(){
  return (
    <div>
      <HTML 많은 곳/>
      <박스>
        <제목>안녕하세요</제목>
      </박스>
    </div>
  )
}

아까의 3스텝 그대로 따라주시면 되겠습니다.

그럼 font-size : 25px인 컴포넌트가 완성되네요.


추가 문법 : props로 스타일링하기

여러가지 사용법 중 가장 유용한건 바로 props로 스타일링하는 방법입니다.

여러가지 비슷한 UI가 필요한 경우 어쩌죠?

예를 들면 위에서 만든 제목 (h4) 요소가 여러가지 색깔 버전으로 필요할 때 어떻게 하냐는 것이죠.

그럼 제목요소를 여러번 복붙하는 것 보다는

다양한 스타일이 필요한 곳에서 props 문법을 이용해 개발해주시면 됩니다.

예를 들면 이렇습니다.

import React, { useState } from 'react';
import styled from 'styled-components';

let 박스 = styled.div`
  padding : 20px;
`;
let 제목 = styled.h4`
  font-size : 25px;
  color : ${ props => props.색상 };
`;

function Detail(){
  return (
    <div>
      <HTML 많은 곳/>
      <박스>
        <제목>안녕하세요</제목>
      </박스>
    </div>
  )
}

▲ 일단 제목(h4)에다가 color 라는 스타일을 집어넣었습니다.

그리고 색상기입란에 ${ props => props.색상 } 이라는 코드를 적었습니다.

  • ${} 이라는 문법은 문자를 생성하는 백틱 기호안에서 쓸 수 있는 ES6 문법인데, 문자 중간중간 함수나 변수를 집어넣고 싶을 때 사용합니다.

  • 그리고 props.색상이라는 props 변수를 여기에 집어넣은 겁니다. 그냥 ${ props.색상 } 이렇게 넣으면 안되고 저렇게 콜백함수로 넣으셔야합니다.

암튼 이렇게 props로 원하는 곳에 빵꾸를 뚫어놓으면 됩니다.

그럼 뭐가 좋냐면

import React, { useState } from 'react';
import styled from 'styled-components';

let 박스 = styled.div`
  padding : 20px;
`;
let 제목 = styled.h4`
  font-size : 25px;
  color : ${ props => props.색상 };
`;

function Detail(){
  return (
    <div>
      <HTML 많은 곳/>
      <박스>
        <제목 색상="blue">안녕하세요1</제목>
        <제목 색상={'red'}>안녕하세요2</제목>
      </박스>
    </div>
  )
}

▲ props 로 미리 빵꾸를 뚫어놓으신 부분에 원하는 문자를 전송해줄 수 있습니다.

위의 예제에선 props.색상이라는 props에 각각 blue라는 문자, red라는 문자가 들어가서 컴포넌트가 생성됩니다.

그럼 미리보기 화면에선

안녕하세요1

안녕하세요2

각각 컴포넌트가 이렇게 다른 색으로 뜹니다.

같은 컴포넌트인데 props 문법을 이용해 각각 다른 스타일을 넣을 수 있다는 것이지요.

편리하겠죠?


참고 : 리액트에서 props 전송하실 땐 두가지 방법이 있습니다.

<제목 색상="blue"></제목>
<제목 색상={'red'}></제목>

그냥 일반 텍스트를 전달하고 싶으면 “” 따옴표 안에 쓰시면 되고

변수나 자료형을 담고 싶으면 {} 중괄호 안에 쓰시면 됩니다.

하지만 이런 기능을 CSS로 이걸 구현할 수 없는 것도 아니기 때문에 (class 하나 더 만들면 되잖아요)

저같은 틀딱 개발자들은 CSS가 편합니다.

굳이 자바스크립트 코드와 혼연일체된 스타일을 관리하고 싶진 않기도 하고

스타일이 많아지면 다른 파일로 빼야하는데 그게 CSS 파일 쓰는거랑 뭔 차인지 모르겠고

요즘것들은 편한 것만 할려고하고 나때는 말이여 쯧쯧

하지만 styled-component의 극강의 장점은 CSS 막짜도 된다는겁니다.

1 . 스타일넣을 때 다른 파일이랑 컴포넌트 명이 겹쳐도 전혀 CSS적으로 문제가 생기지 않습니다.

2 . 그리고 나중에 컴포넌트 스타일 수정을 원할 때 CSS가 아니라 컴포넌트 파일을 찾으면 되니 수정도 편리하고요.

개인적으로 내가 CSS 아키텍쳐 잘하면 CSS + SASS로 작성한 뒤 원하는 css 파일만 import 쓰는게 전체적 스타일 관리하는데 편리할 것이고

CSS 초보자라면 styled-components 라이브러리 이용하는게 관리하기 편할 수 있습니다.

그리고 사내에 퍼블리셔라든지 CSS 디자인 담당하는 사람이 있으면

리액트 숙련도를 요구하기 때문에 HTML CSS만 달랑 아는 사람과는 협업이 어려울 수 있습니다.


아니면 CSS대신 SASS를 쓰자 (SASS 문법 10분 총정리)

node-sass 설치는 되는데 미리보기 띄울 때 에러가 난다면

nodejs 14버전을 쓴다면 node-sass 지웠다가 4.14버전으로 설치하면 끝입니다.

지우는건 npm uninstall node-sass 혹은 yarn remove node-sass 입니다.

설치는 npm install node-sass@4.14.1 이렇게 하면 될듯요

nodejs 16버전 이상을 쓴다면

  1. npm install node-sass 혹은 yarn add node-sass로 6버전 설치해주시고

  2. node_modules 폴더랑 yarn.lock 혹은 package-lock.json 보이는걸 다 삭제하시고

  3. 터미널에서 yarn install 혹은 npm install로 node_modules 폴더 재설치 해주시면 잘 되는듯요

실은 지금은 Sass 쓰시려면 nodejs 지우고 14버전 설치를 추천드립니다.

node-sass 만든사람에게 따지려면 https://github.com/sass/node-sass/issues/3103

▲ node-sass는 세계 최고로 에러 잘나는 라이브러리기 때문에 에러나면 참고합시다.

CSS가 너무 길어진다면

CSS를 약간 프로그래밍 언어스럽게 작성할 수 있는 SASS라는 preprocessor 를 이용하시면 됩니다.

SASS에선 변수, 함수, 반복문, 연산자 이런게 사용가능해서

SASS 문법으로 쉽게쉽게 짧게짧게 CSS를 작성할 수 있습니다.

작성한 뒤에 CSS로 컴파일만 해주시면 됩니다.

근데 그건 node-sass라는 라이브러리만 설치하시면 알아서 해줍니다. 설치해보도록 합시다.

설치부터 합시다 & SASS가 뭐냐면

터미널 켜시고

npm install node-sass

yarn add node-sass

둘중 하나 입력하시면 됩니다.

CSS는 주먹구구식으로 작성하면 되는 원초적인 스타일링 언어입니다.

근데 CSS를 조금 더 프로그래밍언어스럽게 다룰 수 있는 CSS 대체 문법이 존재합니다.

SASS라는 pre-processor 입니다. 이걸 이용하면 변수, 반복문, 함수 이런 문법으로 CSS를 작성할 수 있습니다.

그리고 SASS 문법으로 스타일을 쭉 작성하셨다면 다시 CSS로 컴파일을 하셔야합니다.

그건 node-sass 라는 라이브러리가 자동으로 알아서 해주기 때문에 그래서 방금 설치한겁니다.

SASS 파일을 생성해서 장착해봅시다

Detail.js에만 종속되는 CSS파일을 만들고 싶으면

src 폴더 내에 Detail.css 를 만들어 작성합니다.

그 후에 Detail.js에 방금 만든 css 파일을 import 해오시면 됩니다.

(Detail.css 파일)

.red {
  color : red;
}
(Detail.js 파일)

import './Detail.css';

▲ 이런 식으로요. 이렇게 스타일링하는게 일반적입니다.

근데 SASS문법으로 작성하고 싶으시면 그냥 파일명만 .scss로 바꿔주시면 끝입니다.

(Detail.scss 파일)

.red {
  color : red;
}
(Detail.js 파일)

import './Detail.scss';

그러면 scss 파일 내에선 SASS 문법을 가득 작성하실 수 있습니다.

그럼 CSS 파일로 자동으로 알아서 컴파일해줍니다.

그럼 SASS문법을 쓰는 이유를 알아봅시다.

SASS 문법 1. 변수 사용

scss파일에선 변수를 사용할 수 있습니다. 이렇게 사용합니다.

(Detail.scss)

$메인칼라 : #ff0000;

.red {
  color : $메인칼라;
}

$ 변수명 : 집어넣을값;

이렇게 변수를 만들고 원하는 곳에서 사용가능합니다.

색상 뿐만 아니라 px값 퍼센트 값 별걸 다 집어넣을 수 있습니다.

변수명은 당연히 한글도 잘 먹습니다. 요즘 한글 안되는 코딩 언어가 어딨습니까.


SASS 문법 2 . @ import

실은 그냥 CSS문법이긴 한데 많이 쓰니 넣어두겠습니다.

CSS 파일 간 import를 하실 수 있는 문법인데 @ import ‘./어쩌구.css’; 이렇게 사용하면 끝입니다.

보통 CSS 아키텍쳐 이런거 잘하는 분들이 자주 하는데

평소에 CSS 파일 짜다보면 자주 사용하는 스타일들이 간혹 있습니다. 예를 들면..

body {
  margin : 0;
}
div {
  box-sizing : border-box;
}

이런 스타일들입니다. reset 이라고 보통 부르죠.

이런걸 다른 css (혹은 scss)파일에 저장해두고 필요해질 때마다 @ import 해오시면 편리합니다.

(실은 그냥 우리 앱의 메인 CSS 파일에 집어넣으셔도 되긴합니다)

SASS 문법 3. nesting 문법

CSS 짜다보면 셀렉터를 길게 복잡하게 알아보기 힘들게 쓰는 경우가 있습니다.

scss파일 안에선 셀렉터 말고 이런 식으로도 개발이 가능합니다.

div.container h4 {
  color : blue;
}
div.container p {
  color : green;
}
▲ 이렇게 말고



div.container {
  h4 {
    color : blue;
  }
  p {
    color : green;
  }
}

▲ 이렇게 작성할 수 있습니다.

그냥 셀렉터를 옆으로 길게 나열하는게 아니라 안쪽에다가 작성합니다.

이게 nesting 문법입니다.

h4 {} 이렇게 안쪽에 쓰시면 띄어쓰기 셀렉터랑 같은 의미입니다.

굳이 이렇게 쓰는 이유는

  1. 셀렉터 해석이 쉽고

  2. 관련된 class끼리 뭉텅이로 관리하기 편해서 입니다.

SASS 문법 4. extends 문법

그 전에 간단한 알림창 UI를 디자인해봅시다.

(Detail.scss 파일)

.my-alert {
  background : #eeeeee;
  padding : 15px;
  border-radius : 5px;
  max-width : 500px;
  width : 100%;
  margin : auto;
}
.my-alert p {
  margin-bottom : 0;
}
(Detail.js 파일)

function Detail(){
  return (
    <div>
      <HTML많은곳/>
      <div className="my-alert">
        <p>재고가 얼마 남지 않았습니다</p>
      </div>
    </div>
  )
}

이렇게 하면 아주 예쁜 회색 알림창이 하나 생성됩니다. 끝!

Q. 근데 갑자기 빨간색, 파란색 등 다양한 배경색의 알림창이 또 필요해지면 어쩌죠?

초보) .my-alert 라는 클래스를 복붙해서 .my-alert2라는 클래스를 하나 더 만들어 수정합니다.

배운사람) 손수복붙하지 않고 @ extend 문법을 이용합니다.

(Detail.scss 파일)

.my-alert {
  background : #eeeeee;
  padding : 15px;
  border-radius : 5px;
  max-width : 500px;
  width : 100%;
  margin : auto;
}
.my-alert2 {
  @extend .my-alert;
  background : yellow;
}

.my-alert p {
  margin-bottom : 0;
}

이런 식입니다.

@ extend 어쩌구

이렇게 사용하시면 어쩌구라는 클래스명에 들어있던 모든 내용을 그 자리에 복붙해줍니다.

CSS코드를 재사용할 일이 있을 때 손수 복붙 안하셔도되고 비슷한 UI 만들기도 쉬워집니다.

SASS 문법 5. @ mixin / @ include 문법

mixin은 그냥 함수만드는 문법입니다. (함수문법은 코드 축약하고 재사용할 때 많이 쓰죠?)

SASS에선 function 키워드 대신 @ mixin 이라고 쓰시면 되고

중괄호 안에 내가 축약하고 싶은 코드들을 다 담으시면 됩니다.

그리고 함수를 부를 땐 @ include 함수명() 이렇게 불러줍니다.

(Detail.scss 파일)

@mixin 함수() {
  background : #eeeeee;
  padding : 15px;
  border-radius : 5px;
  max-width : 500px;
  width : 100%;
  margin : auto;
}
.my-alert {
  @include 함수()
}

.my-alert p {
  margin-bottom : 0;
}

이런 식으로 사용합니다.

(자바스크립트 문법과는 다르게 함수명이 위에 선언되어있어야 밑에서 사용가능합니다)

그리고 함수에 파라미터같은것도 넣을 수 있고 여러가지 기능들을 만들어낼 수 있습니다.

반복문 이런 것도 있는데 은근 많이 쓸일은 없어서 SASS 문법에 대한 상세한 내용은 구글을 참고하도록 합시다.


Lifecycle Hook (옛날사람) useEffect (요즘사람)

오늘의 숙제 : Detail 페이지 방문 후 2초 후에 저번시간에 만든 alert 박스가 사라지게 해보십시오.

책이든 블로그든 정말 어딜 들쳐봐도 다들 어렵게 가르치는 개념이 있습니다.

Lifecycle 이라는 건데, 실은 별거아닙니다.

이걸 배우는 이유는 componentDidMount() 이런 유용한 Lifecycle Hook 함수들을 쓰기 위해서 배우는겁니다.

요즘 사람들은 저렇게 긴 함수 안쓰고 useEffect() 라는 깔끔한 함수를 사용하기 때문에 우리도 그걸 배워봅시다.

일단 Lifecycle이 뭔지 알아봐야하는데 그 전에 Link 복붙하고 나서부터 크롬 콘솔창에 뜨는 워닝메세지좀 해결해봅시다.

<Nav.Link> 안에 <Link> 쓰면 브라우저 콘솔창에 워닝이 뜹니다

저번 Router 했을 때부터 뜨는 콘솔창에 “a태그 안에 a태그 넣으면 안될 것 같은디요” 라고 워닝을 해결해봅시다.

App.js로 돌아가서

<Nav.Link><Link to="/"> Home </Link></Nav.Link>

이 부분을 찾습니다.

▼ 그 다음에 이렇게 바꿔줍니다.

<Nav.Link as={Link} to="/"> Home </Nav.Link>

그럼 워닝이 뜨지 않습니다.

as라는 것은 react-bootstrap 문법인데 그냥 기본 a태그 대신 사용할 HTML태그 혹은 컴포넌트를 집어넣을 수 있습니다.

그래서 Link 태그를 집어넣은 것일 뿐입니다. 그럼 끝!

컴포넌트의 Lifecycle & Hook을 알아보자

여러분이 만들어쓰고있는 컴포넌트는 Lifecycle이라는 개념이 있습니다.

컴포넌트도 인생이 있다는겁니다.

컴포넌트는 1. 생성이 될 수도 있고 2. 삭제가 될 수 있고 3. 관련된 state가 변경되면 재렌더링(업데이트)가 일어날 수도 있습니다.

Q. 그래서 이걸 왜 알아야하는데요?

A. 컴포넌트의 인생 중간중간 Hook을 걸 수 있습니다. 그래서 배우는겁니다.

Hook이 뭡니까. 갈고리죠?

Hook을 이용해 인생중간중간에 참견을 할 수 있습니다.

“Detail 컴포넌트 등장 전에 이것좀 해줘”

“Detail 컴포넌트 사라지기 전에 이것좀 해줘”

“Detail 컴포넌트 업데이트 되고나서 이것좀 해줘”

Hook을 사용하면 이런 코드를 짤 수 있다는 것이지요.

Hook의 정확한 명칭은 Lifecycle Hook 이라고 합니다.

Lifecycle Hook은 어떻게 생겼는가

위에서 설명한 Hook들은 원래 class로 만든 컴포넌트에서 사용가능합니다.

▼ 이런식으로 작성합니다.

class Detail2 extends React.Component {
  componentDidMount(){
    //Detail2 컴포넌트가 Mount 되고나서 실행할 코드
  }
  componentWillUnmount(){
    //Detail2 컴포넌트가 Unmount 되기전에 실행할 코드
  }
}

class 컴포넌트 작성하는 곳 안에 그냥 대충 작성하시면 됩니다.

가장 유용한 Hook 두개는

1 . componentDidMount()

2 . componentWillUnmount() 입니다.

각각

1 . 컴포넌트 첫 등장 후 실행할 코드

2 . 다른페이지로 넘어간다든지 등의 사유로 컴포넌트가 사라지기 전 실행할 코드

를 자유롭게 담으시면 됩니다.

그럼 알아서 잘 실행되니 필요할 때 사용하시면 됩니다. 끝!

function 컴포넌트에서 사용하는 useEffect 훅

요즘 리액트개발에선 useEffect를 많이 사용합니다. (약간 더 짧고 쉬우니까요)

어떻게 사용하냐면 그냥 function 컴포넌트 안에 넣어주시면 됩니다. (return 나오기 전에요)

import React, {useState, useEffect} from 'react';

function Detail(){

  useEffect(()=>{
    //코드를 적습니다 여기
  });

  return (
    <HTML많은곳/>
  )
}

1 . 근데 미리 페이지 상단에서 useEffect를 import 해오신 후

2 . useEffect() 를 사용하셔야합니다.

3 . 그리고 안에 콜백함수를 집어넣습니다.

4 . 콜백함수 안에는 Detail 컴포넌트가 첫 등장하고나서 실행하고싶은 코드가 있으면 적어주면 됩니다.

조금 더 자세하게 알아보는… useEffect() 내의 코드의 실행조건은

  • 컴포넌트가 첫 등장해서 로딩이 끝난 후에 (전문용어로 mount 끝났을 때)

  • 컴포넌트가 재렌더링 되고난 후 때 (전문용어로 update 되고난 후에)

입니다.

이제 Detail 컴포넌트 로드시나 업데이트시 뭔가 코드실행하고 싶은게 있으면 여기다 다 적으면 되겠죠?

오늘의 숙제 : Detail 페이지 방문 2초 후에 alert 알림창이 사라지게 하려면?

20220427_171543

저번시간에 만들어놓은 간단한 알림창 HTML이 있습니다.

이걸 페이지 방문 2초 후에 저절로 사라지게 만들어봅시다.

이건 숙제로 하도록 합시다.

힌트) 리액트에서의 UI 만드는 법을 잠깐 생각해보십시오. UI 어떻게 만들었습니까.

힌트2) useEffect 외의 다른 부분 많이 건드셔도 됩니다

그전에 알아야할 내용 : setTimeout

자바스크립트로 X초 후에 코드를 실행하고 싶으면 setTimeout이라는 함수를 사용합니다.

사용법은 이렇습니다.

setTimeout( ()=>{  1초 후 실행할 코드 }, 1000);

끝입니다.

1000이라고 숫자적은 곳에 ms 단위로 시간을 적어주시면 됩니다.

1000이라고 적으면 1초겠죠? 1초 후에 내부 코드를 실행해줍니다.


기능 2. 컴포넌트가 사라질 때 코드를 실행하고 싶으면

이렇게 코드를 짜면 됩니다.

import React, {useState, useEffect} from 'react';

function Detail(){

  useEffect(()=>{
    return function 어쩌구(){ 실행할 코드 }
  });

  return (
    <HTML많은곳/>
  )
}

useEffect() 안에는 return이라는걸 넣을 수 있습니다.

그리고 여기 넣은 함수는 컴포넌트가 사라질 때 실행됩니다.

  • 당연히 다른 곳에서 만들어 놓은 함수명을 입력하셔도 됩니다.

  • arrow function 집어넣으셔도 가능합니다.


기능3. 여러개를 사용하고 싶다면

useEffect는 여러개 사용하셔도 됩니다.

import React, {useState, useEffect} from 'react';

function Detail(){

  useEffect(()=>{
    //1빠로 실행할 코드
  });
  useEffect(()=>{
    //2빠로 실행할 코드
  });

  return (
    <HTML많은곳/>
  )
}

그냥 차례로 쭉 적으면 되는데

적은 순서대로 순차적으로 실행이 됩니다.

나중에 개발시 활용하시면 되고 그럼 이제 숙제나 해보도록 합시다.


useEffect 숙제 풀이 & 나머지 기능

저번시간 숙제 : Detail 페이지 방문 후 2초 후에 alert 박스가 사라지게 해보십시오.

저번시간 내드린 숙제부터 해결한 후 useEffect 훅의 사용법 하나만 더 알아봅시다.

숙제는.. alert 박스가 사라지게 하는 것이 문제입니다.

“근데 사라지게 하는 건 배운 적이 없는 것 같은데..” 는 실은 배웠습니다.

예전에 모달창 이런거 만들 때 [리액트에서 UI 만드는 법]을 설명해드린 적이 있습니다.

[UI 만드는 법]에 의해 alert 박스를 다시 만들어놓으시면 쉽게 UI를 사라지게 만들 수 있습니다.

일단 UI 어떻게 만든다고 했습니까

  1. UI 보이고 안보이고의 상태를 state로 저장해둠 (true/false 이런걸로)

  2. state가 true일 때만 UI를 보여준다고 if문을 짜둠

그래서 이런 법칙에 의해 alert를 다시 만들어주시면 됩니다. 그럼 쉽게쉽게 UI 끄기 켜기 가능

그럼 진짜 만들어보겠습니다.

function Detail() {

  let [ alert, alert변경 ] = useState(true);

  useEffect(() => {

  });
  return (
    <HTML많은곳 />
    {
      alert === true
      ? (<div className="my-alert2">
          <p>재고가 얼마 남지 않았습니다</p>
      </div>)
     : null
    }
  )
}

▲ state를 만들고 state에 의해서 UI를 보여주도록 if문을 완성했습니다.

여러분도 기존에 있던 alert UI를 이렇게 바꾸시면 되겠습니다.

Q. 세상에 모든 UI는 그럼 이렇게 만들어야하나요?

A. 항상 보이는 UI가 아니라 껐다키는 기능이 필요하면 다 이렇게 만듭니다.

숙제 : Detail 페이지 방문 후 2초 후에 alert 박스가 사라지게 해보십시오.

그럼 이제 숙제의 답을 쉽게 낼 수 있습니다.

그냥 Detail 컴포넌트 로드 후 2초 후에 alert라는 state를 false로 만들어주면 되는 것이군요.

그럼 useEffect()안에 이런 식으로 개발하면 되겠군요.

(Detail.js 파일)

function Detail(){

  let [ alert, alert변경 ] = useState(true);
  useEffect(()=>{
    let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
  });

  return (
    <HTML많은곳/>
    {
      alert === true
      ? (<div className="my-alert2">
          <p>재고가 얼마 남지 않았습니다</p>
        </div>)
      : null
    }
  )
}

▲ useEffect 안에 2초 타이머를 추가했고 2초 후에 alert 라는 state를 false로 변경하라고 시켰습니다.

끝!

잉 근데 Detail 컴포넌트가 업데이트될 때도 저거 useEffect 실행됨

원래 그렇습니다. useEffect()는 컴포넌트 등장 & 업데이트가 되고나서 항상 실행됩니다.

컴포넌트 업데이트시 진짜 실행되는지 실험해보도록 합시다.

(Detail.js 파일)

function Detail(){

  let [ alert, alert변경 ] = useState(true);
  let [inputData, inputData변경] = useState('');
  useEffect(()=>{
    let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
  });

  return (
    <HTML많은곳/>

    { inputData }
    <input onChange={ (e)=>{ inputData변경(e.target.value) }}/>

    {
      alert === true
      ? (<div className="my-alert2">
          <p>재고가 얼마 남지 않았습니다</p>
        </div>)
      : null
    }
  )
}

▲ 세줄을 추가했습니다.

  1. inputData라는 빈 state를 하나 만들었습니다.

  2. input> 태그를 만들어서 거기 문자가 입력될 때마다 inputData라는 state에 저장되게 했습니다.

  3. 그리고 그 inputData를 구경하기 위해서 { inputData }데이터바인딩했습니다.

을 왜 만들었냐고요? 그냥 Detail 컴포넌트 강제로 업데이트 시키려고 만들었습니다.

input>에다가 뭔가 입력하면 계속 Detail 컴포넌트가 재렌더링됩니다. (업데이트됨)

그럼 input>에다가 뭔가 입력할 때마다 useEffect() 이것도 실행되겠죠?

근데 useEffect()도 실행되면 안될 것 같죠?

맞습니다. 이건 자원낭비입니다.

업데이트될 때는 useEffect() 실행하지 말아주세요

라고도 코드를 짤 수 있습니다.

그러고 싶으면 좋은말 할 때 그대로 따라합니다.

useEffect(()=>{
   let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
}, []);

useEffect() 함수 끝부분에 대괄호[] 를 집어넣을 수 있습니다.

여기에는 state를 넣을 수 있습니다.

useEffect(()=>{
   let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
}, [ alert ]);

이런 식입니다. 이렇게 사용하시면

alert라는 이름의 state가 변경이 될 때만 업데이트로 치고 실행해주세요~

라고 명령을 줄 수 있습니다.

일종의 실행조건이라고 생각하시면 되겠습니다.

암튼 그럼 1. Detail컴포넌트 로드가 될 때 & 2. alert라는 state가 변경이 될 때만 실행됩니다.

(대괄호 안에 state는 콤마로 여러개 넣을 수 있습니다)

그럼 이건 무슨뜻일까요?

useEffect(()=>{
   let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);
}, []);

▲ 그냥 []안에 아무것도 안넣었습니다.

조건을 안넣은 겁니다.

그럼 이제 이 useEffect() 코드는 컴포넌트가 업데이트 될 때 절대 실행되지 않습니다.

그냥 컴포넌트 로드때만 한번 딱 실행하고 싶은 코드를 담을 때 쓸 수 있는 일종의 트릭쇼입니다.

암튼 그러면 이제 아까 업데이트 될 때마다 타이머가 동작하는 문제가 해결되었죠?

끝입니다.

setTimeout 타이머를 쓰셨으면 타이머해제도 해야합니다.

방금 Detail 방문시 2초 후에 UI 사라지게 해주세요~ 라고 코드를 짰습니다.

근데 2초가 되기도 전에 Detail을 벗어나면 어떻게 될까요?

지금은 별 문제없는 것 같지만 코드가 길어지거나 꼬이면

남아있는 타이머 때문에 이상한 현상이 일어날 수 있습니다.

그래서 컴포넌트가 사라질 때 타이머를 없애는 코드도 추가해주는게 좋습니다.

useEffect(()=>{
  let 타이머 = setTimeout(()=>{ alert변경(false) }, 2000);

  return ()=>{ clearTimeout(타이머) }
}, []);

useEffect안에는 return + 함수를 추가하면

컴포넌트가 사라질 때 특정 코드를 실행할 수 있다고 했습니다.

거기에 clearTimeout을 추가한겁니다.

clearTimeout(타이머이름)

이렇게 쓰시면 타이머를 바로 해제할 수 있습니다.

이것이 버그를 예방하는 에프킬라식 코딩입니다.


리액트에서의 Ajax 요청방법 & Ajax는 무엇인가

다음 경로로 GET 요청을 하면 상품 데이터 3개를 보내줍니다 : https://codingapple1.github.io/shop/data2.json

오늘의 숙제 : 더보기 버튼을 누르면 상품 데이터 3개를 가져온 후 메인페이지 하단에 상품 레이아웃 3개를 추가해보십시오.

리액트에서 Ajax 요청하는 방법을 알아봅시다.

Ajax 요청은 실은 별거아니고 서버에게 요청을 하는데 새로고침 없이 할 수 있게 도와주는 일종의 코드같은 겁니다.

일반 개발환경에선 jQuery 설치 후 ajax 함수 이용하는 방식이 가장 흔한데

리액트 사용자들은 약간 더 가벼운 axios 라이브러리를 설치하거나 fetch 라는 쌩자바스크립트 함수를 사용합니다.

그럼 일단 초짜 개발자 분들을 위해 Ajax가 뭔지 부터 알아봅시다.

Ajax는 뭐냐면

서버에 새로고침없이 요청을 할 수 있게 도와주는 일종의 자바스크립트 코드입니다.

서버랑 요청이라는 것도 알아야겠군요.

서버는 누군가 요청을 하면 데이터를 가져다주는 프로그램일 뿐입니다.

네이버 서버는 naver.com으로 요청하면 네이버 메인페이지 가져다주는 하찮은 프로그램일 뿐이고

넷플릭스 서버는 netflix.com으로 요청하면 넷플릭스 메인페이지 가져다주는 하찮은 프로그램일 뿐입니다.

요청은 그냥 서버에 요청하는 방법을 뜻합니다.

GET, POST 이런 요청방법이 있습니다.

GET : 데이터, 웹페이지 같은걸 읽고 싶을 때 서버에 보내는 요청입니다.

POST : 데이터를 서버로 보내고 싶을 때 서버에 보내는 요청입니다.

실은 웹탐색, 로그인, 검색 이런 모든 것들을 우리는 GET, POST 이런 요청을 통해 진행하고 있습니다.

20220427_172042

GET요청은 쉽게 해보실 수 있는데 여러분 브라우저 주소창이 바로 GET 요청하는 공간입니다.

브라우저 주소창에 naver.com 입력하면 그게 GET 요청입니다.

네이버의 특정 페이지를 갖다달라는 GET 요청을 한겁니다.

POST요청도 쉽게 해볼 수 있는데 네이버 이런데서 로그인 하거나 댓글 올려보시면 그게 POST 요청입니다.

그럼 Ajax는 뭐냐면 GET,POST 이런걸 새로고침 없이 할 수 있게 도와주는 코드입니다.

원래 GET, POST 요청을 하시면 강제로 새로고침이 됩니다.

근데 새로고침 없이 몰래 서버에 GET, POST요청을 할 수 있게 도와주는게 바로 Ajax입니다.

Ajax는 1. jQuery Ajax를 쓰든가, 2. axios 설치해서 쓰든가, 3. 쌩자바스크립트 fetch()를 쓰든가 하시면 됩니다.

근데 리액트 개발환경에선 axios 혹은 fetch()를 많이 사용합니다.

우린 편리하고 참고할 문서도 많은 axios를 설치해서 이용합시다.

메인페이지에 더보기 버튼을 만들어보자

20220427_172115

▲ 버튼을 하나 만들어봅시다. 그리고 이걸 누르면.. 상품 3개가 하단에 더 출현해야합니다.

이걸 어떻게 구현할 거냐고요? 서버에 새로운 상품데이터 달라고 요청을 할겁니다.

그 전에 HTML부터 짜고 생각을 해보도록 합시다.

(App.js)

function App(){

  return (
    <HTML많은곳/>
    <button className="btn btn-primary">더보기</button>
  )
}

HTML은 이렇게 그냥 짜면 되겠습니다. 그리고 Ajax를 이용하기 위해 라이브러리 설치나 합시다.

Ajax쓰려면 axios를 설치하고 import 해옵시다.

터미널에

yarn add axios

npm install axios

둘 중 하나 입력하시면 됩니다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary">더보기</button>
 )
}

▲ 그리고 상단에 import 해오시면 axios로 ajax 요청할 준비는 끝입니다.

이제 따라 작성합니다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary" onClick={()=>{

     axios.get('GET요청할URL');

   }}>더보기</button>
 )
}

▲ axios.get() 이라고 작성하면 GET요청을 새로고침 없이도 몰래 할 수 있습니다.

GET요청을 할 목적지 (URL)은 괄호안에 문자형태로 적어주시면 됩니다.

Q. 어떤 URL로 요청해야할지 제가 어찌알아요?

A. 서버쟁이에게 물어보시면 됩니다. 데이터를 보내주는 URL을 작성하는게 서버개발자의 역할

Q. 지금 서버개발자가 없는데 그럼 어디다 요청하죠?

A. 제가 미리 서버 만들어놓음 ㅅㄱ

다음 경로로 GET 요청을 하면 상품 데이터 3개를 보내줍니다 : https://codingapple1.github.io/shop/data2.json

▲ 진짜 상품데이터가 오는지 확인하고 싶으면 이걸 그대로 브라우저 주소창에서 GET 요청 해보시면 됩니다.

브라우저 주소창에 이 URL을 복붙하면 자료 3개가 나오죠?

20220427_172201

▲ 딱봐도 추가상품 3개의 제목, 가격 이런 정보들입니다.

그럼 이제 우리 사이트의 더보기 버튼을 눌렀을 때 진짜 GET요청도 해보도록 합시다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary" onClick={()=>{

     axios.get('https://codingapple1.github.io/shop/data2.json');

   }}>더보기</button>
 )
}

▲ GET 요청 끝! 그럼 이제 버튼을 누를 때마다 데이터를 몰래 가져오네요.

가져온 데이터를 출력하고 싶으면 하단처럼 작성합니다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary" onClick={()=>{

     axios.get('https://codingapple1.github.io/shop/data2.json')
     .then(()=>{ 요청성공시실행할코드 })
     .catch(()=>{ 요청실패시실행할코드 })

   }}>더보기</button>
 )
}

▲ .get() 함수 바로 뒤에 쩜찍어서 저렇게 두개의 함수를 붙일 수 있습니다.

각각 요청성공/실패시 실행할 코드를 담을 수 있습니다.

더보기를 눌렀을 때 성공/실패메세지를 띄우고 싶다면 다 저런 함수안에 담으시면 됩니다.

요청 성공시 데이터를 출력해보고 싶으면 하단과 같이 작성합니다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary" onClick={()=>{

     axios.get('https://codingapple1.github.io/shop/data2.json')
     .then((result)=>{ console.log(result.data) })
     .catch(()=>{ 요청실패시실행할코드 })

   }}>더보기</button>
 )
}

▲ then 안의 콜백함수 안에 파라미터를 추가하면 그게 받아온 데이터입니다.

result라는 곳이죠?

그 안엔 실제 데이터 뿐만 아니라 성공한 이유라든지 그런 여러가지 정보들도 담겨있습니다.

쌩자바스크립트 문법인 fetch() 문법도 거의 똑같이 사용가능합니다.

fetch(요청할URL) 이렇게 쓰시면 그냥 바로 GET 요청해줍니다.

fetch(요청할URL).then() 이렇게 쓰는 것도 똑같습니다.

하지만 가져온 자료가 JSON이라면 object로 자동 변환이 안됩니다.

(참고) 우리가 요청한 데이터는 array/object 자료가 아닌 JSON이라는 자료형입니다.

20220427_172252

▲ 따옴표가 다 쳐있죠? 왜냐면 서버와 통신할 때는 텍스트만 전송할 수 있습니다.

그래서 텍스트럼 보이게 하기 위해서 Object에 따옴표를 다 친겁니다.

그걸 전문용어로 JSON이라고 합니다.

JSON은 Object 자료형처럼 어쩌구.title 이런 식으로 정보를 뽑지 못해서

JSON자료는 Object로 변환을 해주어야합니다.

axios라이브러리 쓰시면 JSON 자료를 가져와도 지가 알아서 따옴표를 제거한 Object로 자동으로 만들어줘서 편리한데

fetch()는 그런거 안해줍니다.

참고하시면 되겠습니다.

그리고 이 숙제를 다음강의 듣기 전까지 해오시면 되겠습니다.

오늘의 숙제 : 더보기 버튼을 누르면 상품 데이터 3개를 가져온 후 메인페이지 하단에 상품 레이아웃 3개를 추가해보십시오.

그리고 새로 추가된 상품레이아웃엔 새로운 상품 제목등이 잘 박혀있어야겠죠?


리액트에서의 Ajax 요청방법 2 & 숙제풀이

저번시간 숙제 : 더보기 버튼을 누르면 상품 데이터 3개를 가져온 후 메인페이지 하단에 상품 레이아웃 3개를 추가해보십시오.

오늘은 저번시간 숙제 풀이와 Ajax의 사용 예시를 몇가지 더 가져왔습니다.

  • GET 말고 POST 요청을 사용해서 서버로 데이터 전송하는 법,

  • 로딩중입니다 안내 UI 표시해주는 법,

  • 특정 페이지 방문하자마자 Ajax 요청을 실행하려면..

이런 것들을 알아봅시다.

숙제해설 : 더보기 버튼을 누르면 상품레이아웃 3개 추가하기

저번 시간에 상품 데이터 3개를 요청하는건 완료했습니다. 이제 그 데이터로 레이아웃을 만들어봅시다.

그럼 HTML 생성하는 마법같은 문법을 사용하면 되나요? 그건 생각할 필요 없습니다.

상품레이아웃은 Card> 컴포넌트 + map 반복문으로 만들었습니다.

(App.js)

function App(){
  return (
    <div>
      <HTML많은곳/>
      {
        shoes.map((a, i)=>{
           return <Card shoes={shoes[i]} i={i} key={i} />
        });
      }
    </div>
  )
}

▲ shoes라는 state 갯수 만큼 Card> 레이아웃을 생성해주세요~ 라고 코드를 짰었습니다.

그럼 여러분이 할 일은 “Card>를 더 만들어주세요~” 라는 코드를 짜는게 아니라

“shoes 라는 state에 데이터 몇개를 추가해주세요~” 입니다.

그럼 state에 데이터 3개가 추가되면 Card> 레이아웃은 알아서 6개가 될테니까요.

옛날 아조씨 개발자처럼 “HTML을 생성하는 코드를 어떻게 짜지~?” 고민은 하지 말길 바랍니다.

리액트 환경에서 여러분이 할 일은 그냥 데이터 조작입니다. 그럼 HTML은 알아서 바뀜 ㅅㄱ

그래서 더보기 버튼을 누르면 1. ajax 요청으로 데이터 3개를 가져오고

  1. ajax 성공하면 shoes라는 state에 추가하도록 합시다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary" onClick={()=>{

     axios.get('https://codingapple1.github.io/shop/data2.json')
     .then((result)=>{   shoes변경([...shoes, ...result.data ])   })
     .catch(()=>{ })

   }}>더보기</button>
 )
}

▲ then 함수 안에서 ajax가 성공했을 때의 코드를 작성하실 수 있습니다.

거기서 shoes변경() 함수로 shoes라는 state에 데이터를 추가했습니다.

이번엔 state 데이터를 변경하기 위해 사본을 하나 생성하는게 아니라 약간 한번에 처리를 해봤는데

[…shoes, …result.data ]

요로케요

이게 뭔소리냐면 1. shoes라는 기존 state 데이터를 괄호 벗겨서 여기 넣어주시고,

  1. result.data라는 ajax 성공시 받아오는 데이터도 괄호 벗겨서 여기 넣어주세요

  2. 그리고 이걸 전부 [] 대괄호로 감싸서 array를 만들어주세요

입니다. 이러면 기존 state 사본생성 없이도 원하는 데이터를 한큐에 추가하실 수 있습니다.

(지금 Array 데이터를 다루고 있지만 Object 데이터들도 마찬가지로 … 괄호벗기기 연산자 사용가능합니다)

그럼 이제 더보기 버튼 누르면 상품 레이아웃 3개가 추가되죠? 성공입니다.

추가로 고민해볼 사항들

Q. 더보기로 보여줄 상품이 3개 밖에 없습니다. 근데 사용자가 버튼을 또 누르면 어쩌죠?

A. 보여줄 상품의 마지막에 도달했을 시 버튼을 숨기거나 하시면 됩니다.

Q. 지금 ‘어쩌구/data2.json’ 으로 요청했는데 버튼을 또 누르면 ‘어쩌구/data3.json’ 으로 요청하게 하려면?

A. 그건 axios.get()안의 경로를 작성하실 때 하드코딩하지 말고 버튼을 1회 누르면 data2.json, 2회누르면 data3.json 이 경로로 요청하도록 코드를 바꿔주시면 되겠네요.

버튼 누른 횟수를 변수나 state에 저장하시면 편리하겠군요.

Q. 실패했을 경우 어쩌죠?

A. catch() 함수 안에 실행 원하는 코드를 담으시면 됩니다. “요청실패시 사용자에게 알림창 UI를 보여줍니다~” 이런 코드도 괜찮겠네요.

서버에 데이터를 보내는 POST 요청하는 법

가끔은 데이터를 받아오는게 아니라 서버로 전송하기도 해야합니다.

로그인할 때, 검색할 때, 게시물을 발행할 때… 이런 경우입니다.

서버로 데이터를 전송하시려면 POST 요청을 하시면 됩니다.

POST 요청은 데이터 전송할 URL과 전송할 데이터 이 두가지 항목을 입력하실 수 있습니다.

axios.post('https://codingapple1.github.io/shop/data2.json', { id : 'test', pw : 1234})
  .then((result)=>{  })
  .catch(()=>{ })

▲ get 대신 post라는 함수를 쓰시면 되며

URL 옆에 두번째 파라미터로 원하는 데이터를 입력해주시면 됩니다.

그럼 전송됩니다.

POST 요청도 마찬가지로 요청성공/실패의 경우가 있으며

역시 then, catch 함수 안에서 원하는 바를 실행해주시면 됩니다.

요청할 때 header 정보도 보낼 수 있습니다. 사용법은 구글에 많이 나와있으니 필요해지면 찾아사용하도록 합시다.

“로딩중입니다~” 안내 UI는 어떻게 코드를 짜는게 좋을까요?

어떤 요청을 하든간에 전송버튼 누르기 ~ 요청성공 사이에 대기시간이 존재합니다.

서버의 처리속도가 느리거나.. 아니면 저처럼 LG 5G 네트워크를 사용한다면요.

그럴 때 로딩중 UI를 띄우고 싶다면 …

이제 UI 만드는 법은 다 아시죠? 그러면 UI를 얼른 하나 만들어놓습니다.

그리고 그 UI를 이때 띄워주고 이때 없애주시면 됩니다.

(App.js)

import 많은곳;
import axios from 'axios';

function App(){

 return (
   <HTML많은곳/>
   <button className="btn btn-primary" onClick={()=>{

     로딩중 UI 띄워주는 코드
     axios.get('https://codingapple1.github.io/shop/data2.json')
     .then((result)=>{  로딩중 UI 없애는 코드   })
     .catch(()=>{   로딩중 UI 없애는 코드 & 실패UI 띄워주는 코드    })

   }}>더보기</button>
 )
}

▲ 여기에 코드를 짜는게 좋겠군요.

UI 띄우고 없애는 코드는 여러분 지금까지 수업을 잘 들었다면 아시리라 생각합니다.

(역시나 state 변경입니다)

페이지 방문하자마자 Ajax요청을 실행하고 싶으면

가끔 그러고 싶은 경우가 있습니다.

Ajax를 이용해서 페이지 내용을 받아오거나 그럴 때가 있습니다.

그럼 그냥 저번에 배운 useEffect() 함수 안에 집어넣으면 되지 않을까요?

useEffect()는 컴포넌트 등장시/업데이트시 실행되는 함수라고 했으니까요.

function App(){

  useEffect(()=>{
    axios.get().then().catch();
  },[]);
  return (
    <div>
      <HTML많은곳/>
    </div>
  )
}

▲ 이런 식입니다.

그럼 App 이라는 컴포넌트가 등장시/업데이트시 ajax 요청을 실행하겠군요.

하지만 업데이트시 매번 ajax 요청을 하기 싫을 때도 있겠죠?

그럼 위처럼 useEffect() 안에 대괄호를 추가해주시면 되겠군요.

그럼 등장시에만 한번 실행되고 끝납니다.


Component를 3개 중첩해서 만들면 state 전달은 어떻게 하죠?

20220427_172646

▲ 이렇게 컴포넌트를 여러개 만들어놨는데 에 있는 state 데이터를 가 쓰거나 변경하고 싶으면 어떻게 하냐는 소리입니다.

결론부터 말하자면 App에서 Detail, 그리고 Detail에서 Info로 state를 전송하면 됩니다.

= 그냥 props문법 두번 쓰시면 됩니다.

하위 컴포넌트들이 상위 컴포넌트의 state를 변경하고 싶을 때도 state변경함수를 props로 전달만 잘 해주시면 됩니다.

  • 이게 뭔소린지 모르겠다

  • 아직도 props/state에 자신이 없다

그렇다면 저랑 같이 재고데이터를 다뤄보며 props를 한번 더 연습해보도록 합시다.

잘 할 수 있다면 그냥 다음강의로 넘어가도록 합시다.

재고데이터를 표시하고 싶어졌습니다

재고데이터를 state에 저장해서 하나 만들어보도록 합시다.

그냥 예제기 때문에 대충 [10,11,12] 이런 데이터를 저장하면 될듯요. (각각 상품 0,1,2의 재고데이터입니다)

(App.js)

function App(){

  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
  )
}

▲ 그리고 재고 state는 많은 컴포넌트들이 사용할 것 같기 때문에 App.js에 만들었습니다.

여러분도 빨리 App.js 에 state 저렇게 만드시면 됩니다.

이 state 데이터를 Detail> 컴포넌트 내의 Info> 컴포넌트에 보여주고싶습니다.

연습을 위해 Info> 컴포넌트를 Detail> 컴포넌트 안에 우선 하나 만들어보십시오.

그럼 Detail.js로 가보도록 합시다.

(Detail.js)

function Detail(){
  return (
    <div>
      <Info></Info>
    </div>
  )
}

function Info(){
  return (
    <p>재고 : ???</p>
  )
}

▲ 위와같이 Info 컴포넌트를 하나 작성해서 Detail 컴포넌트 안에 집어넣으시면 됩니다.

이제 준비는 끝났습니다.

Q. 그럼 아까의 재고 state 데이터를 Info 라는 컴포넌트 안에 데이터바인딩하고싶으면 어떻게 해야할까요?

A. 데이터가 상위컴포넌트에 있으니까 props로 전송하면 되겠죠뭐

20220427_172755


▲ 근데 App> 안에 있는 state데이터잖아요.

그럼 <App> -> <Detail> -> <Info> 이렇게 데이터를 전송해야합니다.

그냥 props 문법을 2번 써주시면 됩니다.

그래서 저는 이렇게 코드를 짰습니다.

(App.js)

function App(){

  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <div>
      <HTML많은곳/>
      <Detail 재고={재고} />
    </div>
  )
}

(Detail.js)

function Detail(props){

  return (
    <div>
      <HTML많은곳/>
      <Info 재고={props.재고}></Info>
    </div>
  )
}

function Info(props){
  return <p>재고 : { props.재고[0] }</p>
}

▲ props로 재고라는 state 데이터를 2번 전송했습니다.

<App> -> <Detail> -> <Info> 이런 순서로요.

그럼 이제 Info라는 컴포넌트 내에서도 재고라는 state를 사용가능합니다.

복잡하죠?

하위 컴포넌트가 많으면 많아질 수록 props의 양이 증가합니다.

지금 props로 보낼게 하나라 그렇지 수십개면 어떡합니까.

그러니 컴포넌트 만들땐 각오하고 만드는게 좋습니다.

Q. 주문하기 버튼을 누르면 재고 state에서 1을 빼려면?

Detail 컴포넌트 내에 주문하기 버튼이 있습니다.

이걸 누르면 상위 요소가 가지고 있는 재고라는 state의 0번째 데이터에서 1을 빼고 싶은겁니다.

그럼 코드를 어떻게 짜야할까요?

주문하기 버튼이 없으면 이것부터 만들면 됩니다.

(Detail.js)

function Detail(props){

  return (
    <div>
      <HTML많은곳/>
      <button onClick={()=>{}}> 주문하기 </button>
    </div>
  )
}

▲ Detail.js에다가 이쁜 버튼부터 만듭니다.

그럼 이 버튼을 눌렀을 때 재고[0] 데이터에서 1을 빼고 싶은데 어떻게하면 될까요?

state를 다룰 땐 당연히 state 변경함수를 써야한댔죠?

쓰시면 됩니다.

근데 state 변경함수도 상위 컴포넌트에 있기 때문에 이것도 props로 전송해서 쓰시면 됩니다.

(App.js)

function App(){

  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <div>
      <HTML많은곳/>
      <Detail 재고={재고} 재고변경={재고변경} />
    </div>
  )
}
(Detail.js)

function Detail(props){

  return (
    <div>
      <HTML많은곳/>
      <button onClick={()=>{ props.재고변경([9,10,11]) }}> 주문하기 </button>
    </div>
  )
}

▲ 이런 식으로 전송해서 쓰시면 됩니다.

오늘의 교훈 : 함수든 변수든 부모가 가진걸 자식컴포넌트가 사용하려면 항상 props로 전송해서 쓸 수 있습니다.

이게 귀찮으면 컴포넌트를 많이 만들지 않으면 되겠습니다.

혹은 나중에 배울 Context 문법 혹은 redux를 사용하면 됩니다.


만든 리액트 사이트 build & Github Pages로 배포해보기

이번시간은 간단한 내용이기 때문에 글로 빠르게 진행합니다.

여러분이 만든 사이트를 배포하려면 그냥 작업하던 App.js 파일 그대로 올리시는게 아니라

build용 파일을 생성하신 후 그걸 올리셔야합니다.

왜냐고요? 웹브라우저는 HTML CSS JS 이 세개의 언어만 해석할 수 있습니다. 리액트의 이상한 state, JSX 이런거 전혀 못알아듣습니다.

그래서 리액트 프로젝트를 build 라는걸 하시면 브라우저 친화적인 HTML CSS JS 파일로 바꿔줍니다.

그리고 그걸 웹에 올리셔야 사용자들이 여러분의 사이트를 구경할 수 있는겁니다.

우리도 build를 해본 후 무료로 HTML 파일을 호스팅해주는 Github Pages를 이용해 배포까지 해보도록 합시다.

어서 github.com 들어가셔서 계정을 먼저 하나 생성하시길 바랍니다.

Q. 웹서버 가지고 있는데 여기에 배포는 어떻게 합니까?

서버를 만들줄 아는 똑똑이시군요.

리액트는 HTML 이쁘게 만들어주는 툴일 뿐입니다.

그래서 리액트로 열심히 프로젝트 만드시고 build 하시면 index.html 파일이 생성됩니다.

그리고 “어떤 놈이 codingapple.com 으로 접속하면 build/index.html 파일 전송해라”

라고 서버 API를 작성하면 간단한 배포가 끝납니다.


  1. 배포하기 전 체크할 사항

(1) 일단 미리보기 띄워보셨을 때 콘솔창, 터미널에 에러만 안나면 됩니다.

warning 메세지는 사이트 구동에 별 영향이 없기 때문에 테스트해보실 땐 개무시하셔도 됩니다.

(2) 혹시 사이트를 배포하실 때

http://codingapple.com/ 여기에 배포하시는 경우엔 따로 설정이 필요없이 대충 하셔도 되지만

http://codingapple.com/blog/ 이런 하위 경로에 배포하고 싶으시면 프로젝트에 설정이 따로 필요합니다.

여러분의 프로젝트 파일 중 package.json 이라는 파일을 오픈하면 큰 object가 하나 있는데

“homepage”: “http://codingapple.com/blog”, homepage라는 key값을 추가하신 후

여러분이 배포할 사이트 경로를 추가해주시면 됩니다. (혹은 /blog 이렇게 경로 쓰셔도 됩니다)

그리고 리액트 라우터가 설치되어있다면 라우터가 제공하는 basename=”” 속성을 추가하시는게 라우팅 잘될겁니다.

자세한 내용은 https://create-react-app.dev/docs/deployment/#building-for-relative-paths

  1. 별 문제가 없다면 이제 터미널에 build 명령어를 입력하십시오.

여러분이 작성하신 state, JSX, <컴포넌트>, props 이런 문법들은 브라우저가 해석할 수 없으니 그대로 배포할 수 없습니다.

그래서 이런 문법들을 전통적인 CSS, JS, HTML 문법으로 바꿔주는 작업이 필요합니다.

이것을 컴파일 또는 build라고 합니다.

하시려면 여러분의 리액트프로젝트에서 터미널을 켠 후

npm run build

yarn build

둘 중 하나를 입력하시면 됩니다.

그럼 여러분 작업 프로젝트 폴더 내에 build 라는 폴더가 하나 생성됩니다.

20220427_172947 ▲ 여기 안에 index.html, css파일, js 파일이 전부 담겨있습니다.

여기 안에 있는 내용을 모두 서버에 올리시면 됩니다.

그럼 끝!


2. 근데 우린 무료 호스팅해주는 github pages에 올릴겁니다

간단하게 HTML/CSS/JS 파일을 무료로 호스팅해주는 고마운 사이트입니다.

일단 github.com에 들어가셔서 로그인까지 하십시오.

▼ 그 다음엔 우측 상단 + 버튼을 누르셔서 New Repository 버튼을 누르십시오.

20220427_173103


▼ 그 다음엔 노란 곳에 다음과 같이 입력합니다.

20220427_173129 20220427_173140

▲ Repository name 은 꼭 왼쪽에 뜨는 여러분아이디.github.io 라고 입력하셔야합니다.

여러분아이디.github.io 말고 임의로 설정하시면 여러분 코딩인생 끝납니다.

그리고 README 파일 생성도 체크한 뒤에 생성해주시면 됩니다.

  1. Repository 생성이 되었다면 여러분 파일을 여기 올리시면 됩니다.

Repository 생성이 끝나면 repository로 자동으로 들어가질겁니다.

▼ 그럼 거기에 build 폴더 내의 파일을 전부 드래그 앤 드롭하시면 됩니다.

20220427_173208

(주의) build 폴더를 드래그 앤 드롭하는게 아닙니다. build 폴더 안의 내용물이요.

드래그 앤 드롭하시고 초록버튼까지 눌러주시면 배포 끝입니다.

실수했다면 repository 과감하게 삭제하고 다시 만드시면 됩니다.

이제 10분 정도 후에 아까 여러분이 작성했던 https://여러분아이디.github.io 로 들어가시면 사이트가 보입니다.

이제 집가서 부모님께 자랑합시다.

(흔한 github pages 에러) 왜 사이트 주소로 접속했는데 404 페이지가 뜨죠?

  • 10분 더 기다려보십시오.

  • ctrl + shift + r 을 이용해 새로고침 해보십시오.

  • 혹은 repository 생성하실 때 여러분 아이디를 잘못적으신겁니다. 대소문자 틀리지말고 정확히 적으셔야합니다.

정확히 안적었으면 그냥 다시 하나 새로 만드시면 됩니다.


(추가) github이 좋아졌습니다.

이제 여러 repository를 동시에 호스팅해준다고합니다. 다른 HTML 페이지도 호스팅받고 싶으면

  1. 위에서 만든 내이름.github.io 라는 repository 잘 있죠? 그거 지우면 안됩니다.

  2. 남에게 자랑하고픈 새로운 프로젝트를 올릴 repository를 새로 만들어줍니다. 이름은 아무렇게나 하시면 됩니다.

  3. 그 repository에 아까처럼 드래그앤드롭으로 원하는 HTML CSS JS 파일을 업로드하고 확인까지 누릅니다.

  4. repository setting 메뉴에 들어가서 Github pages 부분을 찾습니다.

20220427_173814

▲4. 저기 source 부분을 None이 아니라 main 이런걸로 바꿔주고 저장하셈

  1. 그럼 끝입니다. 이제 님아이디.github.io/repository이름/ 으로 들어가시면 아까 업로드했던 HTML파일을 볼 수 있습니다.

안보이면

님아이디.github.io/repository이름/html파일명.html

이렇게 직접 들어가시면 됩니다. 그리고 첫 업로드 후엔 보통 10~20분넘게 기다려야 반영됩니다.

Q1. 첫 페이지 로딩 속도를 빠르게 하려면

원래 리액트, 뷰로 만든 웹앱들은 첫 방문시 필요한 파일을 전부 로드합니다.

트래픽을 조금이라도 줄이고 싶으면 컴포넌트들을 lazy하게 로딩하는 방법을 사용할 수도 있습니다.

공식 튜토리얼에 있는 lazy 함수 : https://reactjs.org/docs/code-splitting.html#route-based-code-splitting

그리고 어짜피 한국에서 github pages는 개느립니다. 서버가 미국에 있어서 이미지 같은거 로드할 때 한세월임 (이건 어쩔 수 없음)

Q2. 업데이트 사항이 생기면 배포 또 어떻게하죠?

build 또 하시고 그 파일 그대로 다시 업로드하시면 됩니다.

build 할 때 마다 CSS, JS 파일 명이 무작위로 다시 생성됩니다.

그래서 새로 배포할 때마다 사이트 방문자들은 새로운 CSS,JS 파일을 이용할 수 있습니다.

Q3. build 할 때 압축 시키지말고 남기고 싶은 파일은?

여러분이 ./ 부터 시작하는 경로로 첨부한 이미지, js 파일들은 전부 짜부되고 이름이 변합니다.

이름이 변하지 않게 하고 싶으면 public 폴더안에 넣고 build 해보십시오.

그럼 build 하고 나서도 그대로 루트경로에 파일이 남아있습니다.

(개발시 그런 파일들을 이용하고 싶으면 public 폴더에 보관하시고 ./ 이게 아닌 / 경로로 import 해오시면 됩니다)

Q4. 서버에 올렸는데 왜 접속하면 이상한 페이지가 나오거나 일부 img, css파일이 로드가 안되는 것이죠?

대부분 경로 문제입니다.

  • build 할 때 에러 안나셨겠죠

  • 혹시 하위폴더에 배포하신거 아닙니까

  • 배포한 페이지가 안나오면 크롬개발자도구 열어서 index.html이 쓰고있는 css, js, img 파일들의 경로가 제대로 되어있는지 체크해보도록 합시다.

Q5. 메인페이지 말고 왜 특정 페이지로 접속하면 404 에러가 뜨나요?

어쩌구.github.io/detail/1 이렇게 세부 페이지 URL을 주소창에 직접 입력하시면

찾는 페이지가 없어요~ 이렇게 404 에러가 날 수 있습니다.

이건 서버에서 “누군가 어쩌구.github.io/어쩌구 로 접속하면 메인페이지로 안내하세요~”

라고 API 개발을 해놓으셔야합니다.

아니면 URL에 #기호가 붙는 hashRouter를 리액트 라우터 코드짤 때 쓰시든가요.

근데 github은 우리가 서버를 만지고 어찌할 수 있는게 아니고 그냥 HTML 파일 올린것만 샤락 보여주는 곳이기 때문에

사이트 메뉴에다가 페이지 이동버튼을 잘 만들어두시면 되겠습니다.


컴포넌트 많을 때 props 쓰기 싫으면 Context API 쓰셈

저번시간에 잠깐 <컴포넌트>안의 <컴포넌트>안의 <컴포넌트>까지 props 연속사용하는 체험을 했었는데 끔찍했었습니다.

딱봐도 컴포넌트가 5개 6개 중첩해있으면 매우 복잡해질 느낌이 나죠?

props 연속사용이 싫다면 Redux를 쓰든가 아니면 Context 어쩌구를 쓰든가 하시면 됩니다.

그럼 props 전송 없이도 하위 컴포넌트들 끼리 state 값들을 똑같이 공유할 수 있습니다.

둘 중 약간 더 간단하게 쓸 수 있는 리액트 기본 문법 Context API 사용법에 대해 알아봅시다.

(물론 중첩된 컴포넌트가 몇개 없으면 props가 가장 간단하고 좋습니다)

Context 문법으로 props 없이 state 공유하기

말그대로 모든 하위 컴포넌트들이 props 없이도 state를 사용가능하게 만들어주는 문법입니다.

필요할 때 쓰시면 됩니다.

그럼 일단 context 셋팅부터 하십시오.

(App.js)

let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
  )
}

▲ 1. 일단 같은 state 값을 공유하고 싶으면 context부터 만드십시오.

그냥 createContext()라는 함수를 이용해 변수만들라는 소리입니다.

그럼 그 변수는 바로 특별한 <컴포넌트>가 됩니다.

(App.js)

let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
    <재고context.Provider value={재고}>
      <카드레이아웃3개생성하는부분/>
    </재고context.Provider>

  )
}

▲ 2. 아까만든 특별한 컴포넌트로 state 값 공유를 원하는 컴포넌트들을 <범위>로 전부 감쌉니다.

그리고 value={state이름} 이렇게 공유할 state를 집어넣으면 됩니다. 끝!

그럼 이제 <범위> 안에 있는 모든 HTML & 컴포넌트는 재고 state를 이용가능합니다.

복잡한 props 전송없이도요.

근데 쓰려면 뭔가 작업이 하나 필요합니다.

(App.js)

import React, {useState, useContext} from 'react';

function Card(){
  let 재고 = useContext(재고context);

  return (
    <HTML많은곳/>
    <div>{재고[0]}</div>
  )
}

▲ 3. state를 사용하고 싶으면 useContext() 라는 훅을 이용해서 사용을 원하는 context를 불러오셔야합니다.

위에서 쓴건 재고context에 들어있는 state를 변수로 저장해 쓰겠습니다~ 라는 문법입니다.

그럼 이제 let 재고라는 변수엔 아까 지정해놨던 재고라는 state 데이터가 그대로 들어있습니다.

여기까지가 props 전송없이 state를 쓰는 법이라 보시면 되겠습니다.

(그리고 useContext 훅을 쓰려면 상단에 ‘react’ 로부터 import 해오시면 됩니다.)

Q. props보다 불편한데요?

A. 넹 셋팅도 해놓고 그래야해서 불편합니다. 이건 중첩해서 사용한 컴포넌트가 많을 때 빛을 발하는 문법입니다.

그냥 <컴포넌트> -> <컴포넌트> 정도는 그냥 props 쓰는게 가장 편합니다.

그럼 Card> 안에 하위컴포넌트를 하나 더 만들어봅시다

거기서 재고 state를 사용해보도록 합시다.

(App.js)

import React, {useState, useContext} from 'react';

function Card(){
  let 재고 = useContext(재고context);
  return (
    <HTML많은곳/>
    <Test></Test>
  )
}

function Test(){
  return <p> 재고 : ???? </p>
}

▲ 그래서 Card안에 Test라는 컴포넌트를 생성해놨습니다.

이제 Context 문법을 사용해 Test>까지 재고라는 state를 전송해주려면 어떻게 해야합니까.

  1. React.createContext() 로 범위 생성해주고

  2. <범위 value={재고}> </범위> 이걸로 전송원하는 컴포넌트를 감싸고

  3. state 사용을 원하는 컴포넌트는 useContext(범위)를 이용하면 된다고 했습니다.

근데 1,2번은 이미 셋팅이 완료가 되었기 때문에 여러분이 할 일은 3번입니다.

해봅시다.

(App.js)

import React, {useState, useContext} from 'react';

function Card(){
  let 재고 = useContext(재고context);
  return (
    <HTML많은곳/>
    <Test></Test>
  )
}

function Test(){
  let 재고 = useContext(재고context);
  return <p> 재고 : {재고}</p>
}

▲ 그래서 3번 사용법에 따라서 useContext로 데이터를 받아서 사용하면 되겠습니다.

이제 뭔가 props보다 편리한 것 같죠?

아니면 props나 쓰도록 합시다.

응용 : 컴포넌트가 다른파일에 있다면?

Detail.js 라는 곳에서 재고라는 state를 쓰고싶으면 어쩌죠?

는 상관없이 그냥 똑같이 하시면 됩니다.

(App.js)

let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
    <재고context.Provider value={재고}>
      <Detail/>
    </재고context.Provider>
  )
}

(Detail.js)

function Detail(){
  let 재고 = useContext(재고context);  //근데 여기서 에러남 ㅅㄱ

  return (
    <HTML많은곳/>
  )
}

▲ 근데 Detail.js에서 3번 말대로 useContext(범위)를 쓰려고 했으나

재고context is not defined 라는 에러가 뜨네요.

왜냐면 재고context 라는 변수는 App.js에 있으니까요.

= 그럼 그냥 그 변수를 App.js에서 export하고 Detail.js에서 import 해주시면 됩니다.

(App.js)

export let 재고context = React.createContext();

function App(){
  let [재고, 재고변경] = useState([10,11,12]);

  return (
    <HTML많은곳/>
    <재고context.Provider value={재고}>
      <Detail/>
    </재고context.Provider>
  )
}

(Detail.js)

import {재고context} from './App.js';

function Detail(){
  let 재고 = useContext(재고context);  //근데 여기서 에러남 ㅅㄱ

  return (
    <HTML많은곳/>
  )
}

▲ 각각 export와 import 하나씩 추가해주었습니다.

그랬더니 잘 동작하네요. 이런식으로 사용하면 됩니다.

  • export 키워드는 변수나 함수 선언 왼쪽에 붙일 수 있습니다.

그럼 다른 파일에서 import { 변수명, 함수명 } 이렇게 가져와서 쓸 수 있습니다.


Tab 만들기와 리액트에서의 애니메이션 (react-transition-group)

이쯤되면 혼자서 다 할 수 있겠지만 걱정되서 준비했습니다.

탭 UI를 만들어봅시다.

그리고 애니메이션을 부여해보도록 합시다. 애니메이션은 그냥

  1. 일반 CSS 짜듯이 애니메이션 class를 하나 만들어두고

  2. 컴포넌트가 보여질 때 / 업데이트될 때 className에 부여되도록 코드를 짜시면 됩니다.

근데 그 논리 생각하기 귀찮으면 react-transition-group이라는 라이브러리를 사용합니다.

거기서 시키는대로만 하면 딱히 생각을 안해도 애니메이션이 쉽게 부여됩니다.

가벼운 마음으로 탭을 만들어봅시다

복잡해보이는 탭도 역시 UI 만드는 법을 떠올리시면 됩니다.

그냥 모달창만들듯 하면 되는데, 서로 배타적인 모달창이 3개라고 생각하고 만들면 됩니다.

Detail.js에다가 HTML부터 짜보도록 합시다.

(Detail.js)

function Detail(){
  return (
    <div>
      <button>버튼0</button>
      <button>버튼1</button>
      <button>버튼2</button>
      <div>내용0</div>
      <div>내용1</div>
      <div>내용2</div>
    </div>
  )
}

▲ 탭은 별거 아니고 그냥 버튼3개, div 3개가 있는 UI입니다.

그리고 버튼을 누르면 각각 거기 맞는 div를 보여줄 뿐입니다.

이것도 역시 UI 만드는 법을 떠올리시면 쉽습니다.

  1. 몇번째 버튼 눌렀는지를 state로 저장해둠

  2. state에 따라 div를 보이게/안보이게 해주시면 됩니다.

이번엔 state에 true/false 같은 데이터를 저장하는게 아니라 숫자를 저장할겁니다.

버튼이 3개니까 몇번째 버튼을 눌렀는지 0,1,2 같은 숫자데이터를 저장해야 할 것 같으니까요.

근데 지금은 약간 디자인이 하찮아서 코드짤 맛이 나지 않습니다.

그래서 React-bootstrap 사이트 들어가셔서 Tabs 라고 검색한 뒤에 버튼들을 복붙해서 진짜 버튼같은 UI를 만들어봅시다.

(Detail.js)

function Detail(){
  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link-0">
        <Nav.Item>
          <Nav.Link eventKey="link-0">Active</Nav.Link>
        </Nav.Item>
        <Nav.Item>
          <Nav.Link eventKey="link-1">Option 2</Nav.Link>
        </Nav.Item>
      </Nav>
      <div>내용0</div>
      <div>내용1</div>
      <div>내용2</div>

    </div>
  )
}

▲ 이제 좀 멋있는 버튼이 생성된 것 같습니다. (당연히 Nav라는거 상단에 import 해오셔야합니다 react-bootstrap으로부터요)

그럼 일단 UI 만드는 법에 따라 쭉 해봅시다. 일단 UI의 상태를 저장할 state부터 만들어줍시다.

그리고 버튼을 누르면 각각 state가 0,1,2로 변하도록 만들면 되겠네요.

거기까지가 우리가 했던 UI 만드는법이었음

(Detail.js)

function Detail(){

  let [누른탭, 누른탭변경] = useState(0);
  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link-0">
        <Nav.Item>
          <Nav.Link eventKey="link-0" onClick={()=>{ 누른탭변경(0) }}>Active</Nav.Link>
        </Nav.Item>
        <Nav.Item>
          <Nav.Link eventKey="link-1" onClick={()=>{ 누른탭변경(1) }}>Option 2</Nav.Link>
        </Nav.Item>
      </Nav>
      <div>내용0</div>
      <div>내용1</div>
      <div>내용2</div>

    </div>
  )
}

▲ Detail 컴포넌트 안에서만 쓸 것 같으니 여기다가 state 만들어놨습니다.

그 다음에 버튼에다가 onClick을 입력해서 버튼을 누르면 state가 각각 0, 1, 2로 변하게 만들어주면 되겠습니다.

그 다음에 할일은 뭔가요. state의 현재상태에 따라 UI를 보여주면 되는 것이 아닐까요.

state가 0이면 0번 div를 보여줌

state가 1이면 1번 div를 보여줌

이런거 if문으로 짜놓으면 UI 완성입니다. 아이쉬운것

근데 간단한 if/else문은 삼항연산자를 썼었는데 if 갯수가 늘어나면 삼항연산자만으로는 약간 난해할 수 있습니다.

그래서 그냥 if문을 적용한 컴포넌트를 하나 만드시는 것도 좋은 선택입니다.

그래서 만들어봤습니다.

(Detail.js)

function Detail(){

  let [누른탭, 누른탭변경] = useState(0);
  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link-0">
        <Nav.Item>
          <Nav.Link eventKey="link-0" onClick={()=>{ 누른탭변경(0) }}>Active</Nav.Link>
        </Nav.Item>
        <Nav.Item>
          <Nav.Link eventKey="link-1" onClick={()=>{ 누른탭변경(1) }}>Option 2</Nav.Link>
        </Nav.Item>
      </Nav>
      <TabContent />

    </div>
  )
}

function TabContent(){
  if (누른탭 state가 0이면..){
    return <div>내용0</div>
  } else if (누른탭 state가 1이면..){
    return <div>내용1</div>
  } else if (누른탭 state가 2면..){
    return <div>내용2</div>
  }
}

▲ 만들었습니다. 근데 누른탭 state를 써야하는데 그건 부모 컴포넌트에 있죠?

props로 전송하면 쓸 수 있겠군요.

(Detail.js)

function Detail(){

  let [누른탭, 누른탭변경] = useState(0);
  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link-0">
        <Nav.Item>
          <Nav.Link eventKey="link-0" onClick={()=>{ 누른탭변경(0) }}>Active</Nav.Link>
        </Nav.Item>
        <Nav.Item>
          <Nav.Link eventKey="link-1" onClick={()=>{ 누른탭변경(1) }}>Option 2</Nav.Link>
        </Nav.Item>
      </Nav>
      <TabContent 누른탭={누른탭} />

    </div>
  )
}

function TabContent(){
  if (props.누른탭 === 0){
    return <div>내용0</div>
  } else if (props.누른탭 === 1){
    return <div>내용1</div>
  } else if (props.누른탭 === 2){
    return <div>내용2</div>
  }
}

▲ 탭만들기 성공입니다.

UI 만드는법만 잘 기억하면 모든 UI는 쉽게쉽게 만들 수 있습니다.

애니메이션을 어떻게 넣냐면

그냥 대충 HTML CSS 짤 때랑 똑같습니다.

  1. 애니메이션 주는 class를 CSS 파일에 열심히 짜서 제작해놓고

  2. 컴포넌트 등장/업데이트시 className을 부착하시면 됩니다.

className을 어떻게 원할 때 부착하냐고요? className={} 이렇게 중괄호안에 삼항연산자 if문을 쓰든가 하시면 됩니다.

끝입니다. 내가 CSS를 잘한다 그러면 이렇게 개발하는게 훨씬 쉽습니다.

근데 이런 생각을 하기 귀찮거나 나의 CSS 스킬이 갓 태어난 밤비 다리처럼 흔들흔들하다면

라이브러리 설치 후 사용도 좋은 방법입니다.

react-transition-group 이라는 라이브러리를 설치하시면 간단한 애니메이션 부여하기 쉽습니다.

쉬운건 아니고 패턴만 아시면 됩니다.

터미널 켜서

yarn add react-transition-group

npm install react-transition-group

둘 중 하나 입력합시다.

설치가 끝났다면 애니메이션 주고 싶은 컴포넌트 파일 상단에

(Detail.js)

import {CSSTransition} from 'react-transition-group';

이런걸 import 해오시면 끝입니다.

그럼 step1. CSSTransition>으로 애니메이션 적용할 HTML들 감싸면 됩니다.

그럼 step2. 그리고 거기에 in, classNames, timeout 속성 넣으십시오.

(Detail.js)

function Detail(){
  return (
    <div>
      <CSSTransition in={true} classNames="wow" timeout={500}>
        <TabContent 누른탭={누른탭} />
      </CSSTransition>
    </div>
  )
}

function TabContent(){
  if (props.누른탭 === 0){
    return <div>내용0</div>
  } else if (props.누른탭 === 1){
    return <div>내용1</div>
  } else if (props.누른탭 === 2){
    return <div>내용2</div>
  }
}

▲ 그래서 전 탭을 감쌌습니다.

in은 스위치입니다. true일 때 애니메이션을 적용해줍니다.

classNames는 어떤 애니메이션을 적용할지 작명해주는 부분이고

timeout은 작동시간이라 보시면 됩니다.

step3. 그럼 Detail.js에 딸려있는 CSS 파일로 가셔서 애니메이션을 하나 디자인해주시면 됩니다.

(Detail.scss)

.wow-enter {
  opacity : 0
}

.wow-enter-active {
  opacity : 1;
  transition : all 500ms;
}

아까 작명한 wow라는 애니메이션의 작동방식/정의를 내려주시면 됩니다.

.작명-enter 라는 클래스명은 컴포넌트 등장시작시 적용할 CSS

.작명-enter-active 라는 클래스명은 컴포넌트 등장중일시 적용할 CSS입니다.

저는 투명도를 0~1로 변경하기 위해 저렇게 했습니다. 그리고 transition은 서서히 변하게 해주세요~ 라는 속성입니다.

step4. 평소엔 in={true} 이걸 false로 해놨다가 원할 때 true로 바꿔주시면 됩니다.

그래서 일단 바꾸기 쉽게 하기 위해 true라고 하드코딩하지말고 state로 만들어놨습니다.

(Detail.js)

function Detail(){

  let [스위치, 스위치변경] = useState(false);
  return (
    <div>
      <CSSTransition in={스위치} classNames="wow" timeout={500}>
        <TabContent 누른탭={누른탭} />
      </CSSTransition>
    </div>
  )
}

function TabContent(){
  if (props.누른탭 === 0){
    return <div>내용0</div>
  } else if (props.누른탭 === 1){
    return <div>내용1</div>
  } else if (props.누른탭 === 2){
    return <div>내용2</div>
  }
}

▲ state만들고 true/false자리에 집어넣어봤습니다.

그 다음에 언제 false, 언제 true가 될지 알아서 정의해주시면 됩니다.

(false에서 true로 변할 때 애니메이션이 동작합니다.)

저는 이렇게 했습니다.

(Detail.js)

function Detail(){

  let [스위치, 스위치변경] = useState(false);
  return (
    <div>
      <Nav variant="tabs" defaultActiveKey="link-0">
        <Nav.Item>
          <Nav.Link eventKey="link-0" onClick={()=>{ 스위치변경(false); 누른탭변경(0) }}>Active</Nav.Link>
        </Nav.Item>
        <Nav.Item>
          <Nav.Link eventKey="link-1" onClick={()=>{ 스위치변경(false); 누른탭변경(1) }}>Option 2</Nav.Link>
        </Nav.Item>
      </Nav>
      <CSSTransition in={스위치} classNames="wow" timeout={500}>
        <TabContent 누른탭={누른탭} 스위치변경={스위치변경} />
      </CSSTransition>
    </div>
  )
}

function TabContent(props){

  useEffect( ()=>{
    props.스위치변경(true); //탭내용 컴포넌트가 로드될 때 true
  });

  if (props.누른탭 === 0){
    return <div>내용0</div>
  } else if (props.누른탭 === 1){
    return <div>내용1</div>
  } else if (props.누른탭 === 2){
    return <div>내용2</div>
  }
}

▲ 1. 탭의 버튼을 누르면 스위치가 false로 바뀌게 했습니다.

  1. 컴포넌트가 로드될 때 스위치가 true로 바뀌게 했습니다. (useEffect 이용)

그럼 잘 되는군요 짝짝짝

이제 투명도조정 말고도 여러가지 애니메이션은 CSS를 잘 알아서 조정해보시길 바랍니다.

역시 상세한 사용법은 라이브러리 홈페이지 or 구글검색을 추천드립니다.


세계최고로 쉬운 Redux 1 : props 싫으면 쓰세요

(수정) 리덕스 설치시 npm install redux@4.1.2 react-redux 입력하면 됩니다.

리액트를 이용한 실제 사이트개발환경에선 redux를 설치해 쓰는 곳이 많습니다.

장바구니 Cart 페이지를 새로 만들며 왜 쓰는지 부터 알아봅시다.

일단 오늘의 결론부터 말하자면

redux 쓰는 이유 1 : props 전송 없이도 모든 컴포넌트들이 state를 사용할 수 있게 만들어줍니다.

예전에 App>안의 Detail> 안의 Info> 이런 복잡한 하위 컴포넌트만들 때

App>의 state 데이터를 Info>가 사용하고 싶으면 props로 2번 3번 이렇게 보냈지 않습니까.

redux를 쓰시면 복잡한 props 전송이 필요없이 그냥 바로 state 데이터를 꺼내쓸 수 있습니다.

(예전에 배웠던 Context API랑 같은 역할입니다)

Cart.js 장바구니 페이지 만들기 (컴포넌트로)

src 폴더 안에 Cart.js라고 하나 만드시면 되겠습니다.

그리고 장바구니스러운 표 레이아웃을 추가합니다.

그냥 Bootstrap table 레이아웃을 넣으면 되겠군요.

(Cart.js)

import React from 'react';
import {Table} from 'react-bootstrap';

function Cart(){
  return (
    <div>
      <Table responsive>
        <tr>
          <th>#</th>
          <th>상품명</th>
          <th>수량</th>
          <th>변경</th>
        </tr>
        <tr>
          <td>1</td>
          <td>Table cell</td>
          <td>Table cell</td>
          <td>Table cell</td>
        </tr>
      </Table>
    </div>
  )
}

export default Cart;

참고로 tr은 가로줄을 하나 생성해주세요~

td/th는 세로줄을 하나 생성해주세요~ 라는 HTML 태그입니다.

합하면 표가 생성됩니다.

(Bootstrap 사이트에서 붙여넣을 때 thead, tbody 이런건 아무 쓸데없어서 지웠습니다.)

그 다음에 라우터 설정하던 부분으로 가서 /cart 라고 접속하면 컴포넌트를 보여줍시다.

(App.js)

import Cart from './Cart.js';

function App(){
  return (
    <Router path="/cart">
      <Cart></Cart>
    </Router>
  )
}

이제 /cart 로 접속하면 Cart 컴포넌트가 보이죠? 성공입니다.

여기에 가짜 상품데이터들을 데이터바인딩 해볼겁니다.

근데 데이터들을 어디다 만드냐면 App.js 이런데가 아니라 redux를 이용해 보관해볼겁니다.

데이터를 보관하기 위한 Redux 설치/셋팅

redux를 이용하려면 라이브러리 2개를 설치하셔야합니다.

npm install redux@4.1.2 react-redux

터미널에 입력하면 됩니다.

(redux, react-redux 두개의 라이브러리입니다)

redux는 데이터를 엄격하게 관리하는 기능, react-redux는 리덕스를 리액트에서 쓸 수 있게 도와주는 기능을 제공합니다.

그 다음 redux를 이용한 개발환경을 셋팅하시려면 index.js를 열어 다음과 같이 작성합니다.

첫 셋팅은 4개의 step이 있습니다.

(index.js)

import 많은곳;
import {Provider} from 'react-redux';

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>

);

(셋팅 1) 라는걸 import 해오신 다음에

(셋팅 2) 내가 state값 공유를 원하는 컴포넌트를 다 감싸시면 됩니다.

저는 App> 컴포넌트를 감쌌습니다.

그럼 App>컴포넌트와 그 안에있는 모든 HTML, 컴포넌트들은 전부 state를 직접! props 전송없이! 사용할 수 있습니다.

이것이 redux를 이용하는 첫번째 이유입니다.

컴포넌트가 매우 깊숙히 있다면 state전달하려고 props 100번 써야되고 귀찮은데

redux를 이렇게 셋팅해주시면 props 100번 쓰실 필요가 없습니다. 바로 꺼내쓰실 수 있습니다.

(Context 그 문법이랑 매우 비슷합니다.)

(셋팅 3) redux에서 state를 하나 만드시려면 createStore() 함수를 쓰셔야합니다.

useState아닙니다.

(index.js)

import 많은곳;
import {Provider} from 'react-redux';
import {createStore} from 'redux';

let store = createStore(()=>{ return [{id : 0, name : '멋진신발', quan : 2}]  })

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>

);

import 해오신 다음에 createStore(콜백함수) 이렇게 사용하시면 되며

콜백함수엔 뭘 작성하냐면.. 내가 원하는 state 초기값을 퉤 뱉어내면 됩니다.

그럼 state 만들기 끝입니다.

(셋팅 4) 이제 에 만든 state를 props처럼 등록하시면 끝입니다.

이제 정말 셋팅 끝입니다. 이러면 정말 하위컴포넌틀이 props전송없이 state를 사용가능합니다.

(index.js)

import 많은곳;
import {Provider} from 'react-redux';
import {createStore} from 'redux';

let store = createStore(()=>{ return [{id : 0, name : '멋진신발', quan : 2}]  })

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>

);

아무튼 {id : 0, name : ‘멋진신발’, quan : 2} 이렇게 생긴 장바구니용 데이터를 redux store 안에 만들었습니다.

(그리고 redux 설치 후엔 state들을 store라는 명칭으로 부릅니다. 아까 변수명이 store이기도 하고요)

store에 있는 state 데이터 꺼내쓰는 법

이제 저장한 데이터를 Cart.js 가서 써보도록 합시다.

진짜 table>내에 아까 그 장바구니용 데이터를 데이터바인딩을 해보자는겁니다.

근데 그냥 하시면 안되고 store 안에 있는 데이터를 props의 형태로 등록하셔야 사용가능합니다.

그러려면 하단에 있는 2단계 스텝을 따라주시면 됩니다.

(Cart.js)

import 많은곳;
import {connect} from 'react-redux';
function Cart(){
  return (
    <div>
      <Table responsive>
        <tr>
          <th>#</th>
          <th>상품명</th>
          <th>수량</th>
          <th>변경</th>
        </tr>
        <tr>
          <td>1</td>
          <td>Table cell</td>
          <td>Table cell</td>
          <td>Table cell</td>
        </tr>
      </Table>
    </div>
  )
}

function state를props화(state){
  return {
    state : state
  }
}
export default connect(state를props화)(Cart);
  1. 일단 장바구니 데이터사용을 원하는 컴포넌트.js 파일 밑에 function을 하나 만들어줍니다.

  2. 그 다음 export default 하던 부분에 connect() 어쩌구를 적습니다.

(connect 함수는 위에서 import 해오셔야합니다)

1번해설 : 저 함수는 store 안에 있던 state를 props로 만들어주는 역할입니다.

return 안에다가

{ 자유작명하셈 : state }

이렇게 적으시면 됩니다. 그럼 store 안에 있던 모든 state 데이터가 props로 등록됩니다.

그럼 이제 Cart.js에서 자유작명하셈 이라는걸 출력해보면 아까 저장해뒀던 redux내의 장바구니 state가 출력됩니다.

{ 자유작명하셈 : state.name }

▲ 아니면 이렇게 원하는 state만 쏙쏙 뽑아서 등록하셔도 되고요.

2번해설 : connect 함수에 아까 만든 함수를 집어넣습니다. 그냥 react-redux 라이브러리 사용법입니다.

그리고 Cart 컴포넌트도 함께 소괄호 안에 집어넣어주시면 됩니다.

그럼 redux store에 있던 데이터들이 props로 엮인 채로 컴포넌트가 export 됩니다.

소괄호 두개붙이는건 자바스크립트 문법 맞아요?

함수()() 이렇게 쓴건

함수() 이렇게 쓴 부분이 또 다른 함수를 return 했기 때문에

또 소괄호를 뒤에 붙여서 쓸 수 있는 것입니다.

역시 그냥 라이브러리 사용법일 뿐입니다.


세계최고로 쉬운 Redux 2 : reducer/dispatch로 데이터 수정하는 법

저번시간엔 그냥 state를 저장하고 사용하는 방법만 알아봤습니다.

그럼 이제 데이터 수정이 문제입니다.

redux를 쓴다면 state 데이터를 수정하고 싶을 때 그냥 대충 하지 않습니다.

  1. reducer 함수를 만들고 그곳에 데이터 수정하는 방법을 정의해놓습니다.

  2. 그리고 원하는 곳에서 dispatch() 라는 함수를 써서 reducer에게 수정해달라고 요청을 합니다.

꼭 이렇게 데이터를 수정하시길 바랍니다. 안그러시면 redux 쓰는 이점이 없음

왜 이런 헛짓거리를 시키는지도 마지막에 알아봅시다.

장바구니 품목에 +/- 버튼 만들기

그니까 표의 행마다 +/- 버튼을 하나씩 만들어서

이걸 누르면 실제 품목데이터가 1 증가/감소하도록 만들어봅시다.

일단 HTML부터 꾸며봅시다.

(Cart.js)

function Cart(props){
  return (
    <div>
      <Table responsive>
        <tr>
          <th>#</th>
          <th>상품명</th>
          <th>수량</th>
          <th>변경</th>
        </tr>
        { props.state.map((a,i)=>{
          return (
          <tr key={i}>
            <td>{a.id}</td>
            <td>{a.name}</td>
            <td>{a.quan}</td>
            <td><button onClick={()=>{ ??? }}> + </button></td>
          </tr>
          )
        })  }
      </Table>
    </div>
  )
}

▲ 일단 시작 전에 저번시간 코드를 이렇게 업그레이드 했습니다.

(map 반복문을 적용해서 장바구니 항목이 여러개면 tr>을 여러개 생성하도록 했습니다.)

그리고 button>을 하나 추가해서 오늘의 기능을 개발하면 되겠습니다.

이제 + 버튼을 누르면 redux에 있는 state를 수정해야하죠?

근데 redux에 있는 state를 수정하고 싶으면 특별한 방법이 필요합니다.

  1. state 데이터의 수정방법을 index.js에다가 미리 정의해놓고 (일명 reducer)

  2. index.js에게 수정좀 해달라고 부탁하셔야합니다.

안그러면 혼납니다. 수정하는 방법은 reducer로 만들어두는데 어떻게 하는지 알아봅시다.

데이터수정하는 reducer 만드는 법

데이터 수정하는 법은 reducer로 만들어줍니다.

reducer는 어려워보이지만 그냥 function 어쩌구로 시작하는 흔히보는 함수로 만드시면 됩니다.

근데 reducer는 function안에

  1. state 초기값과

  2. state 데이터 수정방법이 잔득 들어있는 함수입니다.

그럼 장바구니 데이터를 조작할 수 있는 reducer를 한번 만들어봅시다.

(index.js)

function reducer(){
  return [{id : 0, name : '멋진신발', quan : 2}]
}
let store = createStore(reducer);

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>
);

일단 셋팅부터 하자면 reducer는 그냥 저렇게 함수로 만들어줍니다.

그리고 state를 퉤 뱉어내는 함수일 뿐입니다.

reducer 만든걸 createStore()안에 넣으시면 reducer가 완성입니다.

지금은 그냥 언제나 state 기본값만 퉤 뱉어내는 reducer일 뿐 별 기능은 없습니다.

혹은 reducer를 만들 때 이렇게 작성하기도 합니다.

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){
  return state
}
let store = createStore(reducer);

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App/>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>
);
  1. 따로 state 변수를 만들고

  2. 그걸 reducer에 default 파라미터 문법으로 집어넣습니다.

아직 별기능없지만 reducer 하나 만들기 끝입니다.

default 파라미터 문법은

함수를 만들 때 실수 또는 의도적으로 파라미터 입력을 안했을 때

기본으로 가질 파라미터를 부여할 수 있습니다.

그냥 파라미터 선언하실 때 = 등호로 입력해주시면 됩니다.


function test(a = 10, b = 20){
  console.log(a + b)
}
test(1);

위의 예제 코드는 콘솔창에 21이 출력됩니다.

왜냐면 a자리엔 1이라는 파라미터가 들어왔는데 b자리엔 아무것도 안들어왔으니까

b에는 default 파라미터로 만들어둔 20이 할당됩니다.


reducer에 데이터 수정방법을 정의해놓자

이제 진짜 reducer 안에 기능개발을 좀 해보도록 합시다.

reducer는 데이터 수정방법을 정의하는 곳입니다.

그냥 이런 식으로 state 데이터 수정방법 쭉 나열하시면 됩니다.

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){
  if (액션.type === '수량증가') {
    return 수량증가된새로운state
  } else {
    return state
  }

}
let store = createStore(reducer);

▲ 방금 데이터가 수정되는 방법을 정의한 겁니다.

  1. ‘수량증가’라는 데이터 수정방법 이름을 하나 작명해주었습니다. (액션.type === 수정방법이름) 이런 식입니다.

  2. if문 안에는 ‘수량증가’라는 요청이 들어올 경우 어떤 state를 퉤 뱉어낼지 정의한 것입니다.

  3. else문 안에는 수량증가 요청이 안들어온 경우 기본 state를 퉤 뱉으라고 정의 했습니다.

데이터 수정방법을 정의했으면 이제 HTML에서 버튼을 눌렀을 때

‘수량증가’ 라고 작명해놓은 데이터 수정방법을 실행해주세요~ 라고 명령을 줄 수 있습니다.

그것은 dispatch() 함수를 이용합니다. dispatch()를 쓰시면 HTML 안에서 reducer함수를 동작시킬 수 있습니다.

(Cart.js에 있던 버튼)

<button onClick={()=>{ props.dispatch({type: '수량증가'}) }}> + </button>

이렇게 하면 버튼 누를 때마다 ‘수량증가’라고 작명해놓은

state 수정방법이 동작합니다.

(type : 데이터수정방법 이부분만 잘 지정해주시면 됩니다.)

귀찮죠? 이게 바로 redux를 쓰는 두번째 이유입니다.

데이터의 수정방법을 미리 정의해둘 수 있다는게 장점입니다.

이게 왜 장점이냐고요?

한번 진짜 해보면 알게될 수도 있습니다.


진짜 데이터를 수정되게 만들어봅시다.

‘수량증가’ 요청이 들어오면 state데이터의 첫째 아이템의 quan 항목이 1 증가하게 해보겠습니다.

근데 array, object로 구성된 state 데이터를 수정하시려면 사본을 만드는게 좋다고 했죠?

그래서 그렇게 수정하면 됩니다.

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){
  if (액션.type === '수량증가') {

    let copy = [...state];
    copy[0].quan++;
    return copy

  } else {
    return state
  }

}
let store = createStore(reducer);

▲ if문 안에 데이터를 수정하는 3줄을 추가했습니다.

state 데이터 카피본을 만들어서 quan 항목에 1을 더해주고 그걸 return으로 퉤 뱉었습니다.

(그냥 평소에 하던 state 수정방법입니다.)

(Cart.js에 있던 버튼)

<button onClick={()=>{ props.dispatch({type: '수량증가'}) }}> + </button>

그럼 이제 버튼을 누를 때마다 수량이 1 증가하죠?

이게 바로 redux에 있는 state 데이터 수정하는 법 끝입니다.

앞으로 데이터 수정하고 싶을 땐 저렇게 1. reducer 만들어놓고 2. dispatch로 reducer를 불러서 수정요청하고 하시면 됩니다.

Q. 그럼 + 버튼 옆에 - 버튼과 기능을 만들어봅시다.

기능도 당연히 reducer를 이용해서 알아서 한번 만들어보십시오.

직접 만들어보셨습니까

전 이렇게 했습니다.

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){
  if (액션.type === '수량증가') {

    let copy = [...state];
    copy[0].quan++;
    return copy

  } else if (액션.type === '수량감소'){

    let copy = [...state];
    copy[0].quan--;
    return copy

  } else {
    return state
  }

}
let store = createStore(reducer);

▲ if문 안에 데이터를 수정하는 3줄을 또 추가하고 ‘수량감소’라고 작명했습니다.

(Cart.js에 있던 버튼)

<button onClick={()=>{ props.dispatch({type: '수량증가'}) }}> + </button>
<button onClick={()=>{ props.dispatch({type: '수량감소'}) }}> - </button>

그 다음에 - 버튼을 만들고 이걸 누르면 ‘수량감소’ 요청을 하도록 코드를 짰습니다.

이러면 이제 누를 때마다 수량이 감소하죠?

이제 redux 데이터들은 이런 스텝에 따라 수정하시길 바랍니다.

  1. reducer에 수정방법을 미리 정의하고

  2. props.dispatch()로 수정방법을 실행해주시면 됩니다. 끝!


읽다가 포기하고 여기까지 스크롤 내린거 다보입니다. 그래서 약간 부가설명을 드리도록 하겠습니다.

“코드만 길어지지 장점을 모르겠군요 redux 왜씁니까”

실은 지금같은 소규모 사이트에선 전혀 필요가 없습니다. (배보다 배꼽이 더 큼)

대규모 사이트들에서 데이터를 한 눈에, 한 곳에 관리할 수 있어서 쓰는겁니다.

대규모 사이트들은 component가 100개있습니다. 그럼 컴포넌트 100곳에서 장바구니 state를 수정하는 코드를 짜게 될듯요.

그럼 중간에 state 하나에 이상한 값이 들어와서 버그가 생기면 어떻게 합니까.

버그 찾으려고 어딜 뒤져야하죠? state 수정하는 100개 컴포넌트를 다 뒤져야합니다.

근데 redux를 만들어 state 수정하는 방법을 미리 정해놓으시면

redux안의 reducer만 잘 들여다보면 됩니다. (혹은 간혹가다 dispatch부분도요)

state 데이터가 어떻게 바뀌는지는 reducer에 전부다 정의되어있으니까요.

그래서 사용합니다.

그래서 리덕스를 쓰시면 전문용어로 ‘state 관리가 용이하다’고 합니다. 그래서 씁니다.

한국어로는 ‘상태관리가 용이하다’라고 합니다.

(맨날 검색해보면 나오는 소리인 “상태관리한다~” 그런 소리가 바로 이 소리였습니다. )

내가 서버개발경험이 있다면 서버의 데이터입출력 API 만드는 과정이랑 유사하다고 이해하면 됩니다.

서버는 미리 API를 짜두죠?

” /delete 라고 적으면 데이터를 이렇게 저렇게 수정할 수 있다~ “ 라고 미리 코드를 짜놓습니다.

리덕스도 비슷한 방식으로 개발하는 것일 뿐입니다.


세계최고로 쉬운 Redux 3 : state와 reducer가 더 필요하면

Cart 페이지에 조그만한 alert UI를 하나 추가하고 싶은데 이걸 열기 & 닫기 기능을 만들고 싶은겁니다.

그래서 열림/닫힘 상태를 저장할 state가 하나 더 필요해진 것이에요.

그럼 redux에서 state를 하나 더 만들고 싶으면 어떻게 해야하는지 알아봅시다.

이번 강의 두줄 요약 :

reducer가 여러개 필요하면 그냥 다른 이름의 reducer2 + reducer2의 초기값을 하나 더 만드시고

이걸 combineReducers()라는 함수안에 집어넣은 후에 createStore()안에 넣으시면 됩니다. 끝

Cart 페이지에 alert 창을 하나 디자인하고 기능을 부여해봅시다.

지금 결제하면 20% 할인해준다는 멋진 안내문을 Cart.js 하단에 띄워주고 싶은겁니다.

열고닫는 기능도 부여하고 싶고요.

이런거 어떻게 만든댔죠? UI 만드는 법에 따라서 HTML 짜고 state 만들고 if문 쓰고 쭉하면 됩니다.

(Cart.js)

function Cart(props){
  return (
    <div>
      <Table>table부분</Table>
      <div className="my-alert2">
        <p>지금 구매하시면 20% 할인</p>
        <button>닫기</button>
      </div>
    </div>
  )
}

▲ 일단 멋지게 디자인부터 해서 집어넣어봤습니다. my-alert2는 예전에 썼던 class 재활용입니다.

이 UI를 보여주고 안보여주는 state를 하나 만들어주고 싶은데

useState를 쓰셔도 되지만 redux를 배운 기념으로 redux에 state를 만들어서 이용해보도록 하겠습니다.

true/false 값을 담으면 되겠죠? 근데 잘 생각해보면 어디에 저장할지가 문제입니다.

이미 state + reducer 한쌍을 만들었는데 여기 데이터 내부에 추가할까요?

 let state초기값 = [ {id : 0, name : '새로운상품', quan : 2}, true ];

이런 식으로요?

  • 가능합니다.

가능하지만 나중에 데이터를 꺼내쓸 때 문제가 생깁니다.

상품만 꺼내쓰고 싶을 땐 함께 저장해둔 true를 걸러야하는데 이 경우 매우 귀찮아지겠죠.

  • 그냥 일반 컴포넌트에선 useState()를 하나 더 쓰시면 됩니다.

  • redux store에선 reducer를 하나 더 쓰시면 됩니다.

그래서 state + reducer 세트를 하나 더 만들어서 여기에 UI의 true/false 값을 저장해봅시다.

reducer 하나 더 만들기

그냥 저번이랑 똑같이 만드시면 됩니다.

reducer에는 state 초기값 + state 수정하는 법을 넣으면 됩니다.

(index.js)

let state초기값 = [ {id : 0, name : '새로운상품', quan : 2} ];

function reducer(){
  저번시간 만든 리듀서
}

let alert초기값 = true;

function reducer2(state = alert초기값, 액션){
  return state
}

▲ 이렇게 했습니다. redux에서 state 만들기 어려운거 아닙니다.

그냥 state 초기값과 reducer 한쌍 만들면 끝입니다. 어찌보면 useState와 비슷합니다.

근데 여러분이 reducer를 하나 더 만드셧으면 store에 등록까지 하셔야 사용가능합니다.

등록하러갑시다.

(index.js)

import {createStore, combineReducers} from 'redux';

let state초기값 = [ {id : 0, name : '새로운상품', quan : 2} ];

function reducer(){
  저번시간 만든 리듀서
}

let alert초기값 = true;

function reducer2(state = alert초기값, 액션){
  return state
}

let store = createStore( combineReducers({reducer, reducer2}) )

▲ 여러분 밑에 createStore 하던 부분을 한줄 수정하면 됩니다.

combineReducers() 라는 함수를 하나 ‘redux’에서 import 해오시고

combineReducers() 안에 모든 리듀서를 object 형식으로 쭉 담으시면 끝입니다.

그럼 이제 방금 만든 state도 자유롭게 사용가능합니다.

사용해서 UI를 완성시키러 갑시다.

reducer 2개 만든거 사용하기

reducer 하나면 대충 꺼내쓰시면 되는데

두개 이상이면 store 데이터의 형식이 약간 달라지기 때문에 약간 주의하시면 되겠습니다.

진짜 이상해졌는지 확인하고 싶으면 state를props화() 해주는 함수 여기서

state를 한번 콘솔창에 출력해보시면 되겠습니다.

(Cart.js)

function Cart(props){
  return (
    <div>
      <Table>table부분</Table>
      <div className="my-alert2">
        <p>지금 구매하시면 20% 할인</p>
        <button>닫기</button>
      </div>
    </div>
  )
}

function state를props화(state){
  console.log(state);
  return {
    state : state,
  }
}
export default connect(state를props화)(Cart);

▲ state라는걸 출력해보면 이상한 자료가 나오죠?

아마 { reducer : [], reducer2 : true } 이런 식의 자료가 나올겁니다.

아까 reducer 합친거 그대로 출력됩니다.

그래서 reducer를 많이 만드셨으면 잘 골라쓰셔야합니다.

(Cart.js)

function Cart(props){
  return (
    <div>
      <Table>table부분</Table>
      { props.alert열렸니 === true
       ? (<div className="my-alert2">
            <p>지금 구매하시면 20% 할인</p>
            <button>닫기</button>
          </div> )
       : null
       }
    </div>
  )
}

function state를props화(state){
  console.log(state);
  return {
    state : state.reducer,
    alert열렸니 : state.reducer2 //리듀서2에 있는거 가져오는법
  }
}
export default connect(state를props화)(Cart);

▲ 그래서 이번에 state를 props로 저장할땐

state.reducer 라는 데이터는 state라고 저장하고 state.reducer2라는 데이터는 alert열렸니라고 저장했습니다.

물론 어떻게 저장하든 상관은없고 그냥 reducer가 여러개면

store에서 받아오는 데이터의 형식이 달라진다는 것만 참고하면 되겠습니다.

그리고 상단에서 props.alert열렸니라는 state가 true일 때만 이걸 보여주도록 코드를 짰습니다.

UI 만들기 끝!

그럼 여기서 문제

Q. 닫기버튼 누르면 UI가 사라지는 기능을 만들어보십시오

성공하면 펼쳐보십시오 (플리즈)

닫기 버튼을 누르면 state를 false로 변경만 하시면 됩니다.

그럼 UI 안보임ㅇㅇ

잉 근데 state를 false로 수정하고 싶은데 그건 redux store 안에 있죠?

그 데이터는 어떻게 수정합니까.

당연히..

  1. reducer2 내에 미리 데이터 수정하는 방법을 만들어두신 다음

  2. 버튼을 눌렀을 때 dispatch로 수정하시면 됩니다.

(index.js)

let alert초기값 = true;

function reducer2(state = alert초기값, 액션){
  if (액션.type === 'alert닫기'){
    return false
  } else {
    return state
  }
}

let store = createStore( combineReducers({reducer, reducer2}) )

(Cart.js)

function Cart(props){
  return (
    <div>
      <Table>table부분</Table>
      { props.alert열렸니 === true
       ? (<div className="my-alert2">
            <p>지금 구매하시면 20% 할인</p>
            <button onClick={ ()=>{ props.dispatch({type : 'alert닫기'}) }}>닫기</button>
          </div> )
       : null
       }
    </div>
  )
}

▲ 이렇게 했습니다.

그럼 버튼을 눌렀을 때 state가 false로 수정이 되고 모달창은 보이지 않습니다.

끝!

(참고) reducer 내에 if/else 문을 쓰실 때

마지막 else문에는 보통 state를 그대로 return 해줍니다.

그냥 아무 수정요청이 없을 땐 현재 state값을 퉤 뱉어라라는 소리입니다.


오늘의 교훈 : 이런 식으로 redux를 쓰면 안됩니다

코드 짜보니 알겠죠? 이거 UI 하나 만드는데 redux에 굳이 저장할 필요는 없습니다.

redux가 있다고 해도 redux에 state 저장할지말지는 선택입니다.

내가 이 state 데이터를 다른 컴포넌트에서 쓸 일이 없다면

간단하게 useState()로 Cart 컴포넌트 안에 간단하게 만드십시오.

반면 많은 컴포넌트들이 공유하는 값은 redux store안에 보관하십시오.

그것이 여러분 코드 양을 조금이라도 줄이는 길입니다.


세계최고로 쉬운 Redux 4 : dispatch할 때 데이터 실어보낼 수 있음

redux는 특유의 복잡함 때문에 미래엔 다른거 쓰지 않을까요

예를 들면 react effector, react recoil 같은 라이브러리가 있어서 이것도 한번 살펴보시길 바랍니다.

(recoil은 페이스북 개발자들이 만드는중)

설치하시면 store, reducer 함수 만들 필요 없이 그냥 useState 비슷한 문법을 써서 쉽게 state를 생성 & 수정 & 관리할 수 있습니다.

모든 컴포넌트에서요.

다만 이미 리덕스로 개발된 곳이많기도 하고 실제 유저도 가장 많기 때문에 리덕스나 계속 진행해봅시다.

Detail.js 페이지에서 장바구니 추가 버튼 기능을 만들고 싶습니다.

장바구니 버튼을 눌렀을 때 state에 새로운 값을 추가하고 싶은겁니다.

실은 그냥 reducer에서 데이터 수정방법 하나 만들고 버튼누를 때 dispatch하면 끝입니다.

여기에 더해서 dispatch 할 때 맘에드는 데이터를 실어보내는 법도 같이 알아봅시다.

오늘의 빠른 결론은

props.dispatch({ type : 어쩌구, payload : ‘안녕’ }) 이렇게 쓰시면 안녕이라는 데이터를 redux store까지 데이터를 실어보낼 수 있고

reducer 안에서 요청을 처리할 땐 액션.payload 라고 쓰면 보냈던 ‘안녕’ 데이터를 사용할 수 있습니다.

Detail 페이지에 장바구니 추가버튼 만들기

Detail 페이지에 주문하기 버튼을 만들어보시길 바랍니다.

근데 예전에 만들어놨던 버튼 HTML은 이미 있네요. 그럼 여기다가 기능개발좀 해보도록 합시다.

주문하기 버튼을 누르면 redux store에 있던 상품데이터들에 항목이 하나 추가되어야합니다.

20220427_184604

▲ 여기 state에 { id : 2, name : ‘멋진신발3’ } 이런 상품데이터를 하나 추가하자는 것입니다.

그럼 어떻게 코드를 짜야할까요?

30초 드릴테니 어떻게 할지 생각해보고 펼쳐봅시다.

주문하기 버튼을 누르면 redux 안의 state에 데이터를 추가하고 싶은 것입니다.

redux 환경에서는 데이터 수정하실 때는 무조건

  1. 데이터 수정하는 법을 미리 만들고

  2. dispatch 하시면 됩니다.

  3. 근데 dispatch할 때 { id : 2, name : ‘어쩌구’ } 데이터를 함께 실어보내면 됩니다.

솔직히 이쯤되면 혼자서도 잘 할 수 있으니 시간많으시면 직접 한번 해보시길 바랍니다.

아직 겁이 많은 분들은 하단을 참고하여 저와 함께 개발을 해보도록 합시다.


  1. reducer에다가 데이터 수정하는 방법부터 정의하시면 됩니다.

저번 시간에 reducer 함수에 if/else문으로 기능개발 열심히 했었죠?

이번엔 if문을 하나 더 추가해서 데이터 수정방법을 하나 더 정의하시면 됩니다.

이번엔 항목추가의 경우입니다.

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){

  if (액션.type === '항목추가') {

    let copy = [...state];
    copy.push(음 버튼누를 때 전송된 데이터?);
    return copy;

  } else if (액션.type === '수량증가'){
    let copy = [...state];
    copy[0].quan++;
    return copy
  } else if (액션.type === '수량감소'){
    let copy = [...state];
    copy[0].quan--;
    return copy
  } else {
    return state
  }
}

▲ 그래서 이번에 if 항목을 맨위에 추가했습니다.

별거없고 ‘항목추가’ 라는 요청이 들어오면

카피본을 생성해서 전송받은 데이터를 push (array에 추가) 해주세요 라고 쓴 것입니다.

전송받은 데이터는 아직 뭔지 모르니 한글만 채워넣도록 합시다.

  1. 이제 원할 때 dispatch 하면 됩니다.

주문하기 버튼을 누를 때 dispatch를 해보도록 합시다.

(Detail.js)

function Detail(props){
  return (
    <HTML많은곳/>
    <button onClick={()=>{ props.dispatch( {type : '항목추가'} ) }}>주문하기</button>
  )
}

▲ Detail 페이지의 버튼에 dispatch 기능을 추가했습니다.

그럼 이제 버튼을 누를 때마다 항목이 하나 추가됩니다.

근데 이거 dispatch 요청을 하실 때 { id : 2, name : ‘어쩌구’ } 데이터를 함께 실어보내보도록 합시다.

그리고 그 데이터를 state에 추가하는 겁니다.

생각만 해도 멋있겠죠?

(참고) props.dispatch를 그냥 쓰면 에러나고 꼭 밑에서 connect 해주셔야합니다.

function state를props화(state){
  console.log(state);
  return {
    state : state.reducer,
    alert열렸니 : state.reducer2
  }
}

export default connect(state를props화)(Detail)

▲ 저번에 redux에 있던 state를 갖다 쓰려면 컴포넌트 파일 하단에 이렇게 쓰라고 했죠?

그래서 이걸 똑같이 Detail.js 하단에도 복붙했습니다.

여러분도 이렇게 하시면 props.dispatch를 Detail.js에서 쓰실 수 있습니다.

(당연히 connect 함수도 위에서 import 해오셔야겠죠?)

  1. dispatch할 때 데이터를 실어보냅시다

redux store에 있던 데이터들과 유사한 데이터를 실어보내봅시다.

그럴려면 이렇게 작성하면 됩니다.

(Detail.js)

function Detail(props){
  return (
    <HTML많은곳/>
    <button onClick={()=>{

      props.dispatch({type : '항목추가', payload : {id : 2, name : '새로운상품', quan : 1} })

    }}>주문하기</button>
  )
}

▲ dispatch 하실 때 안에 type말고 payload라는 항목을 신설해주시면 됩니다.

(payload 말고 다른 이름 작명도 가능합니다. payload는 화물이라는 뜻이 있어서 자주 관습적으로 사용합니다)

이 항목에는 여러분이 redux store로 전달할 데이터를 작성가능합니다.

그래서 위처럼 적으면

버튼을 누를 때마다 {id : 2, name : ‘새로운상품’, quan : 1} 이런 데이터가 redux로 보내집니다.

  1. 그럼 전송한 데이터를 어떻게 출력하거나 갖다쓰나

redux store에서는 아까 보낸 데이터를 어떻게 받아서 쓸 수 있냐면

그냥 reducer 함수 안에서 액션.payload 라고 출력해보시면 아까 보낸 데이터가 뿅하고 들어있습니다.

끝입니다.

그럼 다 배웠으니 아까 의도했던 기능을 완성해보자면

(index.js)

let 기본state = [{id : 0, name : '멋진신발', quan : 2}];

function reducer(state = 기본state, 액션){

  if (액션.type === '항목추가') {

    let copy = [...state];
    copy.push(액션.payload);
    return copy;

  } else if (액션.type === '수량증가'){
    let copy = [...state];
    copy[0].quan++;
    return copy
  } else if (액션.type === '수량감소'){
    let copy = [...state];
    copy[0].quan--;
    return copy
  } else {
    return state
  }
}

▲ copy.push(액션.payload) 이 부분에 코드 한줄을 추가했습니다.

액션.payload라고 쓰시면 아까 dispatch할 때 실어보냈던 데이터가 나옵니다.

그걸 state에 추가하는 기능을 방금완성했습니다.

이제 버튼을 누르면 ‘항목추가’ 요청을 dispatch 하게되고 데이터를 실어보냅니다.

reducer에선 그 데이터를 state에 추가해줍니다.

그럼 장바구니 데이터에 항목이 하나 더 생기겠군요 성공!

(참고) 이쯤되면 눈치가 좋은 분들은 액션이라는 파라미터가 무슨 역할을 하는지 알 수 있을겁니다.

reducer(state, 액션) {} 이렇게 적은 부분에서의 액션이라는 파라미터는

그냥 dispatch() 소괄호 안에 들어있던 모든게 들어있습니다.

그래서 payload 말고도 여러가지 정보들을 함께 보낼 수도 있습니다.

근데 Cart 페이지 방문하면 추가한 데이터가 안보이는 이유

원래 여러분 지금 데이터 저장하고 수정하고 그러는건 사이트 새로 들어올 때마다 초기화가 됩니다.

근데 우린 Detail -> Cart 페이지로 이동했을 뿐인데 state가 초기화가 되는 이유는

그냥 원래 개발단계에서 미리보기 띄우실 때 페이지를 이동하면 페이지를 껐다 켠 것 처럼 초기화 됩니다.

사이트 나중에 발행해보시면 아마 제대로 동작할겁니다.

그게 싫으면 주문하기 버튼을 눌렀을 때

history.push() 등의 라우터 함수를 이용해서 페이지 이동을 강제로 시켜보십시오.

저렇게 라우터 함수를 이용해서 페이지 이동을 시키면 개발환경에서도 초기화가 되지 않습니다.

(Detail.js)

function Detail(props){

  let history = useHistory();
  return (
    <HTML많은곳/>
    <button onClick={()=>{

      props.dispatch({type : '항목추가', payload : {id : 2, name : '새로운상품', quan : 1} });
      history.push('/cart');

    }}>주문하기</button>
  )
}

▲ 그니까 이렇게 라우터를 써서 이동하면 state가 리셋되지 않습니다. 끝!

(이거 쓰려면 당연히 useHistory 훅이 상단에 import 되어있어야합니다.)

집에가서 해볼 것들

  • 지금은 주문하기 누르면 임시상품명을 state에 추가하고 있는데 현재 페이지에 있는 상품명을 추가하기

(상세페이지마다 주문하기를 누르면 각각 다른 상품들이 state에 추가되어야겠죠?)

  • 주문하기 버튼을 눌렀을 때 같은 상품이 이미 state에 존재하면 수량만 증가시켜주게 하고 싶으면?

  • 새로 추가한 상품은 +1 안되는 버그 해결은?


세계최고로 쉬운 Redux 5 : useSelector, useDispatch

redux store에 있던 state를 꺼내쓰려면

state를props화 해주는 함수 어쩌구를 파일 밑에 첨부하면 된다고 배워봤습니다.

하지만 코드가 길어서 짜증이 납니다.

그럴 땐 useSelector Hook을 쓰시면 조금 더 쉽게 꺼내올 수 있습니다.

잠깐 복습

redux 쓰는 이유는

  1. 모든 컴포넌트가 props 없이도 state 꺼내 쓸 수 있어서 씁니다

  2. state 버그 관리가 용이한데

state 수정하려면 수정방법을 reducer로 미리 정의해놓고

컴포넌트는 dispatch() 를 이용해서 state 수정해달라고 reducer에 부탁하는 형식으로 코드를 짜야한다고 배워봤습니다.

그래야 갑자기 state가 이상해지는 버그가 생겨도 범인을 reducer에서 쉽게 찾을 수 있으니까요.

redux 애초에 왜 쓰는지 이유를 까먹지 말도록 합시다.

useSelector 사용하는 법

(Cart.js 하단)

function state를props화(state){
  return {
    state : state.reducer,
    alert열렸니 : state.reducer2
  }
}
export default connect(state를props화)(Cart)

이런 코드를 짜야 state를 꺼내올 수 있다고 했는데

이거 지우고 이거랑 똑같은 기능을 하는 useSelector Hook을 써봅시다.

지울 때 원래 있던 export default Cart; 이 코드는 남겨두셔야합니다

import { useSelector } from 'react-redux';

function Cart(props) {
  let state = useSelector((state) => state )
  console.log(state.reducer)

  (생략)
}

이렇게 코드를 짜보도록 합시다.

  1. useSelector() 라는 함수를 import 해오시고

  2. useSelector() 안에 콜백함수하나만 적어봅시다.

그럼 그 자리에 redux state가 남는데 그걸 변수에 저장해서 쓰면 됩니다.

콘솔창에 출력해보시면 에러도 뜨겠지만 아무튼 진짜로 redux state가 뜹니다.

  • 콜백함수 안에는 파라미터 하나 입력가능한데 그건 자동으로 store (reducer 합친거)가 됩니다.

  • 그럼 (state) => state.reducer 이런 식으로 쓰시면 조금 상세하게 state를 원하는 것만 가져올 수 있겠네요

  • 아무튼 state 쉽게 가져오고 싶으면 쓰십시오

<tbody>
  {
   state.reducer.map((a,i)=>{
     return (
      <tr key={i}>
      <td>{ a.id }</td>
      <td>{ a.name }</td>
      <td>{ a.quan }</td>

▲ useSelector로 가져온 state로 Cart.js 안의 반복문을 바꾸려면 이렇게 하면 되겠네요.

이런거 에러나면 이젠 알아서 에러메세지 읽고 고치십쇼

useDispatch 사용하는 법

dispatch() 하고싶으면 useDispatch로 가져오면 됩니다

import { useSelector, useDispatch } from 'react-redux';

function Cart(props) {
  let state = useSelector((state) => state )
  let dispatch = useDispatch()

  (생략)
}

그럼 이제 props.dispatch()로 state 수정요청 날리던걸

dispatch() 만으로 간단하게 사용할 수 있습니다.

밑에서 props.dispatch() 하던 코드들을 dispatch() 로 고쳐봅시다.


장바구니 기능 완성하기 가이드

알아서 기능 완성하라고 했지만 안할까봐 & 어떻게 할지 감이 안잡힐까봐 준비했습니다.

새로운 리액트 개념은 없으니 프로젝트 완성하실 분들만 참고하시면 되겠습니다.

  • 메인페이지 상품 클릭시 각각의 상세페이지로 이동시키기

  • 장바구니 +/- 버튼 기능 완성하기

  • Detail.js 장바구니 추가 버튼을 누르면 진짜 상품명을 추가하기

  • 이미 있는 상품을 장바구니 추가 누르면 수량만 증가시키기

우리 쇼핑몰에 필요한 이런 기능들을 완성해볼겁니다.

  1. 메인페이지의 를 누르면 상세페이지 이동시키자

이거 어떻게 합니까. 그냥 router 시간에 배웠던거 활용하면 되겠죠?

history.push(‘/detail’) 이런거 쓰면 페이지 이동된다고 했으니

Card> 써있는 곳에다가 onClick 일 때 저거 동작시키면 되겠네요?

▼ 좋은 계획이지만 생각대로 되진 않습니다.

<Card onClick={()=>{ history.push('/detail/0') }} />

눌러도 안됩니다.

안되면 언제나 에러메세지나 구글 검색이 답이긴 하지만 라우터 이상하고 그런건 에러메세지도 잘 안띄워줍니다.

왜냐면 <컴포넌트>는 div가 아니기 때문에 onClick은 속성을 달아도 동작하지 않을 수 있습니다.

그럴 땐 그냥 Card> 컴포넌트를 정의한 곳으로 가서 div에 직접 onClick을 달아주시면 문제 해결입니다.

function Card(props){
  let history = useHistory();
  return (
    <div className="col-md-4" onClick={ ()=>{ history.push('/detail/0') } }>
      <어쩌구저쩌구/>
    </div>
  )
}

▲ 이렇게 하면 문제없이 잘 동작합니다.

근데 지금은 모든 컴포넌트를 클릭하면 /detail/0 으로 이동시키라고 하드코딩 해놨는데

이걸 약간 동적으로 바꾸면..

function Card(props){
  let history = useHistory();
  return (
    <div className="col-md-4" onClick={ ()=>{ history.push('/detail/' + props.shoes.id) } }>
      <어쩌구저쩌구/>
    </div>
  )
}

▲ /detail 뒷부분에 props.shoes.id를 더했습니다.

Card 컴포넌트에서 쓰는 props 중에 shoes라는 이름의 props,

그리고 거기 저장된 id 항목을 더해주세요 라고 썼습니다.

현재 사용중인 3개의 에 보내지는 props.shoes.id는 각각 0,1,2 이런 식으로 될테니

그럼 이제 각각 /detail/0, /detail/1 이렇게 다른 페이지로 이동시켜주겠군요.

뭔말 하는지 모르겠으면 위의 코드해석하려하지말고 안에 있는 props부터 콘솔창에 출력해봅시다.

  1. 장바구니 +/- 버튼 완성하기

장바구니에 있는 + 버튼을 누르면 지금 맨 위의 첫째 상품의 수량만 ++ 되고 있습니다.

고쳐보도록 합시다.

이런거 어떻게 고치면 되겠습니까.

redux를 사용하고 있다면 여러분이 고쳐야할 곳은 … 90% 확률로 reducer 내부입니다.

reducer를 까보니 이런 식으로 수량을 증가시키고 있네요.

(index.js)

let 초기값 = [
  {id : 0, name '멋진신발', quan : 2},
  {id : 1, name '멋진신발2', quan : 1},
];

function reducer(state = 초기값, 액션){
  if (액션.type === '수량증가') {
    let copy = [...state];
    copy[0].quan++;
    return copy
  }
  (이하 나머지 if문들)
}

▲ 수량증가라는 요청이 들어오면 그냥 0번째 데이터의 quan을 1 증가시킬 뿐입니다.

그래서 그랬군요.

copy[0].quan++ 라고 하드코딩하는 대신에

copy[방금 누른 +버튼 옆의 상품번호].quan++ 라고 적어주면 원하는 상품을 ++ 해줄 수 있겠네요.

이런건 어떻게할까요?


조금이라도 직접 해봐야 그냥 버튼을 누를 때 방금 누른 +버튼 옆의 상품번호를 redux store로 보내면 되겠네요

저번시간에 dispatch() 할 때 데이터를 실어보낼 수 있다고 했으니까..

  • 버튼을 눌러서 dispatch() 할 때 수정해야할 상품번호까지 함께 전달하면 되겠군요.

그래서 저는 이렇게 개발해봤습니다.

(Cart.js 안의 +/- button 있던 곳)

<button onClick={()=>{ props.dispatch( {type: '수량증가', 데이터 : a.id} ) }}> + </button>

▲ 버튼에서 dispatch할 때 {데이터 : a.id } 라는 오브젝트도 함께 전달되게 만들어놨습니다.

a.id는 그냥 버튼 주변에 있던 상품의 id입니다. (전체코드는 영상 6:18 참고)

Q. 데이터보낼 때 저렇게 한글로 작명해도 되나요?

A. 넹 요즘은 한글코딩이 대세입니다.

한글로 작성했으면 출력할 때도 한글쓰면 끝인데요뭐

그럼 이제 보낸 데이터를 가지고 아까 하려던거 이거

copy[방금 누른 +버튼 옆의 상품번호].quan++

이렇게 reducer 내부를 수정해봅시다.

(index.js)

let 초기값 = [
  {id : 0, name '멋진신발', quan : 2},
  {id : 1, name '멋진신발2', quan : 1},
];

function reducer(state = 초기값, 액션){
  if (액션.type === '수량증가') {
    let copy = [...state];
    copy[액션.데이터].quan++;
    return copy
  }
  (이하 나머지 if문들)
}

▲ 아까 [0]이라고 써있던 부분을 [액션.데이터] 라고 바꿨더니 이제 제대로 동작하는군요.

  1. 주문하기버튼 누르면 진짜 페이지 내의 상품을 장바구니에 추가하기

지금 Detail 페이지 내의 주문하기 버튼을 누르면 그냥 임시상품명이 장바구니state에 추가됩니다.

왜냐면 그냥 이것도 주문하기 버튼에 대충 하드코딩해놨으니까요.

진짜 데이터가 state에 추가되도록 코드를 약간 수정해봅시다.

이건 별거 아니기 때문에 그냥 진행해봅시다.

Detail.js 안에 있는 주문하기 버튼에 있던 코드를 이렇게 바꿨습니다.

<button onClick={

    props.dispatch( {type : '항목추가', 데이터 : {id: 찾은상품.id, name : 찾은상품.title, quan : 1}} );

}>주문하기</button>

성공입니다.

그럼 이제 주문하기를 누를 때 마다

찾은상품.id / 찾은상품.name이라는 실제 페이지내의 상품데이터가 redux store에 저장됩니다.

20220427_185408

▲ 근데 문제는 같은 상품을 주문하기를 여러번 누르면 계속 항목이 추가되는겁니다.

Grey Yordan 이라는 항목이 저렇게 추가되면 안될 것 같군요.

같은 상품이 이미 있으면 항목을 추가하는게 아니라 수량만 1 증가시키도록 해봅시다.

  1. 같은 상품을 계속 주문하면 항목추가 X 수량증가 O

이건 ‘항목추가’라는 dispatch요청할 때 문제가 있는거죠?

그럼 reducer에서 데이터 수정방법을 손봐주면 되겠군요.

(이게 약간 redux의 편한 점인데, 데이터 저장이 잘못되고 있으면 여러분이 수정할건 90% 확률로 reducer입니다.)

현재 reducer에다가 대충 짜놓은 코드는 이렇게 생겼습니다.

(index.js)

function reducer(state = 초기값, 액션){
  if (액션.type === '항목추가') {
    let copy = [...state];
    copy.push(액션.데이터)
    return copy
  }
  (이하 나머지 if문들)
}

▲ 이런 식으로 짜놓았던 것 같군요. 항상 {액션.데이터}를 state에 추가해주는 코드입니다.

그럼 여기다가 조건을 하나 더 추가하면 되는게 아닐까요?

“{액션.데이터}이거 안의 id를 기존 state에 있던 상품들의 id와 비교해서..

id가 같은게 이미 있다면 push() 하지말고 그 상품의 quan만 1 증가시켜주세요”

이런 코드를 추가하면 됩니다.

그냥 한글로 잘 써놓고 자바스크립트로 번역만 잘 해주면 되는거라 코드는 직접 짜보십시오.

자바스크립트 문법 모르는건 언제나 구글의 도움을 받을 수 있습니다.


답만보고 따라적기만 하면 시간아깝습니다

조금이라도 직접 코드 짜보는게 훨씬 기억에 오래남습니다.

(index.js)

function reducer(state = 초기값, 액션){
  if (액션.type === '항목추가') {
    만약에 (dispatch로 수신한 데이터의 id가 state안에 이미 있으면){
      state카피본[그데이터가몇번째있는지].quan++
    }
    그게 아니면 {
      let copy = [...state];
      copy.push(액션.데이터)
      return copy
    }
  }
  (이하 나머지 if문들)
}

▲ 이런 식으로 코드를 짤건데 일단 첫째 문장부터 자바스크립트로 번역해봅시다.

그니까 dispatch()로 전달된 데이터 { id : 0, name : ‘새로운상품’ } 여기 id가

state라는 변수에 있던 값 [ { id : 0, name : ‘상품1’ }, { id : 1, name : ‘상품2’ } ] 에 있는지 찾겠다는 겁니다.

특정 값이 array 안에 있는지 찾고 싶으면 자바스크립트 기본함수 중에 findIndex() 라는게 있습니다.

예를 들면 array 안에 1이 있는지 찾고 싶으면

 [2,3,1,4].findIndex((a)=>{ return a === 1 })

위의 예제처럼 사용하시면 됩니다.

a라는건 array 안에 있던 하나하나의 자료를 뜻하고

이게 1이랑 일치하면 a라는 자료가 몇번째 있는지를 퉤 뱉어줍니다.

(위의 예제는 3이라는게 그 자리에 남겠네요)

그럼 state라는 array 자료에 액션.데이터에 있던 id가 있는지 찾고 싶으면

 let 몇번째있니 = state.findIndex( (a)=>{ return a.id === 액션.데이터.id } )

이런 식입니다. a는 state 안에 있던 하나하나의 자료 {} 를 의미하고

이 자료안의 id를 === 액션.데이터.id와 비교하는겁니다.

그레서 맞는게 있으면 그게 몇번째인지를 이 자리에 남겨줍니다.

(그 숫자를 변수에 저장해서 쓰시면 됩니다)

자바스크립트로 번역합시다.

(index.js)

function reducer(state = 초기값, 액션){
  if (액션.type === '항목추가') {
    let 몇번째있니 = state.findIndex( (a)=>{ return a.id === 액션.데이터.id });

    if ( 몇번째있니 >= 0 ){
      let copy = [...state];
      copy[몇번째있니].quan++
    } else {
      let copy = [...state];
      copy.push(액션.데이터)
      return copy
    }
  }
  (이하 나머지 if문들)
}

▲ 이런 식으로 한글을 번역만 하면 기능이 완성됩니다.

Q. 잉 갑자기 코드가 길어져서 개어려워요 이해가 안가요

A. 원래 남의 코드 보고 분석하고 이해하려고 하는건 매우 어려운 일입니다.

직접 한글로 써놓고 번역하면서 기능을 완성해보시면 보다 이해가 쉬워집니다.

길어보이지만 별거아니고

“id가 이미 있으면 그거의 상품수량만 1 증가시켜주세요~”

라는 간단한 if/else 문을 추가한겁니다.

심심하시면 이거 말고도 다양한 기능을 추가해보십시오.

  • 새로 추가한 상품은 +1 안되는 버그해결하기

  • 주문하기 버튼 옆에 수량을 직접 입력할 수 있는 input을 하나 만들거나

  • 상품의 사이즈 정보를 저장할 수 있는 곳을 만들어서 사이즈를 선택할 수 있게 하거나

  • 장바구니 항목을 삭제할 수 있게 만들거나

여러분 정도면 10분도 안걸릴듯


리액트에서 자주쓰는 if문 작성패턴 5개

(리액트 강좌 전체 메뉴)

딱히 설명할 부분이 없어서 글로 진행합니다.

JSX 안에서 혹은 그냥 일반 코드작성시 if문을 쓸 때가 매우 매우많습니다.

지금까지는 삼항연산자만 주구장창 사용했는데 또 어떤 if문들을 쓸 수 있는지 맛만 보고 지나갑시다.

그냥 코딩 스타일적인 부분이기 때문에 그냥 이런게 있다고 알아두시면 되겠습니다.

  1. 컴포넌트 안에서 쓰는 if/else
function Component() {
  if ( true ) {
    return <p>참이면 보여줄 HTML</p>;
  } else {
    return null;
  }
}

컴포넌트에서 JSX를 조건부로 보여주고 싶으면 그냥 이렇게 씁니다.

우리가 자주 쓰던 자바스크립트 if문은

return () 안의 JSX 내에서는 사용 불가능합니다.

if (어쩌구) {저쩌구}

이게 안된다는 소리입니다.

그래서 보통 return + JSX 전체를 퉤 뱉는 if문을 작성해서 사용합니다.

(참고) 근데 이렇게 쓰시려면 else 생략이 가능합니다

function Component() {
  if ( true ) {
    return <p>참이면 보여줄 HTML</p>;
  }
  return null;
}

else와 중괄호를 하나 없애도 아까 코드와 똑같은 기능을 합니다.

왜냐면 자바스크립트 function 안에선 return 이라는 키워드를 만나면 return 밑에 있는 코드는 더이상 실행되지 않으니까요.

그래서 else가 필요없는 경우도 많으니 깔끔한 코드를 위해 한번 생략해보십시오.

if -> else if -> else 이렇게 구성된 조건문도 if 두개로 축약가능합니다. 한번 생각해보시면 됩니다.

  1. JSX안에서 쓰는 삼항연산자

영어로 간지나게 ternary operator 라고 합니다.

조건문 ? 조건문 참일때 실행할 코드 : 거짓일 때 실행할 코드

이 형식에 맞춰 쓰면 끝입니다.

function Component() {
  return (
    <div>
      {
        1 === 1
        ? <p>참이면 보여줄 HTML</p>
        : null
      }
    </div>
  )
}

그냥 JSX 내에서 if/else 대신 쓸 수 있다는게 장점이고 이전 강의들에서 자주 해본 것이니 설명은 스킵하도록 하겠습니다.

삼항연산자는 그냥 if와는 다르게 JSX 안에서도 실행가능하며 조건을 간단히 주고 싶을 때 사용합니다.

삼항연산자는 중첩 사용도 됩니다.

function Component() {
  return (
    <div>
      {
        1 === 1
        ? <p>참이면 보여줄 HTML</p>
        : ( 2 === 2
            ? <p>안녕</p>
            : <p>반갑</p>
          )
      }
    </div>
  )
}

else 문 안에 if/else 문을 하나 추가한 건데 제가 써놓고도 뭔소린지 모르겠군요

이렇게 나중에 읽었을 때 + 남이 읽었을 때 보기싫은 코드는 좋지 않습니다.

그냥 return문 바깥에서 if else 쓰신 다음 그 결과를 변수로 저장해놓고 변수를 저기 집어넣든 하십시오.

  1. && 연산자로 if 역할 대신하기

(문법) 자바스크립트에선 &&연산자라는게 있습니다.

“그냥 왼쪽 오른쪽 둘다 true면 전체를 true로 바꿔주세요~” 라고 쓰고싶을 때 씁니다.

true && false;
true && true;

맨 위의 코드는 그 자리에 false가 남고

밑의 코드는 true가 남습니다.

별거 아닙니다.

근데 자바스크립트는 이상한 현상이 있습니다.

&& 기호로 비교할 때 true와 false를 넣는게 아니라 자료형을 넣으면

true && '안녕';
false && '안녕';

맨 위의 코드는 ‘안녕’ 이라는게 그 자리에 남고

밑의 코드는 false라는게 남습니다.

이상한 현상입니다.

(&& 기호를 중첩해서 여러개 쓰면 && 사이에서 처음 등장하는 falsy 값을 찾아주고 그게 아니면 마지막 값을 남겨준다고 생각하면 됩니다)

이걸 리액트에서 약간 exploit 하면 if문을 조금 더 간략하게 쓸 수 있습니다.

if문 쓰실 때 이런 경우가 많습니다.

“만약에 이 변수가 참이면 <p></p>를 이 자리에 뱉고 참이 아니면 null 뱉고”

UI만들 때 이런 패턴의 if문 쓰는 경우가 90%입니다.

이걸 조금 더 쉽게 축약할 수 있습니다. && 연산자를 쓰면 됩니다.

function Component() {
  return (
    <div>
      {
        1 === 1
        ? <p>참이면 보여줄 HTML</p>
        : null
      }
    </div>
  )
}

function Component() {
  return (
    <div>
      {
        1 === 1 && <p>참이면 보여줄 HTML</p>
      }
    </div>
  )
}

그래서 위의 예제 두개는 동일한 역할을 합니다.

밑의 예제를 보시면 && 연산자로 조건식과 오른쪽 JSX 자료를 비교하고 있습니다.

이 때, 왼쪽 조건식이 true면 오른쪽 JSX가 그 자리에 남습니다.

왼쪽 조건식이 false면 false가 남습니다. (false가 남으면 HTML로 렌더링하지 않습니다)

아무튼 “만약에 이 변수가 참이면 <p></p>를 이 자리에 뱉고 참이 아니면 null 뱉고”

이런 상황에서 자주 쓸 수 있는 간단한 조건문입니다.

  1. switch / case 조건문

이것도 기본 문법인데 if문이 중첩해서 여러개 달려있는 경우에 가끔 씁니다.

우리도 redux에서 reducer 만들 때 그런 경우가 있었습니다.

if문으로 reducer 구분하던거 기억나시죠?

function reducer(state, 액션){

  if (액션.type === '수량증가'){
    return 수량증가된state
  } else if (액션.type === '수량감소'){
    return 수량감소된state
  } else {
    return state
  }
}

▲ (기억안날듯)

암튼 if문을 저렇게 연달아 여러개 썼습니다.

근데 자바스크립트의 switch 문법을 이용하시면 이렇게 쓸 수 있습니다.

function reducer(state, 액션){

  switch (액션.type) {
    case '수량증가' :
      return 수량증가된state;
    case '수량감소' :
      return 수량감소된state;
    default :
      return state
  }

}

▲ switch는 일단 어떻게 쓰냐면

  1. switch (검사할변수명){} 이거부터 작성하고

  2. 그 안에 case 검사할변수명이 이거랑 일치하냐 : 를 넣어줍니다.

이게 if문입니다.

  1. 그래서 이게 일치하면 case : 밑에 있는 코드를 실행해줍니다.

  2. default : 는 그냥 맨 마지막에 쓰는 else문과 동일합니다.

장점은 … if문 연달아쓸 때 코드가 약간 줄어들 수 있습니다. 괄호도 줄고요.

  1. 오브젝트 자료형을 응용한 enum

실은 이거 알려드리려고 글작성했습니다.

“경우에 따라서 다른 HTML을 보여주고 싶은 경우”

if문 여러개 혹은 삼항연산자 여러개를 작성해야겠죠? 근데 이렇게 작성할 수도 있습니다.

예를 들면 쇼핑몰에서 상품설명부분을 탭으로 만든다고 합시다.

그리고 경우에 따라서 상품정보 / 배송정보 / 환불약관 내용을 보여주고 싶은겁니다.

현재 state가 info면 <p>상품정보</p>

현재 state가 shipping이면 <p>배송정보</p>

현재 state가 refund면

<p>환불약관</p>

이런걸 보여주자는겁니다.

state를 만들어놓고 if문으로 state를 검사하는 문법을 써야할 것 같지만

이번엔 if문이 아니라 자바스크립트 오브젝트자료형에 내가 보여주고 싶은 HTML을 다 담습니다.

function Component() {
  var 현재상태 = 'info';
  return (
    <div>
      {
        {
           info : <p>상품정보</p>,
           shipping : <p>배송관련</p>,
           refund : <p>환불약관</p>
        }[현재상태]
      }

    </div>
  )
}

▲ 원래 JSX는 저렇게 오브젝트에 담든, 어레이에 담든 아무 상관없습니다.

암튼 이렇게 object 자료형으로 HTML을 다 정리해서 담은 다음

마지막에 object{} 뒤에 [] 대괄호를 붙여서 “key값이 현재상태인 자료를 뽑겠습니다” 라고 써놓는겁니다.

그럼 이제 현재상태라는 변수의 값에 따라서 원하는 HTML을 보여줄 수 있습니다.

만약에 var 현재상태가 ‘info’면 info 항목에 저장된 <p>태그가 보여질 것이고

만약에 var 현재상태가 ‘refund’면 refund 항목에 저장된 <p>태그가 보여지겠죠?

아주 간단하고 직관적인 if문이 완성되었습니다.

이제 if/else 몰라도 코딩이 가능하겠군요

혹은 더욱 간지나게 오브젝트를 변수로 저장해놓고 쓰셔도 무방합니다.

var 탭UI = {
  info : <p>상품정보</p>,
  shipping : <p>배송관련</p>,
  refund : <p>환불약관</p>
}

function Component() {
  var 현재상태 = 'info';
  return (
    <div>
      {
        탭UI[현재상태]
      }
    </div>
  )
}

▲ 뭔가 매우 깔끔해졌습니다.

심심하면 기존 코드들을 이렇게 깔끔하게 다시 바꿔보십시오.

(예제에선 귀찮아서 state가 아니라 var 변수를 만들었습니다)


state 변경함수 사용할 때 주의점 : async

(2022년 이후 리액트 18.0버전 batching 업데이트 나오면 이거 몰라도 됩니다)

자바스크립트의 sync / async 관련 상식

자바스크립트는 일반적인 코드를 작성하면 synchronous 하게 처리됩니다. 번역하면 동기방식 이런데..

뭔소리냐면 코드 적은 순서대로 윗줄부터 차례로 코드가 실행된다는 뜻입니다.

실은 거의 모든 프로그래밍 언어들은 무조건 위에서 부터 한줄한줄 실행됩니다.

예를 들어

console.log(1+1)
console.log(1+2)
console.log(1+3)

이런 코드는 그냥 위에서부터 한줄한줄 잘 실행됩니다. 그니까 콘솔창에 2, 3, 4 순으로 출력된다는 소리입니다.

뭔가 당연한 소리를 하고 있습니다.

자바스크립트는 이상한 함수들을 사용하면 asynchronous 하게 코드실행이 가능합니다. 번역하면 비동기적인데

ajax, 이벤트리스너, setTimeout 이런 함수들을 쓸 때 그런 현상이 일어납니다.

이런 함수들은 처리시간이 오래걸립니다. ajax를 예로 들면 인터넷 상황이 안좋으면 코드 실행이 오래걸리겠죠? 10초도 걸릴 수 있습니다.

그래서 ajax 요청하는 코드들은 순차적으로 실행되지 않고 완료되면 실행됩니다.

예를 들어

console.log(1+1)
axios로 get요청하고나서 console.log(1+2) 실행해주셈~
console.log(1+3)

이런 코드는 2가 출력되고 4가 출력되고 그 다음에 3이 출력됩니다.

3을 출력하는 코드가 asynchronous 처리를 지원하는 코드라 그렇습니다.

3을 출력할 때 오래걸리면 완료될 때 까지 잠깐 보류했다가 다른 코드를 먼저 실행시킨다는 소리입니다.

심지어 ajax요청이 0.00초 걸려도 4가 먼저, 그 다음 3이 출력됩니다.

물리적으로 잠깐 처리가 보류되어서 그렇습니다.

자바스크립트라는 언어의 특징이자 장점이라고 볼 수 있겠습니다.

(asynchronous 처리를 지원하는 함수들을 써야 이런 식으로 동작합니다)

리액트의 setState 함수 특징

리액트로 state 만들 땐 이렇게 합니다.

function App(){
  let [name, setName] = useState('kim')
}

그리고 이제 setName을 사용하시면 name이라는 state를 자유롭게 변경가능합니다.

setName(‘park’) 이런 식으로 하면 변경된다는 겁니다.

근데 문제는 setName() 같은 state 변경함수들은 전부 asynchronous (비동기적) 으로 처리됩니다.

그니까 setName()이 오래걸리면 이거 제껴두고 다른 밑에 있는 코드들부터 실행한다는 겁니다.

그래서 뭔가 예상치 못한 문제가 생길 수 있습니다.

예제 : 버튼을 누르면 2개 기능을 순차적으로 실행하고 싶습니다.

function App(){
  let [count, setCount] = useState(0);
  let [age, setAge] = useState(20);

  return (
    <div>
      <div>안녕하십니까 전 {age}</div>
      <button>누르면한살먹기</button>
    </div>
  )

}

위와 같은 코드가 있다고 칩시다.

여러분도 한번 그대로 따라적어보십시오. 그리고 하단처럼 기능개발해보십시오.

버튼을 누를 때마다

(1) count라는 state를 +1 해야합니다. (버튼누른 횟수 기록용)

(2) age라는 state도 +1 해야합니다.

(3) 근데 count 가 3 이상이면 더 이상 age라는 state를 1 더하지 말도록 코드를 짜십시오.

버튼 3번 이상 누르면 (count가 3 이상이면) 나이를 그만더하라는 기능입니다. 그니까 22살에서 멈춰야합니다.

이거 코드 어떻게 짜면 되죠?

버튼에다가 onClick 열고 짜면 될 것 같은데 빨리 짜보십시오.

저는 이렇게 짰습니다. 잘 되는 것 같지만 뭔가 이상합니다.

<button onClick={()=>{

  setCount(count+1);
  if ( count < 3 ) {
    setAge(age+1);
  }

}}>누르면한살먹기</button>
  1. 버튼을 누르면 count를 +1 해줍니다. 버튼누른 횟수 기록용이니까요.

  2. 그리고 만약에 count라는게 3회보다 적으면 age를 +1 해줍니다.

끝입니다. 그러면 아마 count라는게 2일 때 까지 실행해주니까

age는 20에서 22가 되면 더이상 증가하지 않고 멈추겠군요.

근데 23까지 증가하는데얌?

뭔가 이상합니다.

분명 count가 2일 때까지만 age를 +1 해주라고 했습니다.

count가 1일 때 age +1

count가 2일 때 age +1

count가 3이면 age +1 하지마 이런 코드니까요.

근데 지금은 count가 3일 때도 age +1를 해주고 있는 듯 합니다.

왜죠?

이유는 위에서 제가 말한 async라는 특징 때문에 그렇습니다.

state 변경함수는 async 하게 처리되는 함수기 때문에 완료되기까지 시간이 오래걸리면 제쳐두고 다음 코드를 실행해줍니다.

그래서 코드를 해석해보자면

① 버튼을 세번째 누르면 setCount(count+1); 이걸 실행해서 count를 3을 만들어줍니다.

② 근데 count를 3으로 만드는건 오래걸리니까 제껴두고 if ( count > 3 ) {} 이걸 실행합니다.

③ 이 때 count는 아직 2라서 if문 안의 setAge(age+1)이 잘 동작하고 있는겁니다.

이 모든 문제는 setCount()가 async 함수라서 그렇습니다.

async함수는 오래걸리면 제껴두고 다음 줄 코드부터 실행하니까 그렇습니다.

그래서 저렇게 state1 변경하고나서 state2를 변경하는 코드를 작성할 땐 가끔 문제가 생깁니다.

이걸 정확히 sync스럽게, 순차적으로 실행하고 싶을 때 해결책은 useEffect입니다.

useEffect를 잘 작성하면 특정 state가 변경될 때 useEffect를 실행할 수 있다고 하지 않았습니까.

알아서 해결해보십시오.


전 어떻게 해결했냐면

App 컴포넌트안에 useEffect를 만들어봅니다.

useEffect(()=>{

 }, [count])

useEffect는 컴포넌트가 렌더링/재렌더링될 때 실행되는 함수랬습니다.

근데 뒤에다가 [] 대괄호안에 state를 집어넣으면

state가 변경되면 이 코드 실행해주세요~ 라는 뜻으로도 사용가능합니다.

그래서 이거 쓰시면 아까 말했던 문제를 해결할 수 있습니다.

  1. count라는 state가 변경되고나서 2. age도 변경해주세요~ 이런 식으로 순차적으로 코드를 실행할 수 있다는 것입니다.

① 그래서 일단 버튼을 이렇게 변경했습니다.

<button onClick={()=>{

  setCount(count+1);

}}>누르면한살먹기</button>

count라는 것만 +1 되게 바꿨습니다.

② 그 다음에 나머지 age를 +1 하는 코드는 useEffect안에 개발해놨습니다.

useEffect(()=>{
  if ( count < 3 ) {
    setAge(age+1)
  }
 }, [count])

이런 식입니다.

그러면 useEffect는 count라는 state가 변경되고나서 실행이 되며

그럼 if문으로 count라는 state값을 제대로 의도대로 측정해볼 수 있는 겁니다.

③ 근데 문제는 useEffect 저렇게 써도 처음 페이지 로드될 때도 한번 실행이 되기 때문에 의도치 않은 버그가 발생할 수 있습니다.

그래서 처음 페이지 로드시 useEffect 실행을 막는 코드를 알아서 검색해서 적용하셔도 되고

아니면 count라는 state를 또 활용하셔도 됩니다.

count가 0일 때는 (페이지 처음 로드되었을 때는) 내부 코드를 동작시키지 않으면 될듯요

useEffect(()=>{
  if ( count != 0 && count < 3 ) {
    setAge(age+1)
  }
 }, [count])

이런 식입니다. count가 0이 아닐 때만 실행하라고 조건을 추가해줬습니다.

이제 버튼 누르면 22살까지만 잘 증가합니다.

문제해결! 이지만

  • 혹은 count와 age를 동시에 한 곳의 state에 array/object자료형으로 집어넣어놓아도 해결가능할 것 같고

  • 하나는 굳이 state로 만들지 않고 일반 var 변수로 만드는 것도 쉽게 해결할 수 있을듯요?

모든걸 state로 만들 필요 없습니다. 바뀌면 HTML 재렌더링이 필요한 변수들은 state로 만들라고 했지않습니까. 쓸데없는건 var 변수로 만듭시다.


성능잡기1. lazy loading / React devtools

기능구현 다음은 언제나 성능향상과 유지관리입니다.

리액트도 컴포넌트의 로딩속도 등을 향상시킬 수 있는 방법이 몇가지 존재합니다.

지금 간단한 사이트 만들 땐 전혀 체감이 안되지만 사이트가 크면 클 수록 꼭 필요한 내용입니다.

일단 오늘은 1. 익명함수/익명object 안쓰기 2. 레이아웃에 애니메이션 주지말기 3. 컴포넌트 lazy loading 하기

그리고 React Dev Tools 크롬 확장프로그램 설치해서 이것저것 테스트해보기를 알아봅시다.

  1. 함수나 오브젝트는 변수에 담아쓰는게 좋습니다.

리액트적인 개념은 아니고 그냥 메모리공간을 아낄 수 있는 JS 코딩 관습입니다.

function Cart(){
  return (
    <div style={ {color : 'red'} } ></div>
  )
}

▲ 이렇게 이름없는 콜백함수나 오브젝트를 대충 써넣지 말고

var 스타일 = {color : 'red'};

function Cart(){
  return (
    <div style={ 스타일 } ></div>
  )
}

▲ 이렇게 컴포넌트 바깥에 있는 변수에 저장해서 쓰라는 소리입니다. 함수도요.

(강의에선 function Cart 안에 넣는데 그거 아니고 바깥에 넣으세요)

왜냐면 컴포넌트가 재렌더링될 때 변수에 저장되지 않은 이름없는 object, function 류의 자료형들은

매번 새로운 메모리 영역을 할당해줘야하기 때문에 컴퓨터가 바빠질 수 있습니다.

그걸 방지하기 위해 컴포넌트 바깥에 저렇게 마련해두시면 되겠습니다.

class로 만든 컴포넌트는 class 안에 함수 집어넣는 공간 있으니 거기다 사용하시면 되고요.

  1. 애니메이션 줄 때 레이아웃 변경 애니메이션은 좋지않음

리액트만 적용되는건 아닌 전반적인 CSS 코딩 팁입니다.

레이아웃은 width, margin, padding, left right top bottom 이런 것들을 뜻하는데

자바스크립트나 transition을 이용해 레이아웃을 변경시키는건 브라우저 입장에서 큰 부담이 됩니다.

(왜 그런지는 CSS 렌더링 단계를 한번 찾아보시면 되겠습니다)

그래서 애니메이션을 넣어도 성능에 큰 지장이 없게 만들고 싶으면

transform, opacity 같은 CSS 속성을 이용해 애니메이션을 주길 바랍니다.

transform은 사이즈 변경, 좌표이동, 회전 전부 가능한 좋은 속성입니다.

  1. 컴포넌트 import 할 때 lazy 하게 import 하는 법

App.js 가보시면 import가 매우 많습니다. 나쁜건 아닙니다.

이게 웹앱 사이트들의 특징인데, App.js 라는 메인페이지 방문시 Detail, Cart 등을 모두 import 해옵니다.

그래서 많은 컴포넌트파일을 import 해오라고 써놓으면 사이트 초기 접속속도가 굉장히 느려질 수 있습니다.

(App.js 상단)

import React, {useState, useContext} from 'react';
import Detail from './Detail.js';
import Cart from './Cart.js'

▲ 잘 생각해보면 Detail, Cart 컴포넌트는 첫 페이지 방문시 import를 바로 해올 필요는 없습니다.

“Detail, Cart 컴포넌트들이 필요해질 때 import를 해주세요~” 라고 코드를 작성하실 수도 있습니다.

(App.js 상단)

import React, {useState, useContext, lazy, Suspense} from 'react';
let Detail = lazy( ()=>{ return import('./Detail.js') } );

(App.js 중단에 Detail 컴포넌트 쓰는 곳)
render (
  <Suspense fallback={ <div>로딩중입니다~!</div> }>
    <Detail/>
  </Suspense>
)

▲ 이렇게 바꾸시면 끝입니다.

  1. react 라이브러리에서 lazy, Suspense를 import 해오시고

  2. import Detail 하던걸 lazy 함수를 이용해 저렇게 바꿔줍니다.

  3. Suspense> 라는 컴포넌트로 Detail>을 감싸줍니다.

  4. fallback 속성엔 Detail> 컴포넌트 로딩 전까지 띄울 원하는 HTML을 적어줍니다.

그럼 이제 Detail> 컴포넌트가 필요해질 때 그제서야 import Detail 해줍니다.

그리고 Suspense>를 이용해서 로딩이 오래걸릴 때 띄워줄 안내문 같은걸 적어주실 수 있습니다.

지금은 그냥 div>하나만 띄워주는데 뭔가 회전하는 이미지라든지 그런걸 컴포넌트로 만들어서 집어넣으셔도 무방합니다.

React Dev Tools 리액트 개발자 도구 크롬 확장프로그램

미국 캘리포니아 부터 시작된 유서깊은 프로그램이며 설치하지 않으면 회사에 불화가 생기고 나쁜일이 가득합니다.

빨리 설치해야합니다. 크롬으로 구글에 react dev tools 검색하시면 바로 뜨는데

그거 설치하시면 이제 크롬브라우저에서 우클릭 - 검사 누르시면 리액트 관련 탭들도 생깁니다.

20220427_190348

이렇게 생겼습니다. 그럼 현재 페이지에 사용된 모든 컴포넌트들을 쭉 나열해 보여줍니다.

그리고 컴포넌트를 클릭해보면 거기서 사용중인 props, state, hook 이런 것들이 우측에 쫘르륵 표기됩니다.

  • 그래서 props가 잘 전해졌는지 확인

  • state가 잘 변하고 있는지 확인

  • 실시간 state, props 수정해보기

  • 시계모양 버튼을 눌러 해당 컴포넌트 렌더링을 잠깐 정지해보기

이런 것들이 가능합니다.

20220427_190413

그리고 또 다른 메뉴인 Profiler 탭으로 들어가시면

녹화 버튼(파란점) 을 눌러서 컴포넌트 렌더링 되는 속도를 측정해볼 수 있습니다.

  1. 버튼 누르고 2. 사이트 탐색하고 3. 버튼 다시 누르면 녹화 끝입니다.

20220427_190433

▲ 그럼 방금 탐색했던 과정에서 렌더링된 컴포넌트를 다 저렇게 기록해줍니다.

그럼 이걸 보고

  • 어떤 컴포넌트가 렌더링 시간이 젤 오래걸리는가

  • 쓸데없이 재렌더링 자주되는 컴포넌트가 있는가

  • 렌더링 필요없는 컴포넌트가 있는가

이런 것들을 찾아낼 수 있는 것이지요.

나중에 성능 최적화 하실 때 쓰시면 되고

컴포넌트를 적게, 코드를 깔끔하고 예쁘게 짜면 많이 켜볼 일이 없습니다.


성능잡기2. 쓸데없는 재렌더링을 막는 memo

컴포넌트는 컴포넌트와 관련된 state 혹은 props가 변경되면 항상 자동 재렌더링됩니다.

근데 가끔가다가 가만히 있어야할 컴포넌트들도 이유없이 재렌더링되는 경우가 있습니다.

특히 컴포넌트안에 컴포넌트가 여러개 있을 때,

부모 컴포넌트의 props 내용이 일부 변경되면 props를 전송받고 있는 자식 컴포넌트들도 전부 재렌더링됩니다.

props 변동사항이 없는 가만히 있던 자식 컴포넌트들도 싸그리 다 재렌더링되는데

이런거 그냥 냅두시면 사이트 구동 속도가 저하될 수 있겠죠?

memo라는 함수로 한번 해결해봅시다.

무슨 상황인지 예를 들어보기 위해 컴포넌트 3개 만들기

다음과 같은 코드를 아무 파일에나 추가하고 미리보기까지 해봅시다.

function Cart(){
  return (
    <Parent />
  )
}

function Parent(props){
  return (
    <div>
      <Child1/>
      <Child2/>
    </div>
  )
}
function Child1(){
  useEffect( ()=>{ console.log('렌더링됨1') } );
  return <div>1111</div>
}
function Child2(){
  useEffect( ()=>{ console.log('렌더링됨2') } );
  return <div>2222</div>
}

▲ 원하는 js 파일에 박아넣으시고

Parent/> 컴포넌트를 아무데나 div 안에 집어넣으면 실제 미리보기까지 가능하겠죠?

(저는 function Cart 에다가 Parent>를 표기했습니다.)

그리고 컴포넌트가 로드/재렌더링이 되면 useEffect로 콘솔창에 짧은 단어를 출력시키기까지 합니다.

(useEffect 쓰시려면 상단에 import 해오십시오)

근데 여기서 갑자기 props를 전달하고 싶은 충동이 생겼습니다.

20220427_190853

Cart 안에 있던 존박, 20이라는 데이터를

Child1/Child2 까지 props를 각각 전달하려면 어떻게하죠?

대충 이렇게 하시면 됩니다.

function Cart(){
  return (
    <Parent 이름="존박" 나이="20"/>
  )
}

function Parent(props){
  return (
    <div>
      <Child1 이름={props.존박} />
      <Child2 나이={props.나이} />
    </div>
  )
}

function Child1(){
  useEffect( ()=>{ console.log('렌더링됨1') } );
  return <div>1111</div>
}
function Child2(){
  useEffect( ()=>{ console.log('렌더링됨2') } );
  return <div>2222</div>
}

▲ props 저렇게 전해주면 됩니다. 끝

근데 핵심은 이런 식으로 컴포넌트를 만들 때

재렌더링이 비효율적일 수 있다는 겁니다.

본론이야기 : 재렌더링이 비효율적인 경우 여기 전송하고 있는 props를 변경하면 어떤 일이 일어날까요? ![20220427_191418](/assets/20220427_191418.png) ▲ 위 이미지처럼.. 그니까 존박에서 존박1로 변경해봅시다. 근데 이건 버튼을 만들려면 귀찮으니까 그냥 저번시간 설치한 리액트 개발자도구를 켜서 한번 실험해봅시다. ![20220427_191502](/assets/20220427_191502.png) ▲ 개발자도구에서 를 클릭하면 props를 수정해볼 수 있습니다. 이름을 존박1로 변경해봤습니다. 그러자마자 Child1><Child2 이 두개의 컴포넌트가 재렌더링됩니다. ![20220427_191541](/assets/20220427_191541.png) ▲ (렌더링될 때마다 콘솔창에 뭐 출력하라고 useEffect()를 썼으니 저렇게 나오는 것입니다) ![20220427_191608](/assets/20220427_191608.png) 근데 잘 생각해보면 내가 이름="존박" 이라는 props만 변경했는데 왜 존박이랑 아무 상관없는 Child2>도 재렌더링되는 것입니까. => 그건 그냥 Parent를 구성하는 state나 props가 변경되면 그것과 관련된 모든 컴포넌트를 다 재렌더링 시키니까요. 리액트 앱은 원래 그런식으로 동작합니다. 가만히 얌전히있는 컴포넌트의 재렌더링을 막고싶으면 memo() 함수를 이용하시면 됩니다. memo()로 컴포넌트 불필요한 재렌더링 막기 memo()는 "props 변경이 안된 컴포넌트는재렌더링 하지말아주세요~" 라고 쓰고싶을 때 사용합니다. 써보려면 'react' 라이브러리로부터 import 해오시면 됩니다. ``` import React, {useEffect, memo} from 'react'; function Cart(){ return ( ) } function Parent(props){ return (

<Child1 이름={props.존박}/> <Child2 나이={props.나이}/>
) } function Child1(){ useEffect( ()=>{ console.log('렌더링됨1') } ); return
1111
} let Child2 = memo(function(){ useEffect( ()=>{ console.log('렌더링됨2') } ); return
2222
}) ``` ▲ 1. 상단에서 memo를 import 해왔고 2. Child2라는 원하는 컴포넌트를 memo로 감쌌습니다. 이렇듯 그냥 memo라는걸로 감싸시면 아까처럼 "이 컴포넌트는 얘랑 관련있는 props가 변경이 될때만 제렌더링해주세요~" 라고 사용이 가능합니다. 쉽습니다. 끝! --- 뭐임 Child2 함수만드는데 갑자기 let 뭐임 자바스크립트는 함수만들 때 두가지 방법이 있습니다. ``` function 함수(){} let 함수 = function(){ } ``` 둘 중 밑의 방법을 이용했을 뿐입니다. 밑으로 만드셔야 memo로 감쌀 수 있습니다. 그럼 아까처럼 똑같이 이름을 존박1로 변경해보면 이제는 Child2 라는 존박과 관련없는 컴포넌트는 렌더링되지 않습니다. 아무튼 이게 컴포넌트가 너무 크거나 해서 잦은 재렌더링이 부담스러울 때 쓰는 방법입니다. Q. 어 그럼 좋은거니까 막써도 되겠네요? props가 매우 방대하고 큰 경우엔 오히려 손해일 수 있습니다. memo로 감싼 컴포넌트는 헛되게 재렌더링을 안시키려고 기존 props와 바뀐 props를 비교하는 연산이 추가로 진행됩니다. props가 크고 복잡하면 이거 자체로도 부담이 될 수도 있습니다. 쓸지말지 평가하려면 리액트 개발자도구에서 렌더링속도를 측정해볼 순 있으나 그것마저 귀찮으니 쪼그만한 사이트를 만들거나 컴포넌트 내부에 있는 HTML 양이 매우 적을 경우엔 memo는 쓰지말도록 합시다. --- ### PWA 셋팅해서 앱으로 발행하기 (모바일앱인척하기) 구글이 밀고있는 PWA라는게 있습니다. Progressive Web App이라는건데 이건 웹사이트를 안드로이드/iOS 모바일 앱처럼 사용할 수 있게 만드는 일종의 웹개발 기술입니다. 여러분 지금까지 강의 따라오면서 리액트로 모바일 App 처럼 동작하는 사이트 만들어놨죠? 모바일 앱처럼 스무스하잖아요. 그래서 이 웹사이트를 모바일 앱으로 발행해서 그냥 쓰자는겁니다. 근데 iOS, Android 앱으로 발행하는게 아니라 웹사이트 자체를 스마트폰 홈화면에 설치합니다. 그게 바로 PWA 입니다. 웹사이트를 PWA화 시키는게 뭐가 좋냐면 ![20220427_191749](/assets/20220427_191749.png) 1. 스마트폰, 태블릿 바탕화면에 여러분 웹사이트를 설치 가능합니다. (저거 설치된 앱 누르면 상단 URL바가 제거된 크롬 브라우저가 뜹니다. 일반 사용자는 앱이랑 구분을 못함) 2. 오프라인에서도 동작할 수 있습니다. service-worker.js 라는 파일과 브라우저의 Cache storage 덕분에 그렇습니다. 자바스크립트로 게임만들 때 유용하겠네요. 3. 설치 유도 비용이 매우 적습니다. 앱설치를 유도하는 마케팅 비용이 적게들어 좋다는 겁니다. 구글플레이 스토어 방문해서 앱 설치하고 다운받게 하는건 항상 매우 높은 마케팅비용이 듭니다. 근데 PWA라면 웹사이트 방문자들에게 간단한 팝업을 띄워서 설치유도할 수 있으니 훨씬 적은 마케팅 비용이 들고요. 그래서 미국에선 PWA를 적극 이용하고 있는 쇼핑몰들이 많습니다. PWA 만드는건 별거 없고 그냥 아무사이트나 파일 2개만 사이트 로컬경로에 있으면 브라우저가 PWA로 인식합니다. (그리고 HTTPS 사이트여야합니다) manifest.json과 service-worker.js 라는 이름의 파일 두개를 만드시면 됩니다. 하지만 기본 프로젝트를 npm build / yarn build 했을 경우 manifest.json 파일만 생성해줍니다. service-worker.js 까지 자동으로 생성을 원한다면 프로젝트를 처음 만들 때 애초에 npx create-react-app 프로젝트명 --template cra-template-pwa 이렇게 터미널에 입력하라고 합니다. Q. 잉 그럼 프로젝트 다시만들어야해요? A. 맞습니다. 1. 다른 폴더에 위 명령어를 이용해 프로젝트 새로 하나 만든 다음에 2. 기존 프로젝트의 App.js App.css index.js 이런 파일들을 새 프로젝트로 복붙하시면 됩니다. 여러분이 건드린 파일은 다 복붙하셈 근데 index.js 파일은 많이 바뀐점이 좀 있어서 차이점만 잘 복붙하시면 될듯합니다. 3. router, redux 이런 라이브러리를 설치했다면 그것도 새프로젝트에서 다시 설치하시면 됩니다. 그리고 파일들 중에 index.js 하단에 보시면 ``` serviceWorkerRegistration.unregister(); ``` 이 부분을 ``` serviceWorkerRegistration.register(); ``` 이렇게 바꾸시면 끝입니다. 그럼 이제 yarn build / npm run build 했을 때 아까 그 manifest.json과 service-worker.js 파일이 자동으로 생성됩니다. PWA 발행 끝! manifest.json / service-worker.js 파일 살펴보기 build 하고 나시면 build 폴더 내에 이 파일들이 있을겁니다. manifest.json 파일은 웹앱의 아이콘, 이름, 테마색 이런걸 결정하는 부분이라고 보시면 됩니다. 거기 안에 들어가는 내용들은 대략 이렇습니다. ``` { "version" : "여러분앱의 버전.. 예를 들면 1.12 이런거", "short_name" : "설치후 앱런처나 바탕화면에 표시할 짧은 12자 이름", "name" : "기본이름", "icons" : { 여러가지 사이즈별 아이콘 이미지 경로 }, "start_url" : "앱아이콘 눌렀을 시 보여줄 메인페이지 경로", "display" : "standalone 아니면 fullscreen", "background_color" : "앱 처음 실행시 잠깐 뜨는 splashscreen의 배경색", "theme_color" : "상단 탭색상 등 원하는 테마색상", } ``` 등 여러가지를 집어넣을 수 있습니다. 시간나시면 version, scope 항목에 대해서도 한번 찾아보시길 바랍니다. ▼ 그리고 이 파일은 웹앱에서 사용하는 모든 html 안에 이런 식으로 집어넣으셔야하는데 ``` ``` 근데 다행히도 그거 설치는 리액트가 알아서 해줬기 때문에 우린 건드릴게 없군요. service-worker.js 파일은 좀 설명하자면 긴데 여러분 카카오톡 앱같은거 설치할 때 구글플레이 스토어 가서 설치하죠? 그럼 카톡 구동에 필요한 이미지, 데이터들이 전부 하드에 설치됩니다. 그리고 카톡을 켜면 카톡 로고 같은 데이터를 카톡 서버에 요청하는게 아니라 하드에 이미 설치되어 있는걸 그대로 가져와서 씁니다. 이걸 흉내내도록 도와주는 파일이 바로 service-worker 라는 파일입니다. 이 파일에 설정을 잘 해주면 이제 여러분의 웹앱을 설치했을 때 어떤 CSS, JS, HTML, 이미지 파일이 하드에 설치될지 결정할 수 있습니다. 그럼 이제 다음에 앱을 켤 때마다 서버에 CSS,JS,HTML 파일을 요청하는게 아니라 Cache Storage에 저장되어있던 CSS,JS,HTML 파일을 사용하게 됩니다. (그럼 이제 오프라인에서도 사용이 가능해지는거죠) 근데 설정은 이미 되어있습니다. 그래서 우린 그냥 가만히 있기만하면 됩니다. 모든 HTML CSS JS 파일을 cache storage에 저장하도록 기본 셋팅이 되어있는데 간혹 저장해두기 싫은, 자주변하는 파일들이 간혹 있을 수 있습니다. 필요하면 하단 튜토리얼을 참고해서 수정해보도록 합시다. 그냥 쌩으로 service worker 파일을 만들고 싶다면 구글 공식 튜토리얼이나 크롬브라우저 권장 튜토리얼을 참고하십시오. service worker 제작에 필요한 문법이 따로 있고 그걸 학습하셔야합니다. (공식 튜토리얼) https://developers.google.com/web/fundamentals/primers/service-workers (샘플) https://googlechrome.github.io/samples/service-worker/basic/ 홈페이지 업데이트할 때마다 유저들이 올드한 JS 파일을 사용하진 않을까 걱정은 안하셔도 됩니다. 그리고 build할 때마다 JS,CSS,HTML 파일의 이름과 경로가 무작위로 바뀝니다. 사이트에 필요한 JS,CSS,HTML 파일명이 바뀌면 하드에 있는게 아니라 서버에 요청해서 새로 받아오니까 여러분이 파일을 서버에 올려서 배포할 때 마다 유저는 새로운 파일을 보게 될겁니다. #### 개발자도구로 PWA 디버깅하기 내가 build 했던 프로젝트가 PWA인지 아닌지 살펴보고 싶으면 일단 사이트를 호스팅받아 올리거나 아니면 .. (Github pages 이런 것도 됩니다) VScode 익스텐션중에 live server 이걸 검색해서 설치하신 뒤에 1. build 폴더를 에디터로 오픈하고 2. 거기 있는 index.html을 우클릭 - live server로 띄우기 누르면 됩니다. 아무튼 여러분의 사이트에서 크롬 개발자도구를 켜시면 Application 이라는 탭이 있습니다. 여기 들어가시면 PWA와 관련된 모든걸 살펴보실 수 있습니다. ![20220427_192315](/assets/20220427_192315.png) (내 사이트가 없으면 flipkart.com 이런 PWA 사이트 들어가서 따라해보시면 됩니다) Manifest 메뉴에선 manifest.json 내용들을 확인가능하고 Service Worker 메뉴에선 service-worker 파일이 잘 있는지, 오프라인에선 잘 동작하는지 테스트 가능하고 푸시알림 기능을 개발해놨다면 푸시알림도 샘플로 전송해볼 수 있습니다. Cache Storage 메뉴에선 service-worker 덕분에 하드에 설치된 CSS, JS, HTML 파일들을 확인할 수 있습니다. 캐시된 파일 제거도 가능합니다. 나의 PWA를 커스터마이징하려면 지금 PWA 발행이 쉽고 간단한 이유는 구글의 workbox 라는 라이브러리 덕분입니다. 이게 create-react-app 설치할 때 함께 설치되었기 때문입니다. 그래서 PWA 발행방식 같은걸 커스터마이징 하고싶으면 workbox 사용법을 익히셔야하는데 구글 직원들이 써놓은 개발문서 같은거 보면 매우 불친절하고 어렵습니다. 그래서 빠르게 커스터마이징 방법 하나만 알려드리겠습니다. Q. 하드에 설치할 파일 중에 HTML을 제외하고 싶다면? 이런 경우 많습니다. HTML 파일은 너무 자주 변해서 하드에 저장해놓기 싫다면 여길 수정하시면 됩니다. (근데 그럴거면 앱실행시 아무것도 안뜰꺼고 모바일 앱의 장점이 사라지는데얌) 여러분 프로젝트 폴더 내의 node_modules/react-scripts/config/webpack.config.js 파일을 찾으시면 됩니다. 거기 하단 쯤에 보면 이런 코드가 있습니다. ``` new WorkboxWebpackPlugin.GenerateSW({ clientsClaim: true, exclude: [/\.map$/, /asset-manifest\.json$/], }) ``` (▲구버전) ``` new WorkboxWebpackPlugin.InjectManifest({ swSrc, dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], ``` (▲신버전) 여기의 exclude라는 항목이 어떤 파일을 캐싱하지 않을건지 결정하는 부분입니다. 정규식으로 작성하는데 정규식과 일치하는 파일명을 제외합니다. 그래서 원하는 HTML 파일을 여기 등록하시면 끝입니다. ``` new WorkboxWebpackPlugin.GenerateSW({ clientsClaim: true, exclude: [/\.map$/, /asset-manifest\.json$/, /index\.html/], }) ``` 이거 말고도 "모든 .css로 끝나는 파일" "a라는 글자로 시작하는 파일" 이런 식으로 정규식으로 작성할 수도 있는데 그것은 정규식 문법을 잘 찾아보시면 되겠습니다. 근데 여러분 사이트가 페이스북, 인스타, 유튜브처럼 입장과 동시에 Ajax로 초기데이터들을 전부 받아오는 사이트라면 굳이 HTML 파일을 저렇게 할 필요는 없겠죠? 맞습니다. 쓸데없습니다. 아무튼 위처럼 코드를 추가하면 build 할 때 index.html 파일을 캐싱목록에서 제외해주게 됩니다. 오늘도 service-worker 쉽게 만들어주는 구글신님께 감사인사를 올리도록 합시다. 참고로 PWA는 구글 앱스토어에 올릴 수 있는 apk 파일로 변환할 수도 있는데 PWAbuilder 등을 이용하시면 됩니다. --- ### DB없이 데이터 저장하고싶으면 localStorage 새로고침하면 모든 state 데이터는 리셋됩니다. 왜냐면 당연히 새로고침하면 JS 파일들을 첨부터 다시 읽기 때문입니다. 그래서 장바구니 추가했던 항목들도 기본값으로 리셋되는 것이고요. 이게 싫다면 state 데이터를 전부 DB로 저장하거나 내가 서버나 DB 지식이 없다면 localStorage에 저장하도록 합시다. 브라우저에 몰래 정보를 저장하고 싶을 때 쓰는 공간입니다. ![20220427_192646](/assets/20220427_192646.png) ▲ 크롬개발자 도구에서 Application 탭 들어가시면 구경가능합니다. 사이트마다 5MB 정도의 텍스트,숫자 데이터를 저장할 수 있습니다. 유저가 브라우저 청소를 하지 않는 이상 영구적으로 남아있습니다. #### localStorage 문법 그냥 js 파일 아무데서나 다음 문법을 쓰시면 데이터 입출력을 하실 수 있습니다. ``` localStorage.setItem('데이터이름', '데이터'); localStorage.getItem('데이터이름'); localStorage.removeItem('데이터이름') ``` 차례로 추가, 읽기, 삭제 문법입니다. 오브젝트 자료형처럼 key : value 형태로 자료를 저장해줍니다. 진짜 저장되었는지 application 탭에서 확인해보십시오. localStorage에 오브젝트/어레이를 저장하려면 바로 저장할 수는 없습니다. 왜냐면 순전히 텍스트 자료만 저장가능하니까요. 굳이 저장하고 싶으면 따옴표를 쳐서 JSON이라는걸로 바꿔준 뒤에 저장하시면 문제없습니다. 그럼 텍스트 자료로 인식하니까요. ``` localStorage.setItem('obj', JSON.stringify({name:'kim'}) ); ``` JSON.stringify() 라는 고마운 함수에 오브젝트나 어레이를 집어넣으면 문자처럼 인식할 수 있는 JSON 자료로 바꿔줍니다. 대충 "{"name":"kim"}" 이렇게 바꿔줍니다. 이것이 JSON입니다. 그래서 그걸 저장하시면 문제없이 localStorage에 저장가능합니다. ``` var a = localStorage.getItem('obj'); var b = JSON.parse(a) ``` 하지만 데이터를 다시 꺼내면 JSON 그대로 나와서 오브젝트처럼 자료조작이 불가능합니다. 오브젝트자료형으로 취급할 일이 있으면 다시 JSON을 오브젝트로 바꿔주는 함수인 JSON.parse()를 쓰시면 되겠습니다. 최근본상품 UI 기능 만들기 새로운걸 만들어봅시다. 쇼핑몰같은데 보면 최근 본 상품이라는 UI를 표시해주는 곳이 있습니다. 우리도 이 성가신 UI를 만들어봅시다. 그러려면 유저가 어떤 id의 상품을 봤는지를 기록해야겠죠? 기록은 localStorage에 array 형식으로 하도록 합시다. 만약에 0번 1번 상품을 보았다면 [0,1] 이런 데이터가 저장되게 하면 되겠군요. 그리고 이 데이터를 원하는 페이지에서 UI로 보여주기만 하면 끝입니다. 알아서 개발해보십시오. --- 아직 코드 한줄 시작도 못한 분들을 위한 한글가이드 이런거 머릿 속으로 생각하면서 하려하지마시고 한글로 쭉 정리해놓고 시작하는게 아주 좋은 습관입니다. 이러면 버그같은거 생길 수가 없음 1. 누가 Detail.js 페이지에 들어가면 2. localStorage에 저장합니다. 끝! 이라고 하면 안되겠죠? id : 1번 상품 페이지를 3번 방문하면 [1,1,1] 이렇게 저장할겁니까? 저장하기전에 중복부터 체크해야합니다. 1. 누가 Detail.js 페이지에 들어가면 2. localStorage에 있는 데이터를 꺼냅니다. 3. 그럼 [0,1] 이런게 담긴 어레이가 나오겠죠. 4. 그럼 거기에 현재 페이지에 있는 상품번호 (예를 들면 현재 URL에 있는거)를 push합니다. 그럼 [0,1,1] 이런 식이 되겠죠. 5. 중복 숫자를 제거합니다. 구글찾아보셔도되고 Set 자료형 찾아보셔도 되겠습니다. 6. 중복 제거된 [] 어레이를 다시 localStorage에 저장합니다. 이제 예외처리를 하시면 됩니다. 예를 들면 3번에서 버그가 있을 수 있죠. 사이트 첫방문시 localStorage에 아무자료가 없어서 출력해도 아무것도 안나오는건 어떻게 해결할까요? 저의 가이드는 여기까지고 더 좋은 방법이 있다면 그렇게 하시고 아무튼 다 직접 만들어보신 후 다음시간에 만나도록 합시다. --- ### DB없이 데이터 저장하고싶으면 localStorage 2 (숙제해설) 어떤 유저가 상품을 조회하면 조회한 상품의 정보를 localStorage에 저장하기로 했습니다. watched 라는 이름으로 [1,2] 이런정보를 저장하기로요. 그럼 당연히 function Detail(){} 안에 useEffect 안에다가 개발해야겠죠? 1. 유저가 Detail 페이지로 들어가면 2. localStorage에 있는 데이터를 꺼내서 (데이터가 없는경우도 있겠죠) 3. 일단 꺼낸게 JSON "[ ]" 같으니까 따옴표제거 4. 꺼낸거에 [ ].push(현재상품의id) 5. [ ]에 중복이 있으면 제거 6. [ ]를 다시 localStorage에 넣음 이렇게 개발할겁니다. 혼자 코딩못하는 분들은 특히 한글로 미리 저렇게 써놓고 코드로 옮기는 연습부터 하십시오. 1. 2. 3. Detail 페이지 들어가면 localStorage 데이터 꺼내기 & parse 일단 데이터는 watched라는 이름으로 저장할거라서 watched라는 데이터를 꺼내봅시다. //Detail함수 내부 ``` useEffect( ()=>{ var arr = localStorage.getItem('watched'); arr = JSON.parse(arr); }, [] ); ``` useEffect() 뭔지는 설명안해도 되겠죠? 컴포넌트가 로드시 한번 실행될겁니다. 그 다음에 데이터꺼내고 따옴표를 제거해 JSON -> Object/Array로 변환해줍니다. 근데 여기서 데이터가 없거나 watched 항목이 없을 경우도 있겠죠? 그건 나중에 if문을 쓰든 해보시면 되겠습니다. 4. 5. 현재상품번호 추가하기, 중복제거하기 [ ] 이걸 꺼냈으니까 여기에 현재 보이는 상품의 번호를 추가하면 되겠습니다. 그 번호는 어딨냐고요? URL에 있네요. /detail/0으로 접속하면 0이라는 정보를 [ ] 에 추가하면 되겠습니다. 0이라는 정보는 let {id} = useParams()로 예전에 가져왔으니 id라는 변수가 바로 우리가 찾던 정보네요. //Detail함수 내부 ``` useEffect( ()=>{ var arr = localStorage.getItem('watched'); arr = JSON.parse(arr); arr.push(id); arr = new Set(arr); arr = [...arr]; }, [] ); ``` 그래서 세줄을 추가했습니다. new Set() 이라는곳 안에 어레이[] 를 집어넣으면 Set 자료형으로 바꿔줍니다. Set자료형은 어레이랑 똑같은데 중복을 자동으로 제거해줍니다. 그래서 Set으로 변환했다가 다시 []로 변환하는 코드를 작성해봤습니다. 그럼 자동으로 중복제거해줍니다. 매우편리함 6. localStorage에 다시 저장하기 저장하시면 됩니다. 근데 따옴표쳐서 JSON으로 저장하셔야겠죠? //Detail함수 내부 ``` useEffect( ()=>{ var arr = localStorage.getItem('watched'); arr = JSON.parse(arr); arr.push(id); arr = new Set(arr); arr = [...arr]; localStorage.setItem('watched', JSON.stringify(arr) ); }, [] ); ``` 그래서 맨 밑에 한줄을 추가했습니다. 이제 if문 등을 이용해서 watched 항목이 localStorage에 없을 경우를 처리해보십시오. 혹은 그냥 방문자들에게 전부 watched 항목을 localStorage에 하나 강제로 생성하는 것도 편리한 방법입니다. --- ### Node+Express 서버와 React 연동하기 이번 강의 빠른 요약 : 두개 합치는건 별거 아니고 서버는 유저가 메인페이지로 접속하면 리액트로 만든 html 파일을 보내주면 끝입니다. 서버만들 때 Express + MongoDB를 사용하면 JavaScript 만으로도 풀스택 개발을 체험해볼 수 있는 시대입니다. 여기다가 React를 더하고 싶은 분들이 많아서 준비했습니다. 리액트/서버개발 다시한번 개념정리부터 들어갑니다. 서버는 누가 html 파일 요청하면 보내주는 간단한 프로그램입니다. 진짜로 서버는 별거 아니고 어떤 고객이 codingapple.com으로 접속하면 거기맞는 html을 보내주는 기계일 뿐입니다. 임시 서버를 Node + Express로 쉽게 만들어봅시다. 1. 작업폴더를 에디터로 오픈 한 뒤에 터미널을 열어서 npm init 입력 후 뭐 선택하라고 하면 엔터 여러번 2. npm install express 입력 3. server.js 파일을 만드시고 다음 코드 작성 ``` const express = require('express'); const path = require('path'); const app = express(); const http = require('http').createServer(app); http.listen(8080, function () { console.log('listening on 8080') }); ``` 4. 터미널에서 node server.js 입력하시면 브라우저로 localhost:8080 접속시 서버가 뜹니다. 5. nodemon이라는게 있다면 nodemon server.js를 입력합니다. 그러면 서버 코드 바꿀 때마다 node server.js 로 다시 입력 안해도 됩니다. 리액트는 HTML을 이쁘게 만들어주는 툴입니다. 리액트는 대단한거 아니고 앱처럼 부드럽게 동작하는 HTML을 만들고 싶을 때 사용하는 툴입니다. ![20220427_193124](/assets/20220427_193124.png) ▲ 카톡 같은 모바일 앱 생각해보면 그런 앱들은 새로고침 없이 페이지 전환이 샥샥 되죠? 그런 식으로 새로고침할 필요없이 부드럽게 웹사이트를 만들고 싶을 때 리액트 쓰면 됩니다. 쌩자바스크립트로도 가능한데 코드 길어져서 귀찮아서 리액트쓰는 것임 어떤 식으로 개발하는지는 리액트 강좌 무료파트 몇개 들어보면 이해갑니다. 리액트를 사용하면 Nodejs 서버의 view 부분 기능개발이 필요없습니다. Nodejs 서버만들기 강의를 들으셨던 분들에게 말씀드리자면 우리 지금까지 ejs/html 파일 여러개 만들어서 페이지만들고 그런 짓거리를 하지 않았습니까. 그런거 개발은 리액트가 알아서 할 수 있으니 ejs파일은 버리셔도 됩니다. 애초에 리액트에서 페이지를 여러개 만들고 그걸 라우팅도 할 수 있습니다. 그니까 "누가 /list로 접속하면 글목록 html들 보여주셈" 을 서버 언어로 짰었는데 이걸 리액트에서도 똑같이 코드를 짤 수 있다는 겁니다. (근데 웹앱처럼 스무스하게 라우팅해줌 베리굿) 그래서 리액트 잘쓰면 서버에 작성할 코드는 "유저가 글리스트 요청하면 DB에서 데이터 뽑아서 전해주세요" 라는 데이터 입출력 API밖에 없습니다. 그리고 가끔 데이터 뽑아주기 전에 미들웨어로 "니 로그인했냐" 라고 물어보는거 그 정도 밖에 없겠군요. 하지만 언제나 리액트쓰는게 마냥 좋은 일은 아닙니다. 전환이 부드러운 웹앱만들고 싶으면 쓰는게 리액트라니까요 페이지 20개 30개 되는 사이트는 그냥 페이지로 나누는 것도 좋은 선택입니다. 리액트로만 만들면 코드 매우 더러워짐 리액트 프로젝트 만드는 법 지금 하고 있는 server.js 옆에 서브폴더로 리액트 프로젝트를 하나 만들어봅시다. 0. nodejs 극 최신버전이 아니면 에러납니다. 1. 터미널에 npx create-react-app 프로젝트명 을 입력합니다. 프로젝트명 자유작명하셈 다만 작명할 때 띄어쓰기하지마십시오. 2. 그럼 서브폴더에 리액트 프로젝트가 생성되는데 그걸 다시 에디터로 오픈합니다. 3. 리액트 문법으로 열심히 만들고 싶은걸 개발해봅니다. 개발시 코드를 미리보고 싶으면 npm run start 를 입력합니다. 4. 개발 완료되면 리액트 프로젝트 터미널에 npm run build를 입력하면 리액트 완성본 파일이 build 폴더내에 생성됩니다. 그럼 서버에서 보낼 준비 완료! ##### 리액트로 만든 HTML 전송하는법 리액트로 개발한 html파일을 고객에게 보내주면 그게 Nodejs 서버랑 리액트 합치는거 끝임 ![20220427_193209](/assets/20220427_193209.png) ▲ 리액트로 개발을 다 마친 후 npm run build 라는걸 하시면 build라는 폴더가 생기고 안에 html css js 파일이 생성됩니다. 그 중에 html 파일은 사진에 보이는 index.html 파일 딱 하나입니다. 왜냐면 기본적인 리액트 프로젝트는 SPA라는걸 만들어주는데 이게 뭐냐면 html 페이지가 하나이고 다른 페이지 이동은 자바스크립트를 이용해서 스무스하게 구현하는 웹앱을 말합니다. SPA는 기본적으로 HTML 파일 하나가 끝입니다. 이걸 서버에다가 이렇게 코드를 짜면 ``` (server.js) 어떤 놈이 메인페이지로 접속하면 저거 리액트로 build한 index.html 보내주셈 ``` 리액트와 Nodejs 서버 합치기 끝입니다. ![20220427_193243](/assets/20220427_193243.png) ▲ 예를 들면 폴더구조를 이렇게 만들었다고 칩시다. server.js 옆에다가 react-project라는 폴더명으로 리액트 프로젝트를 하나 만든겁니다. 그럼 안에 build한 파일들이 있겠죠? 이렇게 되어있는 경우 코드를 어떻게 짜냐면 (server.js에 추가) ``` app.use(express.static(path.join(__dirname, 'react-project/build'))); app.get('/', function (요청, 응답) { 응답.sendFile(path.join(__dirname, '/react-project/build/index.html')); }); ``` express.static이라는걸 쓰시면 특정 폴더안의 파일들을 static 파일로 고객들에게 잘 보내줄 수 있습니다. 그럼 아마 build 폴더 안의 css js img 파일들도 잘 사용할 수 있겠죠. 그리고 늘 하던대로 누군가 / 페이지로 접속하면 리액트로 만든 html 보내주는겁니다. 그럼 localhost:8080 으로 접속하시면 리액트 프로젝트가 나옵니다. 합치기 끝! (리액트 아는사람만) 리액트에서 라우팅을 담당하는 경우 리액트에서도 서버가 하던 라우팅을 대신 해줄 수 있습니다. react-router-dom을 설치하시면 됩니다. 그럼 누가 /list 로 접속하면 글목록 보여주고 /mypage 접속하면 마이페이지도 보여줄 수 있겠는데 그럼 nodejs 서버에서 라우팅이 필요없어지겠군요! 근데 리액트 라우팅으로 /list 페이지를 개발해놨는데 실제 localhost:8080/list 로 접속하면 아무것도 안뜹니다. 왜냐면 브라우저 URL창에 때려박는건 서버에게 요청하는거지 리액트 라우터에게 라우팅 요청하는게 아니기 때문입니다. 이걸 리액트가 라우팅하게 전권을 넘기고 싶다면 server.js 에 다음과 같은 코드를 밑에 추가하십시오. (server.js에 추가) ```` app.get('*',``` function (요청, 응답) { 응답.sendFile(path.join(__dirname, '/react-project/build/index.html')); }); ```` 별표 \* 라는 것은 모든 문자라는 뜻입니다. "고객이 URL란에 아무거나 입력하면 걍 리액트 프로젝트나 보내주셈"이라는 뜻인데 이렇게 하면 리액트 라우팅 잘됩니다. 끝! 리액트로 프론트엔드를 만들 경우 개발흐름 예를 들어서 DB에서 글목록 데이터를 꺼내서 HTML로 보여주고 싶은 경우 이전엔 뭐 글목록.html 페이지를 서버에서 보내줬을 텐데 리액트가 있을 경우 리액트는 index.html 페이지 하나로 개발하기 때문에 1. 서버는 누군가 /list로 GET요청을 하면 DB에서 데이터 꺼내서 보내준다고 API를 짜놓습니다. 2. 리액트는 글목록 페이지를 보여주고 싶으면 서버로 ajax GET요청을 보냅니다. 3. 그럼 데이터 받아오겠죠? 그걸 가지고 html에 집어넣든 맘대로 개발하면 됩니다. 리액트는 페이지가 index.html 하나만 있기 때문에 서버와의 통신은 거의 ajax로 진행하는 것만 잘 알아두면 됩니다. 세션이 있을 경우 회원정보 확인같은 것도 ajax로 알아서 됩니다. 그리고 nodejs 서버파일엔 const 여러개 모여있는 곳 하단에 ``` app.use(express.json()); var cors = require('cors'); app.use(cors()); ``` 이 코드 넣고 시작하셔야 리액트와 nodejs 서버간 ajax 요청 잘됩니다. #### 서브디렉토리에 리액트앱 발행하고 싶은 경우 지금 메인페이지가 리액트앱인데 그거 말고 /react 이렇게 접속하면 리액트 앱 보여주고 싶은 경우 어떻게 하냐면 (server.js) ``` app.use( '/', express.static( path.join(__dirname, 'public') )) app.use( '/react', express.static( path.join(__dirname, 'react-project/build') )) app.get('/', function(요청,응답){ 응답.sendFile( path.join(__dirname, 'public/main.html') ) }) app.get('/react', function(요청,응답){ 응답.sendFile( path.join(__dirname, 'react-project/build/index.html') ) }) ``` ▲ server.js 라우팅을 다 이렇게 바꿔주시고 (리액트프로젝트 내의 package.json) ``` { "homepage": "/react", "version": "0.1.0", ... 등 } ``` ▲ 리액트 프로젝트 내의 package.json에 homepage라는 항목을 여러분이 발행을 원하는 서브디렉토리명으로 새로 기입해주시면 됩니다. 그럼 방금 server.js 에서 /react 접속시 리액트 프로젝트보내고 / 접속시 일반 html 파일 보내라고 했으니 정말 그렇게 잘 됩니다. 끝! 서버앱과 리액트앱을 동시에 띄워서 개발을 진행하고 싶으면 그니까 리액트도 localhost로 미리보기 띄워놓고, 서버도 localhost로 미리보기를 띄워두고 개발을 진행하고 싶다면 리액트에서 package.json이라는 파일을 열어서 proxy라는 부분 설정을 서버 미리보기 띄우던 localhost:어쩌구 이걸로 설정해주면 됩니다. 그러면 리액트에서 서버로 ajax 요청 이런거 잘됨 https://create-react-app.dev/docs/proxying-api-requests-in-development/ 이걸 참고합시다.




© 2021.03. by yacho

Powered by github