완성본 github 주소: https://github.com/yeom-kenco/account-book.git
완성본 배포 사이트: https://yeom-kenco.github.io/account-book/
💭가계부 이름에 내 닉네임만 뜨지만... input으로 받아서 뜰 수 있게했으면 좋았겠다고 뒤늦게 생각이 드는...
💭모달 구현 진행 예정....
💭유효성 검사 및 이에 대한 토스트창 띄우기 진행 예정...


과제 요구사항

개요
수입과 지출을 기록하고 잔액을 관리할 수 있는 웹 애플리케이션으로, 모든 데이터는 브라우저의 로컬 스토리지에 저장된다.
React의 기본 개념, 상태 관리, 로컬 저장소 활용 능력을 평가하기 위한 것이다.
디자인의 정확한 구현보다 기능적 요구사항을 충족하는 데 중점을 둘 것.
기술 스택
- React
- CSS (또는 bootstrap, Tailwind CSS)
- LocalStorage API
핵심 기능 요구사항
1. 잔액 표시
- 현재 잔액을 상단에 표시
- 수입 금액 합계 표시
- 지출 금액 합계 표시
2. 거래 입력 기능
- 텍스트 입력 필드: 거래 내역 설명 입력
- 금액 입력 필드: 거래 금액 입력
- 거래 유형 선택: 수입/지출 구분
- '거래 추가' 버튼: 새 거래를 등록
3. 거래 내역 표시
- 각 거래 항목에 대해 다음 정보 표시:
- 금액 (수입은 녹색 '+', 지출은 빨간색 '-'로 표시)
- 스크롤 가능한 거래 내역 목록
- 거래 삭제 기능 (정말 삭제? 확인 기능 modal)
4. 데이터 저장
- 모든 거래 데이터는 LocalStorage에 저장
- 페이지 새로고침 후에도 데이터 유지
선택적 추가 기능
- 예쁜 디자인 ^^
- 카테고리 (예: "용돈", "영화", "외식" 기본 카테고리와 카테고리 추가)
- 거래 편집 기능
데이터 구조 예시
const transactions = [
{
id: 1,
description: "용돈",
amount: 300000,
type: "income",// "income" 또는 "expense"
date: "2025-04-25"
},
{
id: 2,
description: "영화 관람",
amount: 11000,
type: "expense",
date: "2025-04-25"
}
];
UI 요구사항
- 제공된 디자인 목업과 일치하지 않아도 됨
- 반응형 디자인 구현 (모바일, 태블릿, 데스크톱)
- 사용자 친화적인 인터페이스
개발 가이드라인
- 컴포넌트 기반 구조 사용 (최소 4개 이상의 컴포넌트로 분리)
- React Hooks 활용 (useState, useEffect 등)
- LocalStorage를 위한 커스텀 훅 작성
- 적절한 에러 처리 구현
과제 진행 과정 및 배운점 정리✍️
파일구조 및 개발 개요
account-book
├─ eslint.config.js
├─ index.html
├─ package-lock.json
├─ package.json
├─ public
├─ README.md
├─ src
│ ├─ App.css
│ ├─ App.jsx
│ ├─ assets
│ ├─ components
│ │ ├─ History.jsx
│ │ ├─ HistoryDetail.jsx
│ │ ├─ Summary.jsx
│ │ └─ TransactionUpdate.jsx
│ ├─ css
│ │ ├─ History.module.css
│ │ ├─ HistoryDetail.module.css
│ │ ├─ Summary.module.css
│ │ └─ TransactionUpdate.module.css
│ ├─ hooks
│ │ └─ useTransactions.js
│ ├─ index.css
│ └─ main.jsx
└─ vite.config.js

