-
Notifications
You must be signed in to change notification settings - Fork 42
[정새론] sprint5 #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "React-\uC815\uC0C8\uB860-sprint5"
[정새론] sprint5 #175
Changes from all commits
9bd6b32
b1e1a67
0e67ac8
5a1fa93
846880d
b9099ea
ac9ca8b
112c7c6
7966b6c
d0acbd1
af16adb
868c5cd
45ce340
00ba031
d07ce37
ea5379f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
REACT_APP_BASE_URL=https://panda-market-api.vercel.app |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +0,0 @@ | ||
.App { | ||
text-align: center; | ||
} | ||
|
||
.App-logo { | ||
height: 40vmin; | ||
pointer-events: none; | ||
} | ||
|
||
@media (prefers-reduced-motion: no-preference) { | ||
.App-logo { | ||
animation: App-logo-spin infinite 20s linear; | ||
} | ||
} | ||
|
||
.App-header { | ||
background-color: #282c34; | ||
min-height: 100vh; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
font-size: calc(10px + 2vmin); | ||
color: white; | ||
} | ||
|
||
.App-link { | ||
color: #61dafb; | ||
} | ||
|
||
@keyframes App-logo-spin { | ||
from { | ||
transform: rotate(0deg); | ||
} | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* params: quantity, page, orderBy | ||
*/ | ||
export default async function fetchLists(quantity, page, orderBy) { | ||
try { | ||
const res = await fetch(`${process.env.REACT_APP_BASE_URL}/products?page=${page}&pageSize=${quantity}&orderBy=${orderBy}`) | ||
const data = await res.json(); | ||
return { totalCount: data.totalCount, list: data.list }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리턴값의 경우도 response가 수정되거나 확장될 가능성을 생각해 따로 객체 만들지말고 그대로 리턴해줍시다 :) |
||
} catch (err) { | ||
console.error(err) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
@import '../theme.css'; | ||
|
||
.button-big { | ||
border: 0; | ||
width: 100%; | ||
height: 56px; | ||
border-radius: 40px; | ||
background-color: var(--primary-100); | ||
color: white; | ||
font-weight: 600; | ||
font-size: 20px; | ||
cursor: pointer; | ||
} | ||
|
||
.button-small { | ||
border: 0; | ||
width: fit-content; | ||
height: 40px; | ||
padding: 12px 23px; | ||
border-radius: 8px; | ||
background-color: var(--primary-100); | ||
color: white; | ||
font-weight: 600; | ||
font-size: 16px; | ||
cursor: pointer; | ||
} | ||
|
||
.button:disabled { | ||
background-color: var(--secondary-400); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { Link } from 'react-router-dom'; | ||
import './button.css'; | ||
|
||
function Button({ children, disabled, link, radius, size }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 구조로 봤을때, Button은 요구사항 변경에 따라 관리해야하는 props 개수를 점점 늘리게될것같아요. 스타일링 관련 props를 variant 패턴으로 리팩토링하면 어떨까요? 이런식으로 variant 객체를 컴포넌트 외부에 추가하면, Button의 스타일과 관련된 정보를 한곳에서 관리할 수 있어 수정 및 확장에 용이해요. const VARIANTS = {
small: {
width: ...,
borderRadius: ...,
},
medium: {
width: 486,
borderRadius: 16,
},
disabled: {
...
}
}; 그리고 단순 스타일링과 관련된 props를 제거해주고, 의미가 명확한 props만 유지하도록 바꿔보면: function Button ({ children, disabled, link }) => {
const variantStyle = VARIANTS[variant] || VARIANTS.medium;
...
} 미리 정의된 디자인 규칙 안에서만 사용하므로 디자인 시스템의 일관성을 유지할 수 있게되고, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 왜 props의 증가가 안좋은지는, 아래 아티클 참고해보시면 좋을것같아요 :) |
||
return ( | ||
<Link to={link}> | ||
<button className={size} disabled={disabled}> | ||
{children} | ||
</button> | ||
</Link> | ||
) | ||
} | ||
|
||
export default Button; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
@import '../theme.css'; | ||
|
||
.wrapper { | ||
width: 100%; | ||
height: fit-content; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
gap: 16px; | ||
} | ||
|
||
.card-image { | ||
width: 100%; | ||
height: 100%; | ||
aspect-ratio: 1; | ||
object-fit: cover; | ||
border-radius: 16px; | ||
} | ||
|
||
.card-text-container { | ||
width: 100%; | ||
display: flex; | ||
flex-direction: column; | ||
gap: 10px; | ||
} | ||
|
||
.card-text-container>h2 { | ||
font-weight: 500; | ||
font-size: 14px; | ||
margin: 0; | ||
} | ||
|
||
.card-text-container>h3 { | ||
font-weight: 700; | ||
font-size: 16px; | ||
margin: 0; | ||
} | ||
|
||
.card-favorite-container { | ||
display: flex; | ||
align-items: center; | ||
gap: 4px; | ||
color: var(--secondary-600); | ||
font-size: 12px; | ||
font-weight: 500; | ||
} | ||
|
||
.card-favorite-container>img { | ||
width: 16px; | ||
height: 16px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import './card.css'; | ||
import Heart from '../public/items/ic_heart.png'; | ||
|
||
function Card({ element }) { | ||
return ( | ||
<div className="wrapper"> | ||
<img | ||
src={element.images[0]} | ||
alt={`${element.id}번째 판매 물품 대표 사진`} | ||
className='card-image' | ||
/> | ||
<div className='card-text-container'> | ||
<h2>{element.name}</h2> | ||
<h3>{element.price.toLocaleString()}원</h3> | ||
<div className='card-favorite-container'> | ||
<img src={Heart} alt={`${element.id}번째 판매 물품 즐겨찾기 아이콘`} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 즐겨찾기 아이콘 클릭하면 사용자에게 피드백을 주기위해 색이 채워지거나 border 두께가 두꺼워지거나 색깔이 진해지지않을까요? ㅎㅎ 이런 인터렉션을 구현하려면 png형식보다는 svg로 export해서, svg를 컴포넌트화하여 props를 보내주는게 좋을것같아요. 예시를 들어드릴게요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
import React from 'react';
function HeartIcon({ isActive = false }) {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill={isActive ? "#FF0000" : "none"}
stroke={isActive ? "#FF0000" : "#000000"}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
</svg>
);
}
export default HeartIcon;
|
||
<span>{element.favoriteCount}</span> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default Card; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
@import '../theme.css'; | ||
|
||
.input-container { | ||
position: relative; | ||
height: 100%; | ||
} | ||
|
||
.input { | ||
width: 100%; | ||
height: 100%; | ||
max-width: 640px; | ||
box-sizing: border-box; | ||
background-color: var(--gray-100); | ||
border: 0; | ||
border-radius: 12px; | ||
font-size: 16px; | ||
color: var(--secondary-800); | ||
padding: 0px 10px; | ||
} | ||
|
||
.input-slanted { | ||
width: 100%; | ||
height: 100%; | ||
max-width: 640px; | ||
box-sizing: border-box; | ||
background-color: var(--gray-100); | ||
border: 0; | ||
border-radius: 12px; | ||
font-size: 16px; | ||
color: var(--secondary-800); | ||
padding: 0px 10px 0px 40px; | ||
} | ||
|
||
.input-button { | ||
position: absolute; | ||
right: 20px; | ||
top: 16px; | ||
background-color: transparent; | ||
border: 0; | ||
} | ||
|
||
.slot-left-image { | ||
position: absolute; | ||
left: 10px; | ||
top: calc((100% / 2) - 10px); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import './input.css'; | ||
|
||
function Input({ slot, slotDirection, slotAlt, onChange, onBlur, type, name, placeholder }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 컴포넌트를 설계 의도에 맞게 더 유연하게 사용할수있도록 개선해볼까요? 현재의 코드는 slot prop을 통해 이미지를 받고있지만 단순히 props를 통해 데이터만 전달하고있고, 내부적으로 조건부 렌더링을 수행하고 있습니다. 따라서 render delegation 패턴을 제대로 활용하려면 자식 컴포넌트의 렌더링 로직을 부모 컴포넌트에 위임하고 children을 통해 렌더링될 내용을 전달하는 방식으로 개선하는것이 유연한 방식입니다. 이렇게 컴포넌트간 역할을 정의해볼까요?
import "./input.css";
function Input({
renderSlot,
slotDirection = "right",
onChange,
onBlur,
type,
name,
placeholder,
}) {
return (
<div className="input-container">
{slotDirection === "left" && renderSlot && (
<div className="slot-left">{renderSlot()}</div>
)}
<input
className={slotDirection === "left" ? "input-slanted" : "input"}
onChange={onChange}
type={type}
name={name}
onBlur={onBlur}
placeholder={placeholder}
/>
{slotDirection === "right" && renderSlot && (
<button className="input-button">{renderSlot()}</button>
)}
</div>
);
}
export default Input;
<Input
renderSlot={() => (
<img
src={Visibility}
alt='비밀번호 보이기 토글 버튼'
/>
)}
onChange={handleChange}
type='password'
name='pw'
onBlur={handleBlur}
/> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 개선하면, 아래와 같은 장점들이 생깁니다:
|
||
return ( | ||
<div className='input-container'> | ||
{slot && slotDirection === 'left' && ( | ||
<img src={slot} alt={slotAlt} className='slot-left-image' /> | ||
)} | ||
<input className={slotDirection === 'left' ? 'input-slanted' : 'input'} onChange={onChange} type={type} name={name} onBlur={onBlur} placeholder={placeholder} /> | ||
{slot && slotDirection === 'right' && ( | ||
<button className='input-button'> | ||
<img src={slot} alt={slotAlt} /> | ||
</button> | ||
)} | ||
</div> | ||
) | ||
} | ||
|
||
export default Input; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URLSearchParams 객체 사용해서 여러개의 파라미터를 관리해보는 방법으로 바꾸면, 쿼리 스트링의 파싱, 조작, 인코딩 등의 작업을 간편하게 처리할 수 있으면서도 실수를 줄일 수 있겠죠? :)
아래 아티클 참고해보세요!
참고