CSS 상에서는 다음처럼 배경을 넣을 수 있다.
./이미지경로
// App.js
<div className="main-bg"></div>
// App.css
.main-bg {
height: 300px;
background-image: url('./banner.jpg');
background-size: cover;
background-position: center;
}
HTML 상에서는 CSS 방식과는 다르게 import를 해서 필요한 곳에서 변수를 사용할 수 있다.
import 작명 from './이미지경로'
// App.js
import Banner from './banner.jpg'
<div className="main-bg" style={{backgroundImage: 'url('+ Banner +')'}}></div>
👉 결과
HTML 상에서 이미지를 넣는 일은 상당히 번거롭다. 만약, 이미지 100개가 필요하다면 import를 100번 해야 하는 번거로움이 있다.
매번 import 하기 번거롭다면 public 폴더에 이미지를 보관하는 방법도 있다.
/이미지경로
🤔 서브경로에 사이트를 발행 시
/이미지경로
이런 식으로 사용하면 서브경로에 사이트를 발행했을 때 이미지의 모든 경로를 수정해야 할 수 있다. 따라서 다음과 같은 CRA 권장 방식을 사용하는 것이 좋다.<img src={process.env.PUBLIC_URL + '/이미지'>
Bootstrap grid를 활용하여 상품목록을 만들었다.
<Container>
<Row>
<Col>
<img/>
<h4></h4>
<p></p>
<p></p>
</Col>
</Row>
</Container>
👉 결과
(서버에서 데이터를 가져왔다고 가정하고 데이터를 state로 관리했다.)
코드가 너무 길고 복잡하다면 import와 export를 이용해 파일을 분리할 수 있다.
함수, 컴포넌트도 export가 가능하다.
내보내려면? export
let a = 10;
export default a
가져오려면? import
import 작명 from './data.js'
내보내려면? export { 변수1, 변수2 }
형식
let a = 10;
let b = 20;
export {a, b}
가져오려면? 작명을 할 수 없고, export한 변수명 그대로 import
import {a, b} from './data.js'
현재 데이터는 let data = [{}, {}, {}]
형식으로 저장되어 있는 것이다.
// data.js
let data = [
{
id : 0,
title : "CALONE 17",
content : "245 g / 8.6 oz classic candle",
price : 170000
},
{
id : 1,
title : "THÉ NOIR 29",
content : "50 ml / 1.7 fl oz eau de parfum",
price : 220000
},
{
id : 2,
title : "SHOWER GEL",
content : "250 ml / 8.5 fl oz hinoki",
price : 110000
}
]
export default data
// App.js
import data from './data'
let [products] = useState(data);
map()
다음처럼 props
로 전달해서 상품목록을 출력할 수도 있다. 하지만 이는 우리가 data가 3개 있다는 사실을 알고 있으므로 가능한 것이다.
만약, 데이터가 100개라면 컴포넌트를 100번 호출하는 것은 좋지 않은 방식이므로 map
을 이용해보자.
// App.js
<div className="App">
<Card products={products[0]} i={1} />
<Card products={products[1]} i={2} />
<Card products={products[2]} i={3} />
</div>
...
function Card(props) {
return (
<>
<Col>
<img src={`/${props.i}.jpg`} />
<h4>{props.products.title}</h4>
<p>{props.products.content}</p>
<p>{props.products.price}</p>
</Col>
</>
)
}
// App.js
<div className="App">
...
<Container>
<Row>
{products.map((item, i) => <Card products={item} i={i+1} key={item.id} />)}
</Row>
</Container>
</div>
👉 결과
하드코딩으로 채워넣었을 때랑 똑같이 출력되는 것을 볼 수 있다.
/detail: 상세 페이지를 보여주고,
/cart: 장바구니 페이지를 보여주도록 만들어 보자.
리액트 미사용 시
리액트(SPA)
index.html
)🤔 import 시 경로
- 내가 만든 js 파일들은 ./부터 시작한다.
- 경로가 없는 것은 대부분 설치한 라이브러리이다.
페이지 하나를 컴포넌트로 만들어 두는 것이 좋다.
// index.js
root.render(
<Provider store={store}>
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
</Provider>
);
// App.js
function App() {
let [products] = useState(data);
return (
<div className="App">
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand href="#home">LE LABO</Navbar.Brand>
<Nav className="me-auto">
<Nav.Link href="#home">Home</Nav.Link>
<Nav.Link href="#features">Cart</Nav.Link>
</Nav>
</Container>
</Navbar>
<Routes>
<Route path="/" element={
<>
<div className="main-bg" style={{backgroundImage: 'url('+ Banner +')'}}></div>
<br/>
<Container>
<Row>
{products.map((item, i) => <Card products={item} i={i+1} key={item.id} />)}
</Row>
</Container>
</>
} />
<Route path="/detail" element={<div>상세페이지</div>} />
</Routes>
{/* <Cart /> */}
</div>
);
}
👉 메인페이지
👉 상세페이지
// App.js
<Link to="/">홈</Link>
<br/>
<Link to="/detail">상세페이지</Link>
// App.js
import Detail from './Detail';
function App() {
...
return (
...
<Link to="/detail">상세페이지</Link>
...
<Routes>
<Route path="/" element={...} />
<Route path="/detail" element={<Detail />} />
</Routes>
);
}
// Detail.js
import React from 'react'
export default function Detail() {
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<img src="./1.jpg" width="100%" />
</div>
<div className="col-md-6">
<h4 className="pt-5">상품명</h4>
<p>상품설명</p>
<p>120000원</p>
<button className="btn btn-danger">주문하기</button>
</div>
</div>
</div>
)
}
👉 결과
useNavigate()
: 페이지 이동 도와주는 함수이다. 보통 다음처럼 변수에 담아서 사용한다.
let navigate = useNavigate();
Link
대신 사용할 수 있다.
<Nav.Link><Link to="/">Home</Link></Nav.Link>
// 이렇게
<Nav.Link onClick={() => navigate('/')}>Home</Nav.Link>
👉 결과
🤔
navigate(1)
,navigate(-1)
앞으로 한 페이지 이동, 뒤로 한 페이지 이동
wildcard(*
)를 통해 유저가 잘못된 경로로 접근했을 때 보여줄 페이지를 만들 수 있다.
<Route path="*" element={<div>404. 존재하지 않는 페이지</div>} />
👉 결과
장점
/about/member
접속 시 About
, Memeber
둘 다 보여짐)/about
이 회사정보 보여주는 페이지라고 할 때, 다음과 같은 페이지도 만들고 싶다면?
/about/member
/about/location
다음처럼 만들 수도 있지만,
<Route path="/about" element={} />
<Route path="/about/member" element={} />
<Route path="/about/location" element={} />
Nested Route를 이용할 수도 있다.
// App.js
function App() {
...
<Route path="/about" element={<About />}>
<Route path="member" element={<Member />} />
<Route path="location" element={} />
</Route>
...
}
function About() {
return (
<>
<div>회사정보</div>
<Outlet></Outlet>
</>
)
}
function Member() {
return (
<div>Member 페이지</div>
)
}
🤔
Outlet
Nested Routes의 element를 보여주는 곳은<Outlet>
이다.
따라서 Nested Route(About
) 안에 있는 HTML(Member
)을 보여주려면Outlet
을 써야 한다.
👉 결과
🤔 Nested Route의 용도
여러 유사한 페이지가 필요할 때 사용할 수 있다.
Nested Route를 활용해서 이벤트 페이지를 만들 수 있다.
// App.js
function App() {
...
<Route path="/event" element={<Event />}>
<Route path="one" element={<div>첫 주문 시 양배추즙 서비스</div>} />
<Route path="two" element={<div>생일기념 쿠폰받기</div>} />
</Route>
...
}
function Event() {
return (
<>
<h3>오늘의 이벤트</h3>
<Outlet></Outlet>
</>
)
}
위에서는 상세페이지를 만들긴 했지만, 아직 상품명, 정보, 가격 등의 정보를 채워넣지 않은 상태이다.
위의 정보들은 products
에 저장되어 있다. 즉, App
컴포넌트 → Detail
컴포넌트로 전송해서 사용할 수 있다.
상품이 3개이므로 상세페이지도 3개 필요 → 여러 개의 페이지를 만들고 싶을 때 URL 파라미터 이용하기!
/detail/0
→ 0번째 상품
/detail/1
→ 1번째 상품
/detail/2
→ 2번째 상품
// App.js
<Route path="/detai/:id" element={<Detail products={products} />} />
// Detail.js
export default function Detail(props) {
let {id} = useParams();
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<img src={`/${parseInt(id)+1}.jpg`} width="100%" />
</div>
<div className="col-md-6">
<h4 className="pt-5">{props.products[id].title}</h4>
<p>{props.products[id].content}</p>
<p>{props.products[id].price}</p>
<button className="btn btn-danger">주문하기</button>
</div>
</div>
</div>
)
}
🤔
useParams()
유저가 URL 파라미터에 입력한 것을 가져올 수 있다.
만약 자료의 순서가 변경되는 정렬버튼이 있다면 상세페이지에도 문제가 생길 것이다. 이는 어떻게 해결할 수 있을까? 👉 detail/0
접속 시 0번째 상품을 보여주는 것이 아닌, 상품 id
가 0
인 것을 보여주면 된다.
products
라는 상품데이터에는 id
라는 영구번호가 있다. 따라서 URL에 입력된(/:id
) 값과 영구번호(id
)가 같은 상품을 찾아서 데이터 바인딩하면 된다.
import { useParams } from 'react-router-dom'
export default function Detail(props) {
let {id} = useParams();
let find = props.products.find((product) => parseInt(product.id) === parseInt(id));
console.log(find);
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<img src={`/${parseInt(id)+1}.jpg`} width="100%" />
</div>
<div className="col-md-6">
<h4 className="pt-5">{find.title}</h4>
<p>{find.content}</p>
<p>{find.price}</p>
<button className="btn btn-danger">주문하기</button>
</div>
</div>
</div>
)
}
🤔
find()
array 자료 안에서 원하는 항목만 찾아올 수 있다. 다음처럼 쓰면 조건식에 맞는 자료를 찾아서 남겨준다.array자료.find(()=>{ return 조건식 })
find()
는 array 뒤에 붙일 수 있으며 return 조건식을 적으면 된다. 그럼 조건식에 맞는 자료를 남겨준다.find()
콜백함수에 파라미터를 넣으면 array 자료에 있던 자료를 뜻합니다. (위 코드에서는product
)product.id === id
라는 조건식을 썼다.array자료.id === url에입력한번호
일 경우 결과를 변수에 담아주므로{상품 1개}
가 남을 것이다.
❗ 참고로, arrow function에서 return과 중괄호는 동시에 생략이 가능하다.