- 총 4개의 컴포넌트로 분리해서 작업을 진행
- 각 컴포넌트에서 state로 local Storage에 저장된 데이터의 상태를 공유할 수 있도록 useTransaction 훅 제작
(hook을 통해 state를 부모에서 자식 컴포넌트로 props를 전달 받으면서 사용해야 같은 데이터를 공유하는 모든 컴포넌트가 따로 새로고침 없이 부모 컴포넌트가 리렌더링 됨과 동시에 함께 리렌더링된다.) - module방식의 css를 사용해, 각 컴포넌트에서 같은이름의 클래스를 사용해도 공유되지 않도록 함
useTransactions 훅 제작 : localStorage 기반의 거래 내역 상태 관리
이 훅은 거래 내역을 관리하며 localStorage를 통해 영속적으로 데이터를 저장한다.
useTransactions 전체코드
// src/hooks/useTransactions.js
import { useState, useEffect } from "react";
const LOCAL_STORAGE_KEY = "transactions";
const useTransactions = () => {
const [transactions, setTransactions] = useState([]);
// localStorage에서 초기 데이터 불러오기
useEffect(() => {
const storedData = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
if (storedData) {
setTransactions(storedData);
}
}, []);
// 거래 추가 함수
const addTransaction = (newTransaction) => {
const updatedTransactions = [newTransaction, ...transactions];
setTransactions(updatedTransactions);
localStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(updatedTransactions)
);
};
// 거래 삭제 함수
const removeTransaction = (id) => {
const updatedTransactions = transactions.filter((tx) => tx.id !== id);
setTransactions(updatedTransactions);
localStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(updatedTransactions)
);
};
return {
transactions,
addTransaction,
removeTransaction,
};
};
export default useTransactions;
1. 기본 구조
import { useState, useEffect } from "react";
const LOCAL_STORAGE_KEY = "transactions";
LOCAL_STORAGE_KEY는 localStorage의 키 이름이다.
거래 데이터를 문자열로 저장할 때 이 키를 기준으로 set/get을 수행하게 된다.
2. 초기 상태 설정
const [transactions, setTransactions] = useState([]);
거래 내역을 담는 state이다. 기본값은 빈 배열이다. 이 state가 변경되면 이 훅을 사용하는 컴포넌트들이 리렌더링 된다.
React의 핵심은 state 변화에 따른 UI 자동 갱신에 있기 때문이다.
3. localStorage에서 초기 데이터 불러오기
useEffect(() => {
const storedData = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
if (storedData) {
setTransactions(storedData);
}
}, []);
의존성에 빈 배열을 넣은 useEffect는 컴포넌트가 처음 마운트될 때 한 번만 실행된다.
이 시점에서 localStorage에 저장된 거래 내역이 있는지 확인하고, 있다면 그것을 state에 반영한다.
즉, 사용자가 이전에 입력했던 데이터가 있다면 그것이 복구된다.
그렇기 때문에 새로고침해도 데이터가 유지되는 것이다.
4. 거래 추가 함수: addTransaction()
const addTransaction = (newTransaction) => {
const updatedTransactions = [newTransaction, ...transactions];
setTransactions(updatedTransactions);
localStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(updatedTransactions)
);
};
거래를 추가하는 함수다. 새 거래는 가장 앞에 추가된다.
배열의 불변성을 유지하며 새로운 배열을 만들어 setTransactions에 넘긴다.
그 후 localStorage에도 업데이트된 배열을 문자열로 저장한다.
이때 중요한 것은 setTransactions이 호출되었다는 점이다.
이로 인해 이 훅을 사용하는 컴포넌트는 모두 리렌더링된다.
5. 거래 삭제 함수
const removeTransaction = (id) => {
const updatedTransactions = transactions.filter((tx) => tx.id !== id);
setTransactions(updatedTransactions);
localStorage.setItem(
LOCAL_STORAGE_KEY,
JSON.stringify(updatedTransactions)
);
};
삭제도 마찬가지이다. 해당 id를 제외한 배열을 만들어 setTransactions에 넣는다.
그리고 localStorage도 동기화한다.
이 역시 state가 변화했기 때문에 관련 컴포넌트는 즉시 업데이트된다.
6. 반환 객체
return {
transactions,
addTransaction,
removeTransaction,
};
이 훅은 단순히 데이터를 반환하는 것이 아니라,
상태와 그 상태를 변화시키는 로직을 함께 리턴한다.
그렇기 때문에 이 훅을 사용하는 곳에서는 거래 내역을 조회하고 추가하거나 삭제할 수 있다.
이 모든 동작은 하나의 상태로 통합 관리되며, 이로 인해 앱의 일관성이 유지된다.
TransactionUpdate.jsx 컴포넌트 제작 : 사용자 input 관리 및 addTransaction() 함수 사용
TransactionUpdate 컴포넌트는 사용자로부터 거래 내역(내용, 금액, 수입/지출 여부)을 입력받아 새로운 거래 항목을 등록하는 UI이다.
1. 상태(state) 선언
const [transactionDetailText, setTransactionDetailText] = useState("");
const [transactionDetailMoney, setTransactionDetailMoney] = useState("");
const [isIncome, setIsIncome] = useState(true);
이 컴포넌트는 총 3개의 상태를 가진다.
- transactionDetailText: 거래 내용 입력값을 저장한다. 문자열 형태이다.
- transactionDetailMoney: 금액 입력값을 저장한다. 초기값은 문자열이지만 숫자로 변환하여 처리한다.
- isIncome: 수입인지 지출인지를 구분하는 불리언 값이다. true면 수입, false면 지출이다.
2. 입력값 핸들링
<input
type="text"
value={transactionDetailText}
onChange={(e) => setTransactionDetailText(e.target.value)}
/>
input 요소의 값은 해당하는 state에 의해 결정되며, 값이 바뀌면 onChange 이벤트를 통해 다시 state를 업데이트하는 흐름이다.
= 화면(input)은 state를 읽기만 하고, 사용자가 입력하면 state를 업데이트만 한다.
→ React의 controlled component (제어 컴포넌트) 구조를 말한다.
Controlled Component란?
input의 value를 직접 관리하는 컴포넌트를 말한다.
즉, 화면에 보이는 input 값이 항상 state에 의해 결정된다는 것
이러한 구조의 장점은 다음과 같다:
- 입력값을 완전히 제어할 수 있다.
- 유효성 검사(validation)를 자유롭게 수행할 수 있다.
- 폼 초기화 및 자동 포커스 등 부가 로직을 쉽게 붙일 수 있다.
1. 입력값을 완전히 제어할 수 있다.
- 사용자가 입력하는 걸 실시간으로 감시할 수 있다.
- 원하는 대로 값을 막거나, 자동으로 변형하거나, 아무 조작도 가능하다.
if (e.target.value.length > 10) {
alert("10글자 넘으면 안돼요!");
return;
}
setText(e.target.value);
=> 사용자가 10글자 넘게 입력하려고 하면 막아버릴 수 있다.
2. 유효성 검사(validation)을 자유롭게 수행할 수 있다.
- 입력값이 숫자인지, 이메일 형식인지, 조건에 맞는지 등을 입력 즉시 확인할 수 있다.
if (!e.target.value.includes("@")) {
setError("이메일 형식이 아닙니다!");
}
=> 입력값이 이상할 때, 바로 에러 메시지를 보여줄 수 있다.
3. 폼 초기화 및 자동 포커스 등 부가 로직을 쉽게 붙일 수 있다.
- 폼 전송이 끝나면, input 값을 한 번에 리셋하거나
- 특정 input에 자동으로 focus를 줄 수도 있다.
예시 (폼 초기화):
const handleSubmit = (e) => {
e.preventDefault();
// 데이터 서버로 전송
setText(""); // 입력창 초기화
};
예시 (자동 포커스):
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
=> 새로 렌더링되었을 때, 자동으로 커서를 깜빡이게 할 수 있다.
🔥 반대로 "Uncontrolled Component"는?
- value를 직접 관리하지 않고, 브라우저가 알아서 관리한다.
- 필요한 순간에만 ref를 통해 값을 가져온다.
이렇게 하면 편하긴 한데, 복잡한 폼 관리나 validation이 어렵다.
지출/수입 선택은 라디오 버튼으로 처리된다:
<input
type="radio"
name="isIncome"
value="income"
checked={isIncome === true}
onChange={handleRadioChange}
/>
핸들러는 아래와 같다:
const handleRadioChange = (e) => {
setIsIncome(e.target.value === "income");
};
여기서 "income" 문자열과 비교하여 isIncome을 true 또는 false로 설정한다.
React에서 라디오 버튼을 제대로 제어하려면 checked 속성을 활용한 이 방식이 필수적이다.
3. ➕ 거래 추가 함수
const handleAddTransaction = () => {
const newTransaction = {
id: Date.now(),
description: transactionDetailText,
amount: parseInt(transactionDetailMoney),
type: isIncome ? "income" : "expense",
date: new Date().toISOString().slice(0, 10),
};
addTransaction(newTransaction);
setTransactionDetailText("");
setTransactionDetailMoney("");
setIsIncome(true);
};
거래 추가 버튼을 누르면 실행되는 함수다. 주요 흐름은 다음과 같다:
🔹 새로운 거래 객체 생성
- 고유 id를 Date.now()로 생성
- 내용, 금액, 유형(type), 날짜를 입력값에서 추출하여 구성
🔹 상위 함수에 전달
- addTransaction() 함수는 props로 전달된 함수이며,
최종적으로 useTransactions 훅의 addTransaction()을 호출하게 된다. - 이 흐름을 통해 최상위 상태가 변경되므로, 연결된 컴포넌트들은 자동 리렌더링 된다.
🔹 입력값 초기화
- 입력창을 다시 빈 값으로 만들기 위해 상태를 초기화한다.
- 이는 사용자 UX 측면에서 매우 중요하다.
방금 작성한 값이 남아있으면 실수로 중복 입력할 수 있기 때문이다.
History 컴포넌트와 HistoryDetail 컴포넌트 정리
이 프로젝트에서는 거래 내역을 화면에 보여주는 역할을 History와 HistoryDetail이라는 두 컴포넌트가 맡고 있다.
각각 어떤 책임을 가지고 있는지, 그리고 구현 방식은 어떤 이점을 가지는지 하나씩 살펴보자.
1. History.jsx
1-1. 역할
History 컴포넌트는 전체 거래 내역을 관리한다.
구체적으로는 다음과 같다:
- 거래 목록(transactions)을 받아온다.
- 각각의 거래를 HistoryDetail 컴포넌트로 넘겨서 개별 렌더링한다.
- 거래가 아예 없다면 "거래 내역이 없습니다"라는 안내 문구를 보여준다.
즉, "거래 목록 전체"를 책임지는 상위 컴포넌트라 할 수 있다.
1-2. 주요 구조
{transactions.length === 0 ? (
<p>거래 내역이 없습니다🤔</p>
) : (
transactions.map((t) => (
<HistoryDetail key={t.id} transaction={t} onDelete={removeTransaction} />
))
)}
여기서 중요한 흐름은 다음과 같다:
- 거래 목록이 비었는지(length === 0) 먼저 체크한다.
- 비었다면 안내 문구를 보여주고,
- 그렇지 않으면 .map()으로 거래 하나하나를 HistoryDetail 컴포넌트로 넘긴다.
2. HistoryDetail.jsx
2-1. 역할
HistoryDetail 컴포넌트는 "거래 하나"를 화면에 표시하는 역할을 한다.
그리고, 마우스를 올렸을 때 삭제 버튼이 나타나고, 클릭 시 삭제 요청을 보내는 로직을 담당한다.
즉, 개별 거래 단위를 다루는 세부 컴포넌트라 할 수 있다.
2-2. 주요 로직
① hover 상태 관리
const [hover, setHover] = useState(false);
- 마우스를 올리거나(onMouseEnter)
- 마우스를 내릴 때(onMouseLeave)
hover 상태를 바꾼다.
그렇기 때문에 사용자가 거래 항목에 마우스를 올렸을 때만 삭제 버튼(❌)이 나타난다.
② 삭제 버튼 동작
const handleDelete = () => {
const confirm = window.confirm("정말 삭제하시겠습니까?");
if (confirm) onDelete(transaction.id);
};
- 삭제 버튼을 클릭하면 window.confirm()으로 사용자에게 한 번 묻는다.
- "확인"을 누르면 onDelete(transaction.id)가 호출된다.
여기서 onDelete는 History 컴포넌트에서 removeTransaction을 넘겨준 함수다.
③ 거래 내용 표시
<div className={css.text}>{transaction.description}</div>
{transaction.type === "income" ? <span>+</span> : <span>-</span>}
<div className={css.amount}>{transaction.amount.toLocaleString()}원</div>
<div className={css.date}>{transaction.date}</div>
- 거래 설명(description), 금액(amount), 날짜(date)를 각각 따로 나눠서 표시한다.
- transaction.type이 "income"이면 금액 앞에 + 표시를 붙이고, "expense"면 -를 붙인다.
- toLocaleString()을 써서 금액을 보기 좋게 1,000단위로 콤마(,)를 넣는다.
3. CSS 추가 설명 : 거래 항목 삭제 버튼 구현 ⭐⭐⭐
이 프로젝트에서는 거래 항목마다 마우스를 올렸을 때(hover) 삭제 버튼(❌)이 부드럽게 나타나도록 만들었다.
하지만 이 기능을 구현하는 데에는 신경 써야 할 부분이 꽤 많았다.
닫기 버튼을 자연스럽고 깔끔하게 띄우기 위해서는,
position 설정과 opacity 전환, 그리고 상대적인 위치 계산을 제대로 다뤄야 한다.
1. 버튼을 아이템 바깥에 띄워야 했다
1-1. 문제
보통 버튼은 아이템 안쪽에 만들어지는데, 이 경우에는
아이템 왼쪽 바깥으로 ❌ 버튼을 살짝 튀어나오게 배치해야 했다.
그렇기 때문에 기본적인 배치 방법으로는 부족했다.
1-2. 해결 방법
.deleteButton {
position: absolute;
left: -28px; /* 아이템 왼쪽 바깥으로 밀어낸다 */
top: 50%;
transform: translateY(-50%);
}
- position: absolute;
→ 버튼을 .item 기준으로 절대 위치에 배치할 수 있게 만든다. - left: -28px;
→ 왼쪽으로 28px 밀어서 아이템 바깥으로 나가게 만든다. - top: 50%; transform: translateY(-50%);
→ 버튼을 세로 가운데 정렬시킨다. → top: 50%로 위에서 절반만큼 내려온 다음, translateY(-50%)로 자기 키(height)의 절반만큼 다시 올려서 정확히 중앙에 맞춘다.
✅ 이 세 가지 설정이 맞아야, 버튼이 예쁘게 좌측에 "살짝 튀어나온" 느낌으로 고정된다.
2. 버튼이 처음에는 안 보여야 했다
항상 버튼이 보인다면 화면이 너무 복잡하고 지저분해진다.
마우스를 올렸을 때만 자연스럽게 나타나야 했다.
그래서 처음에는 opacity: 0으로 투명하게 숨기고,
hover 되었을 때만 opacity: 1로 부드럽게 나타나게 만들었다.
.deleteButton {
opacity: 0;
transition: opacity 0.2s ease;
}
.item:hover .deleteButton {
opacity: 1;
}
- 기본 상태에서는 opacity: 0 → 완전히 투명.
- .item에 hover가 발생하면, .deleteButton의 opacity: 1로 전환.
- transition: opacity 0.2s ease;를 걸어줬기 때문에,
→ 단번에 튀어나오는 것이 아니라, 0.2초 동안 부드럽게 나타난다.
3. 그 외 설정들
버튼 클릭 가능 영역
padding: 2px 6px;
cursor: pointer;
- 클릭하기 편하도록 약간의 padding을 줬다.
- cursor: pointer;로 마우스가 올라갔을 때 손가락 모양으로 바뀌게 했다.
버튼 시각적 구분
border-radius: 3px;
font-size: 12px;
z-index: 2;
- z-index: 2로 레이어를 살짝 위로 띄워서, 다른 요소보다 항상 위에 보이게 했다.
닫기 버튼 하나 구현하는 데에도 생각보다 많은 CSS 테크닉이 들어갔다.
(1) 위치를 절대좌표로 띄우고 → (2) opacity로 등장 애니메이션을 주고 → (3) 클릭 편의성과 디자인 세부 튜닝을 더했다.
📌 이런 걸 기억해두자!
- hover 효과를 버튼이 아니라 부모 요소에 걸자
(그래야 버튼이 아닌 곳에 hover 해도 부드럽게 나타난다) - left, top, transform으로 정확한 위치 잡기
(translateY로 세로 중앙 맞추는 건 CSS에서 자주 쓰이는 고급 팁이다) - opacity + transition 조합은 매우 강력하다
(애니메이션 만들 때 필수 테크닉!)
미비한 점
💡 현재 내 방식 요약
- transactions (거래내역)라는 state를 부모 컴포넌트에서 만들고,
- 이걸 props로 자식 컴포넌트들에 전달했다.
- 새로운 거래를 추가하거나 삭제할 때, 부모의 state를 업데이트해주고,
- 그 덕분에 자식들도 리렌더링되도록 연결되어 있다.
💬 그런데 강사님 피드백은?
"props로 다 전달하는 대신, 필요한 컴포넌트가 스스로 데이터를 가져오게 하면 어때?"
| 지금 방식 | 추천하는 방식 |
| 부모가 모든 state 관리 | 각 컴포넌트가 필요한 데이터/함수만 직접 가져옴 |
| props로 모든 데이터를 넘김 | 필요할 때 getTransactions(), saveTransaction() 같은 함수 호출 |
| 트랜잭션 추가/삭제: 부모 → 자식 | 트랜잭션 추가/삭제: 자식 스스로 |
📚 왜 이런 방식이 더 좋을까?
- 컴포넌트 독립성 증가
- 각 컴포넌트가 필요한 데이터만 직접 가져오기 때문에 의존성(props) 이 줄어듦.
- 더 유연하고 재사용성 높은 컴포넌트를 만들 수 있다.
- props 관리 지옥 방지
- 컴포넌트가 많아지면 props로 주고받는 게 복잡해진다.
- "props drilling" (prop을 자식 → 손자 → 증손자까지 넘기는 문제)을 예방할 수 있다.
- 수정/유지보수 편함
- 나중에 구조가 커져도 "부모 수정 → 자식 수정"의 연쇄작업이 적어진다.
- 코드가 깔끔하고 가독성 좋아짐
- 필요한 컴포넌트에서 필요한 데이터/함수만 딱 불러서 쓰면 된다.
🔥 구체적으로 어떻게 개선하는 걸까?
1. useTransactions.js 훅에서 useState랑 useEffect를 빼버리기: 더이상 상태를 여기서 만들지 않는다.
2. 대신 localStorage를 직접 다루는 순수 함수들만 만들기
3. 컴포넌트 안에서 직접 가져다 쓴다.
예를 들어, History 컴포넌트 안에서는:
import { getTransactions, deleteTransaction } from "../utils/localStorageUtils";
import { useState, useEffect } from "react";
const History = () => {
const [transactions, setTransactions] = useState([]);
useEffect(() => {
const data = getTransactions();
setTransactions(data);
}, []);
const removeTransaction = (id) => {
deleteTransaction(id);
setTransactions(getTransactions()); // 삭제 후 다시 불러오기
};
// 나머지 렌더링
};
→ 이제는 부모가 모든 state를 가지지 않아도 되고,
→ 각 컴포넌트가 필요할 때 알아서 localStorage를 읽어와서 데이터를 갱신하는 것
4. useEffect() 사용
- 왜 useEffect를 써야 하냐면?
- localStorage는 "브라우저 저장소"라서, 실제 반영된 데이터를 다시 읽어와야 리렌더링이 정확히 일어난다.
- 특히, 저장/삭제 같은 작업을 한 뒤 localStorage를 다시 읽어서 state를 업데이트하는 게 중요함
회고
오늘은 팀프로젝트에 앞서, 팀을 짜기위해 개별 실력을 파악하신다고 실습 과제를 내주셨다.
사실 오전 2시간에는 블로그 꾸미기를 하다가 시간을 다 날려서(ㅎㅎ;) 점심먹고부터 열심히 하고있었는데,
강사님께서 왜 다들 아직도 안내냐고, 다들 너무 디테일하게 기능 구현하고, 너무 잘해서 그런거냐고 그런거죠? 다들 그러시느라 그런가보다~ 이러셔서...마음이 아팠다..^^;;
사실상 실력자 대략 3분의 1 정도 빼고는 절대 3시간 만에 gpt없이 요구사항 다 못만들지 않을까라는 생각이었는데, 강사님께서 되게 당황하시길래 '아 이정도는 다들 2-3시간 컷으로 디테일한 기능 구현까지 가능하겠지.'라고 예상하신 것 같았다....ㅜㅜ
결국 나는 gpt손 빌리기 작전^__^
커스텀 훅 만드는 것도 감이 아예 안왔고~... css 미세 조정 하다보니 그냥 시간이 사라졌다!! 막막 내 맘대로 적용 안되는 css들과 똑같은 input구조인데 한개에만 자꾸 css적용되고 난리~!!!! gpt와 함께 해버렸지만...배워간게 있으면 된거 아닐까요?ㅜㅜㅎㅎ
+) 1대1로 내가 만든 결과물에 대한 코드 피드백을 받아본 건 살면서 처음이었다! 그전까지는 이런 피드백 과정이 얼마나 중요하고 값진 시간인지 체감해볼 기회가 없었는데, 단번에 깨달을 수 있었다....ㅜㅜ특히나 나같은 초짜는 작동만 잘 되면 내가 뭔가 잘못된 구조로 개발하고있는지 오래도록 깊이 고민하지 않는 한 스스로 인지하기 어렵다...무엇보다 gpt의 경우 거짓정보를 진짜인 것 처럼 진실과 섞여서 교묘하게...늘 좋은 말만 해주기때문에 전적으로 신뢰하기 어려운 것 같다. 저번 프로젝트때도 전혀 잘못된 과정이었는데 "훌륭해!"라고 해주더니만 이번에도 내가 한 구조를 추천한다고 해줬었기 때문 ㅋㅅㅋ;;
그래도 강사님께 피드백 받고 한 번에 무슨 말씀이신지 이해하기 어려웠는데, 다른분들 코드도 참고하고 gpt한테 질문하며 완전히 이해할 수 있었다. 어쨌든 따봉gpt야 고마어~
'웹 개발 일기 > [LG유플러스] 유레카' 카테고리의 다른 글
| [학습 기록] Day64: API 사용하기 (2) - Tanstack Query (2) | 2025.04.29 |
|---|---|
| [학습 기록] Day63: API 사용하기 (1) - 날씨 API (0) | 2025.04.29 |
| [학습 기록] Day61: Redux (0) | 2025.04.24 |
| [학습 기록] Day59: URLSearchParams, useSearchParams, 페이지네이션 (3) | 2025.04.23 |
| [학습 기록] Day58: React.memo (0) | 2025.04.22 |
