ํ๋ก์ ํธ ํค๋ ธํธ ๐ผ
1. Nav animation
.line {
z-index: 1000;
width: 30px;
height: 3px;
margin: 7px 0px;
transition-duration: 0.5s;
background-color: #000;
}
.hamTopLine {
transform: translateY(10px) rotate(45deg);
transform-origin: center;
}
.hamMidLine {
opacity: 0;
}
.hamBtmLine {
transform: translateY(-10px) rotate(-45deg);
transform-origin: center;
}
}
ham
๋ฒํผ์ด ๋จ์ํ ์์นํด ์๋ ๊ฒ ์๋๋ผ ์ ์ ์ ์ก์
์ ๋ฐ์ํ์ฌ ์์ง์ด๋๋ก ํ๊ณ ์ถ์๋ค !
3๊ฐ์ ๋ผ์ธ ์ค ๊ฐ์ด๋ฐ ๋ผ์ธ์ opacity
๋ฅผ ์ฃผ์ด ์๋ณด์ด๊ฒ ์ฒ๋ฆฌํ๊ณ ์ต์๋จ, ์ตํ๋จ์ ๋ผ์ธ์ transform-origin: center
๋ฅผ ์ฃผ์ด ๊ฐ์ด๋ฐ๋ฅผ ๊ธฐ์ ์ผ๋ก ์์ง์ด๊ฒ ํด์ X๋ฒํผ์ ๋ง๋ค๋๋ก ํ๋ค.
2. Nav @keyframes
animation: modalShowRight 0.3s;
@keyframes modalShowRight {
from {
opacity: 0;
margin-right: -50px;
}
to {
opacity: 1;
margin-right: 0;
}
}
animation
์์ฑ์ ์ฌ์ฉํ์ฌ modalShowRight
์ ๋๋ฉ์ด์
์ ์ง์์๊ฐ์ 0.3์ด๋ก ์ง์ ํด ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
์ ์ฐ์ถํ๊ณ
keyframe
( from
๊ณผ to
)๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ์ผ๋ก ๋์ค๋ ํํ์ ์ ๋๋ฉ์ด์
์ ์ฐ์ถํ๋ค.
โ
์ ๋๋ฉ์ด์ ์์ด ๋ชจ๋ฌ์ฐฝ์ด ๊ทธ๋ฅ ๋ด์ ๋์ ์ ๋๋ฉ์ด์ ์ ๋ถ์ฌํด์ ๋ถ๋๋ฝ๊ฒ ๋ชจ๋ฌ์ฐฝ์ด ๋ด์ ๋ ์์ฑ๋ ์ฐจ์ด๊ฐ ๋ง์ด ๋ ๋ณด์๋ค ! ๊ด๋ จ css๋ฅผ ์ข ๋ ๊ณต๋ถํด๋ด์ผ ํ ๊ฒ ๊ฐ๋ค.
3. let token = localStorage.getItem('TOKEN')
localStorage
์ TOKEN
์ ์ ๋ฌด๋ฅผ ํ์ธํ์ฌ useNavigate
hook์ผ๋ก ๊ฒฐ๊ณผ์ ๋ง๋ ํ์ด์ง๋ก ์ด๋ํ๋ค.
4. config.js
export const BASE_URL = 'http://10.58.52.229:3000'
export const APIS = {
product: `${BASE_URL}/products`,
}
useEffect(() => {
fetch(`${APIS.product}/details/${productId}`)
.then(response => response.json())
.then(result => {
console.log(result)
setProductData(result.data)
})
}, [])
API
์ฃผ์๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋๋ค ๋ณด๋ ๊ทธ ๋๋ง๋ค ๊ฐ ํ์ด์ง์ fetch
๋ฅผ ์ฐพ์ ์ญ์ ํ๋ ค๋ ๋๋ฌด ๋ฒ๊ฑฐ๋ก์ ๋ค.
config.js
ํ์ผ์ src
ํด๋ ๋ด์ ๋ง๋ค์ด์ URL
์ ์ค์ ํ๊ณ ๊ฐ ์๋ํฌ์ธํธ๋ง๋ค ๋ฏธ๋ฆฌ ์์ฑํด๋์๊ณ ,
์ดํ ๋ณ๋์ด ์๊ธธ ์ config.js
ํ์ผ ๋ด์ URL
๋ง ์์ ํ๋ฉด ๋๋๋ก ํ๋ค.
5. if (!productData?. productPrice ) return null
useEffect
๋ด๋ถ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ ์
UI
๋ด๋ถ์ map
์ด๋ reduce
๊ฐ์ ๋ฉ์๋๋ฅผ ๋ง๋๋ฉด ๋ ๋๋ง์ด ์ค์ง๋๊ณ ์๋ฌ๋ฉ์ธ์ง๊ฐ ๋ด๋ค.
useEffect
๋ก ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ๊ฐ ๋ ๋๋ง ๋ค๋ก ๋ฐ๋ ค๋์
์์ง map
์ด๋ redudce
๋ฅผ ์คํํ ๋ฐฐ์ด์ด ๋ถ๋ฌ์์ง์ง ์์์์๋๋ฐ,
์ฒ์์๋
if (!productData) return null
๋ง ์์ฑํ์๋ค. ๊ทธ๋ฌ๋๋ ์ด๋ค ๋ถ๋ถ์ ํด๊ฒฐ์ด ๋๊ณ ํด๊ฒฐ์ด ๋์ง ์๊ณ ์๋ฌ๋ฉ์ธ์ง๋ฅผ ๋์ฐ๋ ์ผ๋ถ๊ฐ ๋จ์๋ค.
๊ทธ๋์ ?. (์ต์
๋ ์ฒด์ด๋ ์ฐ์ฐ์)
๋ฅผ ํ์ฉํด productData
๊ฐ์ฒด์ productPrice
ํ๋กํผํฐ์
์กด์ฌ ์ฌ๋ถ๋ฅผ ํ๋ฒ ๋ ์ฒดํฌํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํด์ ํด๊ฒฐํ๋ค.
?.
์ต์ ๋ ์ฒด์ด๋ ์ฐ์ฐ์
?.
์์ ํ๊ฐ ๋์์ดundefined
๋null
์ด๋ฉด ํ๊ฐ๋ฅผ ๋ฉ์ถ๊ณundefined
๋ฅผ ๋ฐํํ๋ค.
6. toLocaleString( )
product detail
ํ์ด์ง์ purchase
ํ์ด์ง์์ ์ํ ๊ฐ๊ฒฉ์ด๋ ์ด๊ณ ๋ฑ์ ๋ช
์ํ ๋
number type
์ผ๋ก ๊ณ์ฐ์ ํ๊ณ ๊ณ์ฐ์ ๋ง์น ๋ค toLocaleString()
๋ก ์ฌ์ฉ ์ธ์ด์ ๋ง๋ ํํ์ ํฌํจํด์ฃผ์๋ค.
number
์ ์ฌ์ฉ ์ 1,000 ์ฒ๋ผ ํ์ํ ๊ณณ์ ์ผํ๋ฅผ ์ฐ์ด์ค๋ค.
date
์ ์ฌ์ฉ ์ ๋ฌธํ๊ถ์ ๋ง๋ ์๊ฐํ๊ธฐ๋ฒ์ผ๋ก ๋ , ์, ์ผ, ์๊ฐ์ ๋ฆฌํดํด์ค๋ค.
array
์ ์ฌ์ฉ์array
์ ๋ด์ฉ์ ๋ชจ๋ ๋ฌธ์๋ก ๋ฐํํด์ ๋ณด๋ด์ค๋ค.
7. window.scrollTo(x์ขํ, y์ขํ)
onClick={() => {
const element = document.querySelector('.detailInformationBox')
if (element) {
const y = element.getBoundingClientRect().top - 100
window.scrollTo({ top: y, behavior: 'smooth' })
}
}}
์ํ ์ ๋ณด๋ ๋ฆฌ๋ทฐ๋ฅผ ํด๋ฆญํ์ ๋ ์ํ๋ ์์น๋ก ์คํฌ๋กค์ด ์ด๋ํ๋๋ก ๊ตฌํํ๊ณ ์ถ์๋ค !
window. scrollTo()
๋ก๋ง ์์ฑํ๋ฉด ๊ณ ์ ๋ y๊ฐ
์ ์ง์ ํ๊ธฐ ๋๋ฌธ์
์ปจํ
์ธ ์์ ๋ณ๋์ด ์์ ๋ ๋ง๋ค ์๋ก ์ง์ ํด์ค์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์์๋ค.
๊ทธ๋์ ํน์ ์์์ ๊ฐ์ ์ง์ ํ๊ธฐ ์ํด querySelector
๋ก ํน์ ํด๋์ค์ ํด๋นํ๋ ์์๋ฅผ ์ฐพ์ element
์ ๋ด์ ์ ์ธํ๊ณ
element.getBoundingClientRect().top
์ ์ด์ฉํด querySelector
๋ก ์ฐพ์ ์์์ ์๋จ ๊ฒฝ๊ณ๊ฐ
๋ทฐํฌํธ์ ์๋จ๊ณผ ์ผ๋ง๋ ๋จ์ด์ง ๊ฒ์ธ์ง๋ฅผ ์ง์ ํ๋ค.
๋ง์ง๋ง์ผ๋ก window.scrollTo()
์ ์์์ ์ ํ ๊ฐ์ top
์ ์ง์ ํ๊ณ
behavior: 'smooth'
๋ฅผ ํตํด ์คํฌ๋กค์ด ๋ถ๋๋ฝ๊ฒ ์ด๋ํ๋๋ก ํ๋ค.
8. img Carousel Box
<div className={`imgBox ${isCarousel ? '' : 'carousel'}`}>
<img
className="mainImg"
src={productData.mainThumbnailImage}
alt="productImage"
/>
<img
className="mainImg"
src={productData.mainThumbnailImage}
alt="productImage"
/>
</div>
//scss
.imgBox {
display: flex;
transform: translate(0px);
transition: 1s;
img {
width: 100%;
}
}
.carousel {
transform: translate(-500px);
}
.mainImg {
width: 500px;
flex: none;
object-fit: contain;
}
๋จผ์ imgBox
div ์์ carousel
๊ธฐ๋ฅ์ผ๋ก ๋์๊ฐ๊ฒ ํ ์ด๋ฏธ์ง๋ฅผ 2๊ฐ ๋์ดํ๋ค.
mockdata
๋ก ์์
ํ ๋๋ ๋ฐฐ์ด๋ก ๊ด๋ฆฌํด์ index
๋ก 2๊ฐ์ ์ด๋ฏธ์ง๋ฅผ ์์
ํ์๋๋ฐ,
๋ฐฑ์๋์ ํต์ ์ ํ๊ฒ ๋๋ฉด์ ์์ ๋์๋ค.
๊ธฐ๋ณธ imgBox
์ transform : translate(0px);
์ ์ฃผ์ด ๊ธฐ๋ณธ๊ฐ์ ์ฃผ๊ณ ๋ฒํผ์ ๋๋ ์ ๋
state
๋ณ๊ฒฝ์ ๋ฐ๋ฅธ carousel
์ด๋ผ๋ className
์ ์ถ๊ฐํ์ฌ translate
๋ฅผ ์๋ก ๋ถ์ฌํด x์ถ
์ผ๋ก ๋ฐ์ด์ฃผ์๋ค.
๊ธฐ๋ณธ class style
์ transition
์ ์ฃผ์ด ๋ถ๋๋ฝ๊ฒ ์ด๋ฏธ์ง๊ฐ ๋์ด๊ฐ ์ ์๋๋ก ํ๋ค.
์ฌ์ค ์ฒ์์๋ ์ด๋ฏธ์ง ๋์ด์ ๋ฐฐ์ด๋ก ๊ด๋ฆฌํด๋ณด๊ณ ์ถ์๋๋ฐ, ๋ค์์๋ ์ข ๋ ๋ฐฐ์ด๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๊ณ ์ถ๋ค.
9. TOKEN์ ๋ถ์ฌ๋ฐ์ ํ์์ ๋ฐฐ์ก์ง ์ ๋ณด ๋ฑ๋ก(POST), input value ๊ด๋ฆฌ
// purchase page
const [inputValue, setInputValue] = useState({
name: '',
phone: '',
address1: '',
address2: '',
memo: '',
// purchase ๋ด address component
const Address = ({
inputValue,
setInputValue,
}) => {
const { name, phone, address1, address2 } = inputValue
const handleUserInput = e => {
const { name, value } = e.target
setInputValue({ ...inputValue, [name]: value })
}
๋จผ์ purchase
ํ์ด์ง์ address
๋ชจ๋ฌ์์ ๋ฐ์ input value๋ฅผ ์ถ๋ ฅํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์
purchase
ํ์ด์ง์ inputValue
์ setInputValue
state๋ฅผ address
์์ ๋ฐ์์ ์ฌ์ฉํ๋ค.
address
๋ด๋ถ์์๋ inputValue
๋ฅผ ๋น๊ตฌ์กฐํ ํ ๋น์ ํตํด name
, phone
, address1
, address2
๋ก ๋ถํดํ๊ณ ์ด๋ฅผ ํตํด ๊ฐ๊ฐ์ ๊ฐ์ ์ ์ฅํ๊ณ ์ถ์ ํ๋๋ก ํ๋ค.
๋ง์ง๋ง์ผ๋ก handleUserInput
ํจ์๋ input์ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ํจ์์ธ๋ฐ,
e
๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ e.target
์ name
๊ณผ value
๋ฅผ ๊ตฌ์กฐ๋ถํดํ ๋น์ ์ด์ฉํด ์ถ์ถํ๋ค.
๊ทธ ํ ์ถ์ถํ ๊ฐ์ ๋ค์ setInputValue
ํจ์๋ฅผ ํธ์ถํ์ฌ inputValue
์ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋๋ฐ
์ด ๋ ์คํ๋ ๋ ๋ฌธ๋ฒ(์ ๊ฐ์ฐ์ฐ์) ๋ฅผ ์ฌ์ฉํ์ฌ ๋ณต์ฌํ [name]: value
๋ฅผ ํตํด ํด๋น ์
๋ ฅ ์์์ ๊ฐ์ ์
๋ฐ์ดํธ ํ๋๋ก ํ๋ค.
10. ๊ตฌ์กฐ๋ถํด ํ ๋น ๋ฐ ์ ์ฒด ๋์ state ๊ด๋ฆฌ
const [agreeList, setAgreeList] = useState({
isInfoAgree: false,
isUseAgree: false,
})
const { isInfoAgree, isUseAgree } = agreeList
const isAllChecked = Object.values(agreeList).every(list => list === true)
const handleAgree = name => {
setAgreeList(prev => ({ ...prev, [name]: !prev[name] }))
}
const handleAllCheck = () => {
if (isAllChecked) {
setAgreeList(prev => ({ ...prev, isInfoAgree: false, isUseAgree: false }))
} else {
setAgreeList(prev => ({ ...prev, isInfoAgree: true, isUseAgree: true }))
}
}
๋์ผํ ์ฝ๊ด์ ๋ํ ๋์๊ฐ ์ข์ธก ํ๋จ๊ณผ ์ฐ์ธก ์๋จ ๋๋ฒ์ ๊ฑธ์ณ ์ถ๋ ฅ๋์ด์๋ค.
๋ฐ๋ผ์ ์ข์ธก์ ๋์์ ์ฐ์ธก์ ๋์๊ฐ ์ฐ๋๋์ด ๊ด๋ฆฌ๋๋๋ก ํ๋ฉด์, ๊ฒฐ๊ณผ์ ์ผ๋ก 4๊ฐ์ ๋์๊ฐ ๋ชจ๋ ์ฒดํฌ๋์์ ๋
์ ์ฒด ๋์ ์ฒดํฌ๋ฐ์ค์ ์ฒดํฌ๊ฐ ์๋์ผ๋ก ๋ค์ด์ค๊ฒ ํ๊ณ ,
์ ์ฒด ๋์ ์ฒดํฌ๋ฐ์ค๋ฅผ ๋๋ฅด๋ฉด ๋ชจ๋์ ์ฒดํฌ๊ฐ ๋ค์ด์ค๊ฑฐ๋ ๋ชจ๋ ์ฌ๋ผ์ง๊ฒ ํ๊ณ ์ถ์๋ค.
๋จผ์ agreeList
state๋ฅผ isInfoAgree
์ isUseAgree
๋ก ๊ฐ์ฒด ํํ๋ก ๊ตฌ์ฑํ๋ค.
๊ทธ๋ฐ ๋ค agreeList
๋ฅผ isInfoAgree
์ isUseAgree
๋ก ๊ตฌ์กฐ๋ถํด ํ ๋น์ ํด ์ฌ์ฉํ๊ธฐ ํธํ๊ฒ ๋ง๋ค์๋ค.
isAllChecked
๋ Object.values(agreeList)
๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด๊ฐ์ ๋ฐฐ์ด๋ก ๋ณํํ๊ณ
every
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ list ๋ด์ ๋ชจ๋ ๊ฐ์ ์ฃผ์ด์ง ์กฐ๊ฑด์ธ true
์ ๋ถํฉํ๋ ์ง ์ฒดํฌํ์ฌ ์ ์ฅํ๋๋ก ํ๋ค.
handleAgree
๋ name์ ๋งค๊ฐ๋ณ์๋ก agreeList
๊ฐ์ฒด ๋ด์ ๊ฐ๊ฐ์ ์์ฑ์ ์ถ์ ํ์ฌ ์ฒดํฌ๋ฐ์ค์ ์ํ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค.
๋ง์ง๋ง์ผ๋ก handleAllcheck
๋ isAllchecked
์ ๋ฐํ๊ฐ์ ํ์ธํ์ฌ
๋ง์ฝ true
์ธ ์ํ๋ผ๋ฉด ๋ชจ๋ false
๋ก ๋ณํํ์ฌ ๋ชจ๋ ์ฒดํฌ๋ฐ์ค๋ฅผ ๋๋๋ก ํ๊ณ
๋ง์ฝ false
์ธ ์ํ๋ผ๋ฉด ๋ชจ๋ true
๋ก ๋ณํํ์ฌ ๋ชจ๋ ์ฒดํฌ๋ฐ์ค๋ฅผ ํค๋๋ก ํ๋ค.
Object.values()
: ์ ๋ฌ๋ ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด๊ฐ ๊ฐ์ง๋ ์์ฑ์ ๊ฐ๋ง์ผ๋ก ์ด๋ฃจ์ด์ง ๋ฐฐ์ด์ ๋ฆฌํดํ๋ค..every
: ๋ฐฐ์ด ๋ด ๋ชจ๋ ์์๊ฐ ์ฃผ์ด์ง ํ๋ณ์์ ์ถฉ์กฑํ๋์ง ํ์ธํ์ฌboolean
๊ฐ์ ๋ฐํํ๋ค.
11. reduce ๋ฉ์๋๋ก ์ด ๊ธ์ก ๊ณ์ฐ ๋ฐ ๊ด๋ฆฌ
const totalPrice = orderList.reduce(
(acc, cur) => acc + cur.itemQuantity * cur.itemPrice,
0
)
orderList
๋ ๊ตฌ๋งคํ ์ํ๋ค์ ๊ฐ๊ฐ์ ๊ฐ๊ฒฉ๊ณผ ๊ตฌ๋งค ์๋์ ํฌํจํ ๋ฐฐ์ด์ด๋ค.
reduce
๋ด์ acc
๋ ๊ธฐ์กด ๊ฐ, cur
์ ํ์ฌ ๊ฐ์ ์๋ฏธํ๋ค. reduce
๋ ๋ฐฐ์ด์ ๋ชจ๋ ์์๋ฅผ ์ํํ๋ฉฐ
์ ํด์ง ํจ์๋ฅผ ์คํํ๊ณ ์ด์ ์ ์คํํด์ ๋์ถํด๋์๋ ๊ฐ, acc
์ ํฉ์ฐํ๋ค. ๋ชจ๋ ๋ฐฐ์ด ์์์ ์ํ์ด ๋๋๋ฉด ํ๋์ ๊ฐ์ ๋์ถํด ๋ฐํํ๋ค.
reduce
: ๋ฐฐ์ด์ ๊ฐ ์์์ ๋ํด ์ ํด์ง ํจ์๋ฅผ ์คํํ๊ณ ํ๋์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.