-
Notifications
You must be signed in to change notification settings - Fork 8
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
[2주차] 고주희 미션 제출합니다. #1
base: main
Are you sure you want to change the base?
Conversation
fix: 컴포넌트 이름 import 시 소문자를 대문자로
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.
안녕하세요 주희 님!
이번주 과제도 고생 많으셨습니다 🙂👍🏻
"singleQuote": true | ||
"singleQuote": false |
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.
프리티어 설정 확인해 주세요!
index.html
Outdated
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<link rel="stylesheet" href="./reset.css" /> |
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.
이렇게 작성하는 것보다는 전역적으로 스타일을 적용하고 싶다면 Emotion의 Global
컴포넌트를 이용해 보세요 🙂👍🏻
Emotion - Global Styles
src/App.jsx
Outdated
import styled from "@emotion/styled"; | ||
import { Text } from "./components/shared/UI"; | ||
|
||
const Style = { |
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.
(개인적인 생각입니다)
스타일 코드보다 컴포넌트 코드의 중요성이 더 높다 보니 스타일 코드는 컴포넌트 코드 아래에 위치시키는 것은 어떨까요?!
}; | ||
|
||
export default function App() { | ||
const [todos, setTodos] = useLocalStorage("todos", []); |
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.
오호 로컬스토리지 관련 로직을 커스텀훅으로 만드셨군요 👍🏻👍🏻
src/App.jsx
Outdated
<Style.Header> | ||
<Text.Title>SCHEDULE</Text.Title> | ||
<Input addTodo={handleAddTodo} /> | ||
</Style.Header> | ||
<Style.TaskWrapper> | ||
<Text.MiniTitle>{todos.length} tasks</Text.MiniTitle> | ||
<Content | ||
items={todos} | ||
onToggle={handleToggleTodo} | ||
onDelete={deleteTodo} | ||
isCompleted={false} | ||
/> | ||
</Style.TaskWrapper> | ||
|
||
<Style.TaskWrapper> | ||
<Text.MiniTitle>{completed.length} tasks</Text.MiniTitle> | ||
<Content | ||
items={completed} | ||
onToggle={handleToggleTodo} | ||
onDelete={deleteDone} | ||
isCompleted={true} | ||
/> | ||
</Style.TaskWrapper> |
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.
<Header>
와 <TaskWrapper>
부분을 컴포넌트로 분리해도 좋을 것 같습니다!
src/components/shared/Content.jsx
Outdated
{isCompleted ? ( | ||
<> | ||
<Style.CheckedBox | ||
onClick={() => onToggle(index, isCompleted)} | ||
/> | ||
</> | ||
) : ( | ||
<> | ||
<Style.UnCheckedBox | ||
onClick={() => onToggle(index, isCompleted)} | ||
/> | ||
</> | ||
)} |
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.
각 항에 대해 하나의 요소만 존재하므로 Fragment는 없어도 될 것 같습니다!
{isCompleted ? ( | |
<> | |
<Style.CheckedBox | |
onClick={() => onToggle(index, isCompleted)} | |
/> | |
</> | |
) : ( | |
<> | |
<Style.UnCheckedBox | |
onClick={() => onToggle(index, isCompleted)} | |
/> | |
</> | |
)} | |
{isCompleted ? ( | |
<Style.CheckedBox onClick={() => onToggle(index, isCompleted)} /> | |
) : ( | |
<Style.UnCheckedBox | |
onClick={() => onToggle(index, isCompleted)} | |
/> | |
)} |
@@ -10,6 +10,9 @@ | |||
"preview": "vite preview" | |||
}, | |||
"dependencies": { | |||
"@emotion/react": "^11.13.3", | |||
"@emotion/styled": "^11.13.0", | |||
"prop-types": "^15.8.1", | |||
"react": "^18.3.1", | |||
"react-dom": "^18.3.1" | |||
}, |
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.
저희는 yarn을 사용하기 때문에 package-lock.json
파일은 지우셔도 될 것 같습니다 🙂
참고 자료 - 패키지 잠금 파일 (package-lock.json, yarn.lock)
src/App.jsx
Outdated
const [completed, setCompleted] = useLocalStorage("completed", []); | ||
|
||
const handleAddTodo = (todo) => { | ||
setTodos([...todos, todo]); |
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.
spread
연산자 사용 아주 좋습니다! 다만 setState
를 사용할 때 변경되는 state를 직접 참조하여 사용하는 것보다는 콜백 함수를 작성하는 방식이 조금 더 안정적인 방식입니다 🙂
setTodos([...todos, todo]); | |
setTodos((prev) => [...prev, todo]); |
그 이유가 궁금하다면...
프로젝트를 진행하다 보면 state 변경 -> 렌더링 -> state 변경 -> 렌더링 -> ...
과정을 반복하게 됩니다.
setState(state + 1)
은 현재의 state 값을 기반으로 상태를 업데이트 합니다. 하지만 이 방식은 state 값이 변경되는 과정에서 다른 setState 호출이 있을 경우, 이전 렌더링 시점의 상태를 기반으로 하기 때문에 최신 상태값을 반영하지 못할 수 있습니다.
반면 setState(prev => prev + 1)
은 콜백 함수를 사용하여 이전 상태(prev)를 기준으로 상태를 업데이트 합니다. 이 방법은 상태 업데이트가 비동기적으로 처리되는 동안에도 항상 최신 상태를 반영하므로, 상태가 여러 번 업데이트 될 때에도 정확한 결과를 보장한다고 합니다!
src/components/shared/Input.jsx
Outdated
export default function Input({ addTodo }) { | ||
const [inputValue, handleChange, setInputValue] = useInput(""); | ||
|
||
const handleAddTodo = (event) => { |
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.
이벤트 핸들러의 이름은 handle
+ 대상
+ 동작
의 형식으로 짓습니다!
이 경우, <form>
에 바인딩 된 onSubmit
핸들러이기 때문에 아래와같이 작성할 수 있습니다.
const handleAddTodo = (event) => { | |
const handleFormSubmit = (event) => { |
참고자료 - [번역] React의 이벤트 핸들러 네이밍 (Event handler naming in react)
src/components/shared/Task.jsx
Outdated
{items.map((todo, index) => ( | ||
<TaskItem | ||
key={index} |
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.
key
값으로 map()
의 index
를 사용하는 방법은 바람직하지 않습니다!
리스트 항목의 순서가 바뀌거나 항목이 추가/삭제될 때, 각 항목에 대응되는 index
값이 변경되기 때문에 이전 key
와 새로운 key
가 달라지게 됩니다. 이는 곧 React가 해당 항목을 새롭게 렌더링해야 한다고 판단하여 불필요한 리렌더링을 초래할 수 있습니다.
즉, key
를 사용하는 이유가 불필요한 리렌더링의 방지인데, 이 경우 오히려 불필요한 리렌더링이 발생할 수 있다는 것입니다!

참고자료 - React 공식문서 key 규칙
참고자료 - 리액트에서 key에 index를 넣으면 안 되는 '진짜' 이유
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.
주희님 미션 수행하느라 수고 많으셨습니다!!!
주희님 코드 보면서 컨포넌트 구성에 있어 어떤식으로 재사용 가능하게 설계해야할지, 로컬스토리지 값을 관리하는 hook에 대한 부분도 정말 많이 배울 수 있었습니다!
디자인도 너무 이뻐요🥹 다음번 미션도 같이 화이팅합시다!
src/App.jsx
Outdated
import useLocalStorage from "./components/hooks/useLocalStorage"; | ||
import Input from "./components/shared/Input"; | ||
import { Text } from "./components/shared/UI"; | ||
import { Style } from "./components/styles/App.styles"; |
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.
useLocalStorage와 같은 커스텀 훅을 사용하여 로컬 스토리지 관련 기능을 처리한 부분! 저도 배워갑니다!
src/App.jsx
Outdated
const deleteTodo = (index) => { | ||
const newTodos = todos.filter((_, i) => i !== index); | ||
setTodos(newTodos); | ||
}; | ||
|
||
export default App; | ||
const deleteDone = (index) => { | ||
const newCompleted = completed.filter((_, i) => i !== index); | ||
setCompleted(newCompleted); | ||
}; |
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.
저는 거의 동일한 로직을 가지고 있는 함수를 하나로 합치는 방식으로 리펙토링했었는데! 이렇게 나눠서 작성하니 더 직관적으로 보이는 것 같아요!
function useLocalStorage(key, initialValue) { | ||
const [storedValue, setStoredValue] = useState(() => { | ||
const item = localStorage.getItem(key); | ||
return item ? JSON.parse(item) : initialValue; | ||
}); | ||
|
||
useEffect(() => { | ||
localStorage.setItem(key, JSON.stringify(storedValue)); | ||
}, [key, storedValue]); | ||
|
||
return [storedValue, setStoredValue]; | ||
} |
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.
저도 이런식으로 localstorage를 따로 뺴서 적용해봐야겠네요! 덕부에 배워갑니다!
import useInput from "../hooks/useInput"; | ||
import { Style } from "../styles/Input.styles"; | ||
|
||
export default function Input({ addTodo }) { | ||
const [inputValue, handleChange, setInputValue] = useInput(""); |
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.
useInput 훅을 활용해 입력 값의 상태를 관리하는 부분이 매우 깔끔한 것 같습니다!!
src/components/shared/Task.jsx
Outdated
export default function Task({ items, onToggle, onDelete, isCompleted }) { | ||
return ( | ||
<Style.Wrapper> | ||
{items.map((todo, index) => ( |
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.
저도 이 부분에 있어서 예전에 조언받았는데! index를 키로 사용할 경우 항목의 순서가 바뀔 때 리렌더링 최적화에 문제가 생길 수 있기에! todo 항목의 고유 식별자(예: id)를 key로 사용하는 것이 좋다고합니다!
src/components/shared/TaskItem.jsx
Outdated
</> | ||
) : ( | ||
<> | ||
<Button.UnCheckedBox isChecked={isCompleted} onClick={onToggle} /> |
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.
조건부 렌더링 덕분에 가독성이 더좋은 것 같아요! 저도 다음에 이런식으로 코드 작성해야겠어요!
opacity: 0.5; | ||
filter: invert(); |
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.
오홍 filter는 사용해본적 없는데 덕분에 또 배워갑니다!
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.
주희님의 코드리뷰를 하게 되어서 너무 좋습니다. 제가 더 얻어가는 게 많네요 !
로직 구현과 폴더 구조등을 보면 고민을 많이 하신 게 티가 나네요! 저도 얼릉 성장해서 주희님과 행복한 플젝 하고 싶습니당🥰
public/emoji.png
Outdated
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.
불필요한 이미지는 삭제해주세요 !
|
||
const handleToggleTodo = (index, isCompleted) => { | ||
if (isCompleted) { | ||
const newCompleted = completed.filter((_, i) => i !== index); |
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.
이벤트 핸들러 네이밍 잘 지켜주셨군요 ! 저도 잘 지켜야 할 텐데 말입니다..
src/App.jsx
Outdated
}; | ||
|
||
return ( | ||
<Style.AppWrapper> |
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.
emotion에서의 style()
를 적용 잘하신 거 같아요! 저는 css()
만 사용해서 입혔는데 다음에 시도해봐야겠습니다 +-+ !!
}, [key, storedValue]); | ||
|
||
return [storedValue, setStoredValue]; | ||
} |
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.
저는 아래 코드처럼 로컬스토리지를 구현했습니다. 근데 주희님은 커스텀훅으로 따로 만드셨더라구요! 따로 만들게 된 이유와 따로 만들게 되면 이점이 무엇인지 궁금합니다.
// 처음 컴포넌트 렌더링될 때 로컬 스토리지에서 할 일 목록 불러오기
const [todoList, setTodoList] = useState(() => {
const savedTodoList = localStorage.getItem('todoList');
return savedTodoList ? JSON.parse(savedTodoList) : [];
});
// todoList가 변경될 때마다 로컬 스토리지에 저장
useEffect(() => {
localStorage.setItem('todoList', JSON.stringify(todoList));
}, [todoList]);
opacity: 0.5; | ||
filter: invert(); |
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.
filter, opacity 는 처음보네요! 잘 알아갑니다
- filter : 흐림 효과나 색상 변형 등 그래픽 효과를 요소에 적용한다.
- opacity : 불투명도 설정, 요소 뒤에 있는 콘텐츠가 숨겨지는 정도.
안녕하세요, LG U+ URECA 프론트엔드 대면반 1기 고주희입니다.
이번 미션은 지난 번과 같은 기능을 바닐라 자바스크립트가 아닌 리액트로 구현해보는 것이었습니다. 리액트를 사용하여 프로젝트를 진행할 때마다 매번 세팅은 똑같이 하고 프로젝트의 기획만 다르게 하였었는데, 이번 미션을 통해 다양한 것들을 추가로 공부해볼 수 있는 기회가 되어 무척 의미있었습니다!
중점적으로 고려한 요소
알게 된 부분
리팩토링 예정 리스트
생각해 볼 질문들
Virtual DOM의 동작 방식:
초기 렌더링: React 컴포넌트가 처음 렌더링될 때, 실제 DOM에 요소를 그리는 대신 Virtual DOM이라는 가상 표현을 생성함. (이 Virtual DOM은 메모리 상에 존재하는 자바스크립트 객체임)
상태 변경 감지: 사용자가 인터랙션을 하거나 상태(state)가 변경되면, React는 새로운 Virtual DOM을 생성함. 이 Virtual DOM은 변경된 상태를 반영하고, 이전의 Virtual DOM과 비교를 통해 어떤 부분이 실제 DOM에서 변경되어야 하는지 찾음.
디프(Diffing): React는 이전 Virtual DOM과 새로운 Virtual DOM을 비교하여 변경된 부분을 찾음. ( = 디프 알고리즘(diffing algorithm) ) 이 알고리즘은 효율적으로 변경된 요소만을 추적하여 최소한의 연산으로 업데이트를 진행할 수 있게 함.
리콘실리에이션(Reconciliation): 변경된 부분을 실제 DOM에 적용하는 과정을 말함. React는 변경된 요소만 실제 DOM에 반영하여, 불필요한 전체 재렌더링을 피하고 성능을 향상시킴.
Virtual DOM의 장점:
배포 링크
https://zuitopia-todo-with-react.vercel.app/
결과 화면