[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강 혹은 구글검색을 참고합니다.
본격적으로 새로운 프로젝트 생성을 해보도록 합시다.
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 메뉴로 들어갑니다.
▲ 사이트에서 시키는대로 인스톨해주도록 합시다.
터미널켜고
npm install react-bootstrap bootstrap
입력해주시면 됩니다. 명령어 맨날 바뀌니까 사이트 들어가보셔야합니다.
yarn 으로 빠르게 설치하려면
yarn add react-bootstrap bootstrap
입력해주시면 됩니다.
때에 따라 특정 스타일을 사용할 때 Bootstrap CSS 파일을 요구하는 경우가 있습니다.
그럼 그냥 사이트에 있는 CSS 파일을 index.html 파일의 < head > 태그 안에 복붙해주시면 됩니다.
▲ 사이트 내에 이걸 찾아 복붙하시면 됩니다. 아까랑 같은 페이지의 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 예제들이 많이 보입니다.
▲ 마음에 드는 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>
React Router 2 : Link, Switch, history 기능
라우터의 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를 사용할 수 있습니다.
그러니 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버전 이상을 쓴다면
npm install node-sass 혹은 yarn add node-sass로 6버전 설치해주시고
node_modules 폴더랑 yarn.lock 혹은 package-lock.json 보이는걸 다 삭제하시고
터미널에서 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 {} 이렇게 안쪽에 쓰시면 띄어쓰기 셀렉터랑 같은 의미입니다.
굳이 이렇게 쓰는 이유는
셀렉터 해석이 쉽고
관련된 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 알림창이 사라지게 하려면?
저번시간에 만들어놓은 간단한 알림창 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 어떻게 만든다고 했습니까
UI 보이고 안보이고의 상태를 state로 저장해둠 (true/false 이런걸로)
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
}
)
}
▲ 세줄을 추가했습니다.
inputData라는 빈 state를 하나 만들었습니다.
input> 태그를 만들어서 거기 문자가 입력될 때마다 inputData라는 state에 저장되게 했습니다.
그리고 그 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 이런 요청을 통해 진행하고 있습니다.
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를 설치해서 이용합시다.
메인페이지에 더보기 버튼을 만들어보자
▲ 버튼을 하나 만들어봅시다. 그리고 이걸 누르면.. 상품 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개가 나오죠?
▲ 딱봐도 추가상품 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이라는 자료형입니다.
▲ 따옴표가 다 쳐있죠? 왜냐면 서버와 통신할 때는 텍스트만 전송할 수 있습니다.
그래서 텍스트럼 보이게 하기 위해서 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개를 가져오고
- 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 데이터를 괄호 벗겨서 여기 넣어주시고,
result.data라는 ajax 성공시 받아오는 데이터도 괄호 벗겨서 여기 넣어주세요
그리고 이걸 전부 [] 대괄호로 감싸서 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 전달은 어떻게 하죠?
▲ 이렇게 컴포넌트를 여러개 만들어놨는데
결론부터 말하자면 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로 전송하면 되겠죠뭐
▲ 근데 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) 일단 미리보기 띄워보셨을 때 콘솔창, 터미널에 에러만 안나면 됩니다.
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
- 별 문제가 없다면 이제 터미널에 build 명령어를 입력하십시오.
여러분이 작성하신 state, JSX, <컴포넌트>, props 이런 문법들은 브라우저가 해석할 수 없으니 그대로 배포할 수 없습니다.컴포넌트>
그래서 이런 문법들을 전통적인 CSS, JS, HTML 문법으로 바꿔주는 작업이 필요합니다.
이것을 컴파일 또는 build라고 합니다.
하시려면 여러분의 리액트프로젝트에서 터미널을 켠 후
npm run build
yarn build
둘 중 하나를 입력하시면 됩니다.
그럼 여러분 작업 프로젝트 폴더 내에 build 라는 폴더가 하나 생성됩니다.
▲ 여기 안에 index.html, css파일, js 파일이 전부 담겨있습니다.
여기 안에 있는 내용을 모두 서버에 올리시면 됩니다.
그럼 끝!
2. 근데 우린 무료 호스팅해주는 github pages에 올릴겁니다
간단하게 HTML/CSS/JS 파일을 무료로 호스팅해주는 고마운 사이트입니다.
일단 github.com에 들어가셔서 로그인까지 하십시오.
▼ 그 다음엔 우측 상단 + 버튼을 누르셔서 New Repository 버튼을 누르십시오.
▼ 그 다음엔 노란 곳에 다음과 같이 입력합니다.
▲ Repository name 은 꼭 왼쪽에 뜨는 여러분아이디.github.io 라고 입력하셔야합니다.
여러분아이디.github.io 말고 임의로 설정하시면 여러분 코딩인생 끝납니다.
그리고 README 파일 생성도 체크한 뒤에 생성해주시면 됩니다.
- Repository 생성이 되었다면 여러분 파일을 여기 올리시면 됩니다.
Repository 생성이 끝나면 repository로 자동으로 들어가질겁니다.
▼ 그럼 거기에 build 폴더 내의 파일을 전부 드래그 앤 드롭하시면 됩니다.
(주의) build 폴더를 드래그 앤 드롭하는게 아닙니다. build 폴더 안의 내용물이요.
드래그 앤 드롭하시고 초록버튼까지 눌러주시면 배포 끝입니다.
실수했다면 repository 과감하게 삭제하고 다시 만드시면 됩니다.
이제 10분 정도 후에 아까 여러분이 작성했던 https://여러분아이디.github.io 로 들어가시면 사이트가 보입니다.
이제 집가서 부모님께 자랑합시다.
(흔한 github pages 에러) 왜 사이트 주소로 접속했는데 404 페이지가 뜨죠?
10분 더 기다려보십시오.
ctrl + shift + r 을 이용해 새로고침 해보십시오.
혹은 repository 생성하실 때 여러분 아이디를 잘못적으신겁니다. 대소문자 틀리지말고 정확히 적으셔야합니다.
정확히 안적었으면 그냥 다시 하나 새로 만드시면 됩니다.
(추가) github이 좋아졌습니다.
이제 여러 repository를 동시에 호스팅해준다고합니다. 다른 HTML 페이지도 호스팅받고 싶으면
위에서 만든 내이름.github.io 라는 repository 잘 있죠? 그거 지우면 안됩니다.
남에게 자랑하고픈 새로운 프로젝트를 올릴 repository를 새로 만들어줍니다. 이름은 아무렇게나 하시면 됩니다.
그 repository에 아까처럼 드래그앤드롭으로 원하는 HTML CSS JS 파일을 업로드하고 확인까지 누릅니다.
repository setting 메뉴에 들어가서 Github pages 부분을 찾습니다.
▲4. 저기 source 부분을 None이 아니라 main 이런걸로 바꿔주고 저장하셈
- 그럼 끝입니다. 이제 님아이디.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를 전송해주려면 어떻게 해야합니까.
React.createContext() 로 범위 생성해주고
<범위 value={재고}> </범위> 이걸로 전송원하는 컴포넌트를 감싸고
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를 만들어봅시다.
그리고 애니메이션을 부여해보도록 합시다. 애니메이션은 그냥
일반 CSS 짜듯이 애니메이션 class를 하나 만들어두고
컴포넌트가 보여질 때 / 업데이트될 때 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 만드는 법을 떠올리시면 쉽습니다.
몇번째 버튼 눌렀는지를 state로 저장해둠
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 짤 때랑 똑같습니다.
애니메이션 주는 class를 CSS 파일에 열심히 짜서 제작해놓고
컴포넌트 등장/업데이트시 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로 바뀌게 했습니다.
- 컴포넌트가 로드될 때 스위치가 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)
(셋팅 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) 이제
이제 정말 셋팅 끝입니다. 이러면 정말 하위컴포넌틀이 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);
일단 장바구니 데이터사용을 원하는 컴포넌트.js 파일 밑에 function을 하나 만들어줍니다.
그 다음 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 데이터를 수정하고 싶을 때 그냥 대충 하지 않습니다.
reducer 함수를 만들고 그곳에 데이터 수정하는 방법을 정의해놓습니다.
그리고 원하는 곳에서 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를 수정하고 싶으면 특별한 방법이 필요합니다.
state 데이터의 수정방법을 index.js에다가 미리 정의해놓고 (일명 reducer)
index.js에게 수정좀 해달라고 부탁하셔야합니다.
안그러면 혼납니다. 수정하는 방법은 reducer로 만들어두는데 어떻게 하는지 알아봅시다.
데이터수정하는 reducer 만드는 법
데이터 수정하는 법은 reducer로 만들어줍니다.
reducer는 어려워보이지만 그냥 function 어쩌구로 시작하는 흔히보는 함수로 만드시면 됩니다.
근데 reducer는 function안에
state 초기값과
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>
);
따로 state 변수를 만들고
그걸 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);
▲ 방금 데이터가 수정되는 방법을 정의한 겁니다.
‘수량증가’라는 데이터 수정방법 이름을 하나 작명해주었습니다. (액션.type === 수정방법이름) 이런 식입니다.
if문 안에는 ‘수량증가’라는 요청이 들어올 경우 어떤 state를 퉤 뱉어낼지 정의한 것입니다.
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 데이터들은 이런 스텝에 따라 수정하시길 바랍니다.
reducer에 수정방법을 미리 정의하고
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 안에 있죠?
그 데이터는 어떻게 수정합니까.
당연히..
reducer2 내에 미리 데이터 수정하는 방법을 만들어두신 다음
버튼을 눌렀을 때 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에 있던 상품데이터들에 항목이 하나 추가되어야합니다.
▲ 여기 state에 { id : 2, name : ‘멋진신발3’ } 이런 상품데이터를 하나 추가하자는 것입니다.
그럼 어떻게 코드를 짜야할까요?
30초 드릴테니 어떻게 할지 생각해보고 펼쳐봅시다.
주문하기 버튼을 누르면 redux 안의 state에 데이터를 추가하고 싶은 것입니다.
redux 환경에서는 데이터 수정하실 때는 무조건
데이터 수정하는 법을 미리 만들고
dispatch 하시면 됩니다.
근데 dispatch할 때 { id : 2, name : ‘어쩌구’ } 데이터를 함께 실어보내면 됩니다.
솔직히 이쯤되면 혼자서도 잘 할 수 있으니 시간많으시면 직접 한번 해보시길 바랍니다.
아직 겁이 많은 분들은 하단을 참고하여 저와 함께 개발을 해보도록 합시다.
- 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에 추가) 해주세요 라고 쓴 것입니다.
전송받은 데이터는 아직 뭔지 모르니 한글만 채워넣도록 합시다.
- 이제 원할 때 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 해오셔야겠죠?)
- 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로 보내집니다.
- 그럼 전송한 데이터를 어떻게 출력하거나 갖다쓰나
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 쓰는 이유는
모든 컴포넌트가 props 없이도 state 꺼내 쓸 수 있어서 씁니다
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)
(생략)
}
이렇게 코드를 짜보도록 합시다.
useSelector() 라는 함수를 import 해오시고
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 장바구니 추가 버튼을 누르면 진짜 상품명을 추가하기
이미 있는 상품을 장바구니 추가 누르면 수량만 증가시키기
우리 쇼핑몰에 필요한 이런 기능들을 완성해볼겁니다.
- 메인페이지의
를 누르면 상세페이지 이동시키자
이거 어떻게 합니까. 그냥 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>
)
}
▲ 이렇게 하면 문제없이 잘 동작합니다.
근데 지금은 모든
이걸 약간 동적으로 바꾸면..
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개의
그럼 이제 각각 /detail/0, /detail/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]이라고 써있던 부분을 [액션.데이터] 라고 바꿨더니 이제 제대로 동작하는군요.
- 주문하기버튼 누르면 진짜 페이지 내의 상품을 장바구니에 추가하기
지금 Detail 페이지 내의 주문하기 버튼을 누르면 그냥 임시상품명이 장바구니state에 추가됩니다.
왜냐면 그냥 이것도 주문하기 버튼에 대충 하드코딩해놨으니까요.
진짜 데이터가 state에 추가되도록 코드를 약간 수정해봅시다.
이건 별거 아니기 때문에 그냥 진행해봅시다.
Detail.js 안에 있는 주문하기 버튼에 있던 코드를 이렇게 바꿨습니다.
<button onClick={
props.dispatch( {type : '항목추가', 데이터 : {id: 찾은상품.id, name : 찾은상품.title, quan : 1}} );
}>주문하기</button>
성공입니다.
그럼 이제 주문하기를 누를 때 마다
찾은상품.id / 찾은상품.name이라는 실제 페이지내의 상품데이터가 redux store에 저장됩니다.
▲ 근데 문제는 같은 상품을 주문하기를 여러번 누르면 계속 항목이 추가되는겁니다.
Grey Yordan 이라는 항목이 저렇게 추가되면 안될 것 같군요.
같은 상품이 이미 있으면 항목을 추가하는게 아니라 수량만 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문들을 쓸 수 있는지 맛만 보고 지나갑시다.
그냥 코딩 스타일적인 부분이기 때문에 그냥 이런게 있다고 알아두시면 되겠습니다.
- 컴포넌트 안에서 쓰는 if/else
function Component() {
if ( true ) {
return <p>참이면 보여줄 HTML</p>;
} else {
return null;
}
}
컴포넌트에서 JSX를 조건부로 보여주고 싶으면 그냥 이렇게 씁니다.
우리가 자주 쓰던 자바스크립트 if문은
return () 안의 JSX 내에서는 사용 불가능합니다.
이게 안된다는 소리입니다.
그래서 보통 return + JSX 전체를 퉤 뱉는 if문을 작성해서 사용합니다.
(참고) 근데 이렇게 쓰시려면 else 생략이 가능합니다
function Component() {
if ( true ) {
return <p>참이면 보여줄 HTML</p>;
}
return null;
}
else와 중괄호를 하나 없애도 아까 코드와 똑같은 기능을 합니다.
왜냐면 자바스크립트 function 안에선 return 이라는 키워드를 만나면 return 밑에 있는 코드는 더이상 실행되지 않으니까요.
그래서 else가 필요없는 경우도 많으니 깔끔한 코드를 위해 한번 생략해보십시오.
if -> else if -> else 이렇게 구성된 조건문도 if 두개로 축약가능합니다. 한번 생각해보시면 됩니다.
- 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 쓰신 다음 그 결과를 변수로 저장해놓고 변수를 저기 집어넣든 하십시오.
- && 연산자로 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 뱉고”
이런 상황에서 자주 쓸 수 있는 간단한 조건문입니다.
- 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는 일단 어떻게 쓰냐면
switch (검사할변수명){} 이거부터 작성하고
그 안에 case 검사할변수명이 이거랑 일치하냐 : 를 넣어줍니다.
이게 if문입니다.
그래서 이게 일치하면 case : 밑에 있는 코드를 실행해줍니다.
default : 는 그냥 맨 마지막에 쓰는 else문과 동일합니다.
장점은 … if문 연달아쓸 때 코드가 약간 줄어들 수 있습니다. 괄호도 줄고요.
- 오브젝트 자료형을 응용한 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>
버튼을 누르면 count를 +1 해줍니다. 버튼누른 횟수 기록용이니까요.
그리고 만약에 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가 변경되면 이 코드 실행해주세요~ 라는 뜻으로도 사용가능합니다.
그래서 이거 쓰시면 아까 말했던 문제를 해결할 수 있습니다.
- 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 크롬 확장프로그램 설치해서 이것저것 테스트해보기를 알아봅시다.
- 함수나 오브젝트는 변수에 담아쓰는게 좋습니다.
리액트적인 개념은 아니고 그냥 메모리공간을 아낄 수 있는 JS 코딩 관습입니다.
function Cart(){
return (
<div style={ {color : 'red'} } ></div>
)
}
▲ 이렇게 이름없는 콜백함수나 오브젝트를 대충 써넣지 말고
var 스타일 = {color : 'red'};
function Cart(){
return (
<div style={ 스타일 } ></div>
)
}
▲ 이렇게 컴포넌트 바깥에 있는 변수에 저장해서 쓰라는 소리입니다. 함수도요.
(강의에선 function Cart 안에 넣는데 그거 아니고 바깥에 넣으세요)
왜냐면 컴포넌트가 재렌더링될 때 변수에 저장되지 않은 이름없는 object, function 류의 자료형들은
매번 새로운 메모리 영역을 할당해줘야하기 때문에 컴퓨터가 바빠질 수 있습니다.
그걸 방지하기 위해 컴포넌트 바깥에 저렇게 마련해두시면 되겠습니다.
class로 만든 컴포넌트는 class 안에 함수 집어넣는 공간 있으니 거기다 사용하시면 되고요.
- 애니메이션 줄 때 레이아웃 변경 애니메이션은 좋지않음
리액트만 적용되는건 아닌 전반적인 CSS 코딩 팁입니다.
레이아웃은 width, margin, padding, left right top bottom 이런 것들을 뜻하는데
자바스크립트나 transition을 이용해 레이아웃을 변경시키는건 브라우저 입장에서 큰 부담이 됩니다.
(왜 그런지는 CSS 렌더링 단계를 한번 찾아보시면 되겠습니다)
그래서 애니메이션을 넣어도 성능에 큰 지장이 없게 만들고 싶으면
transform, opacity 같은 CSS 속성을 이용해 애니메이션을 주길 바랍니다.
transform은 사이즈 변경, 좌표이동, 회전 전부 가능한 좋은 속성입니다.
- 컴포넌트 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>
)
▲ 이렇게 바꾸시면 끝입니다.
react 라이브러리에서 lazy, Suspense를 import 해오시고
import Detail 하던걸 lazy 함수를 이용해 저렇게 바꿔줍니다.
Suspense> 라는 컴포넌트로 Detail>을 감싸줍니다.
fallback 속성엔 Detail> 컴포넌트 로딩 전까지 띄울 원하는 HTML을 적어줍니다.
그럼 이제 Detail> 컴포넌트가 필요해질 때 그제서야 import Detail 해줍니다.
그리고 Suspense>를 이용해서 로딩이 오래걸릴 때 띄워줄 안내문 같은걸 적어주실 수 있습니다.
지금은 그냥 div>하나만 띄워주는데 뭔가 회전하는 이미지라든지 그런걸 컴포넌트로 만들어서 집어넣으셔도 무방합니다.
React Dev Tools 리액트 개발자 도구 크롬 확장프로그램
미국 캘리포니아 부터 시작된 유서깊은 프로그램이며 설치하지 않으면 회사에 불화가 생기고 나쁜일이 가득합니다.
빨리 설치해야합니다. 크롬으로 구글에 react dev tools 검색하시면 바로 뜨는데
그거 설치하시면 이제 크롬브라우저에서 우클릭 - 검사 누르시면 리액트 관련 탭들도 생깁니다.
이렇게 생겼습니다. 그럼 현재 페이지에 사용된 모든 컴포넌트들을 쭉 나열해 보여줍니다.
그리고 컴포넌트를 클릭해보면 거기서 사용중인 props, state, hook 이런 것들이 우측에 쫘르륵 표기됩니다.
그래서 props가 잘 전해졌는지 확인
state가 잘 변하고 있는지 확인
실시간 state, props 수정해보기
시계모양 버튼을 눌러 해당 컴포넌트 렌더링을 잠깐 정지해보기
이런 것들이 가능합니다.
그리고 또 다른 메뉴인 Profiler 탭으로 들어가시면
녹화 버튼(파란점) 을 눌러서 컴포넌트 렌더링 되는 속도를 측정해볼 수 있습니다.
- 버튼 누르고 2. 사이트 탐색하고 3. 버튼 다시 누르면 녹화 끝입니다.
▲ 그럼 방금 탐색했던 과정에서 렌더링된 컴포넌트를 다 저렇게 기록해줍니다.
그럼 이걸 보고
어떤 컴포넌트가 렌더링 시간이 젤 오래걸리는가
쓸데없이 재렌더링 자주되는 컴포넌트가 있는가
렌더링 필요없는 컴포넌트가 있는가
이런 것들을 찾아낼 수 있는 것이지요.
나중에 성능 최적화 하실 때 쓰시면 되고
컴포넌트를 적게, 코드를 깔끔하고 예쁘게 짜면 많이 켜볼 일이 없습니다.
성능잡기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를 전달하고 싶은 충동이 생겼습니다.
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 저렇게 전해주면 됩니다. 끝
근데 핵심은 이런 식으로 컴포넌트를 만들 때
재렌더링이 비효율적일 수 있다는 겁니다.
본론이야기 : 재렌더링이 비효율적인 경우