Skip to content

[정새론] 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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REACT_APP_BASE_URL=https://panda-market-api.vercel.app
71 changes: 63 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^7.6.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
Expand Down
Binary file removed public/favicon.ico
Binary file not shown.
Binary file removed public/logo192.png
Binary file not shown.
Binary file removed public/logo512.png
Binary file not shown.
38 changes: 0 additions & 38 deletions src/App.css
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);
}
}
30 changes: 13 additions & 17 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import logo from './logo.svg';
import { Route, Routes } from 'react-router-dom';
import './App.css';
import Root from './pages/root.jsx';
import Login from './pages/login.jsx';
import Register from './pages/register.jsx';
import Items from './pages/items.jsx';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<>
<Routes>
<Route path='/' element={<Root />} />
<Route path='/items' element={<Items />} />
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
</Routes>
</>
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/api/fetchCardList.js
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}`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URLSearchParams 객체 사용해서 여러개의 파라미터를 관리해보는 방법으로 바꾸면, 쿼리 스트링의 파싱, 조작, 인코딩 등의 작업을 간편하게 처리할 수 있으면서도 실수를 줄일 수 있겠죠? :)

아래 아티클 참고해보세요!
참고

const data = await res.json();
return { totalCount: data.totalCount, list: data.list };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리턴값의 경우도 response가 수정되거나 확장될 가능성을 생각해 따로 객체 만들지말고 그대로 리턴해줍시다 :)

} catch (err) {
console.error(err)
}
}
30 changes: 30 additions & 0 deletions src/components/button.css
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);
}
14 changes: 14 additions & 0 deletions src/components/button.jsx
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 }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
  ...
}

미리 정의된 디자인 규칙 안에서만 사용하므로 디자인 시스템의 일관성을 유지할 수 있게되고,
의미가 명확한 props만 남게 되어 유지보수에 용이하고,
스타일 규칙을 객체로 관리해주니 수정 및 확장에도 용이해지겠죠? :)

Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
51 changes: 51 additions & 0 deletions src/components/card.css
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;
}
24 changes: 24 additions & 0 deletions src/components/card.jsx
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}번째 판매 물품 즐겨찾기 아이콘`} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 즐겨찾기 아이콘 클릭하면 사용자에게 피드백을 주기위해 색이 채워지거나 border 두께가 두꺼워지거나 색깔이 진해지지않을까요? ㅎㅎ 이런 인터렉션을 구현하려면 png형식보다는 svg로 export해서, svg를 컴포넌트화하여 props를 보내주는게 좋을것같아요.

예시를 들어드릴게요!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • HeartIcon 컴포넌트 만들기 (예시)
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;
  • 사용할때
    <HeartIcon isActive={element.isFavorite} />

<span>{element.favoriteCount}</span>
</div>
</div>
</div>
);
}

export default Card;
46 changes: 46 additions & 0 deletions src/components/input.css
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);
}
19 changes: 19 additions & 0 deletions src/components/input.jsx
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 }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 컴포넌트를 설계 의도에 맞게 더 유연하게 사용할수있도록 개선해볼까요?

현재의 코드는 slot prop을 통해 이미지를 받고있지만 단순히 props를 통해 데이터만 전달하고있고, 내부적으로 조건부 렌더링을 수행하고 있습니다. 따라서 render delegation 패턴을 제대로 활용하려면 자식 컴포넌트의 렌더링 로직을 부모 컴포넌트에 위임하고 children을 통해 렌더링될 내용을 전달하는 방식으로 개선하는것이 유연한 방식입니다.

이렇게 컴포넌트간 역할을 정의해볼까요?

  • 렌더링할 내용을 children prop으로 받아 더 유연하게 사용하기
  • 부모 컴포넌트가 어떤 내용을 렌더링할지 결정
  • Input 컴포넌트는 구조와 스타일링만 담당
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}
/>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 개선하면, 아래와 같은 장점들이 생깁니다:

  • 유연성 향상: 단순히 이미지가 아닌 어떤 React 엘리먼트든 렌더링할 수 있습니다.
  • 컨트롤 위임: 렌더링할 내용을 완전히 부모 컴포넌트에서 제어할 수 있습니다.
  • 재사용성: Input 컴포넌트가 특정 UI 요소(이미지)에 종속되지 않게 됩니다.
  • 테스트 용이성: 렌더링 로직을 분리함으로써 테스트가 더 쉬워집니다.

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;
Loading
Loading