-
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주차] 심여은 미션 제출합니다. #5
base: main
Are you sure you want to change the base?
Conversation
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의 사용법을 여은님의 코드를 보면서 더 많이 배웠습니다🤩
한 주동안 고생하셨습니다~!
function App() { | ||
return <div>React Todo</div>; | ||
const userName = '옹헤'; |
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 GlobalStyles = () => ( | ||
<Global | ||
styles={css` | ||
@font-face { | ||
font-family: 'RixXladywatermelonR'; | ||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/RixXladywatermelonR.woff2') | ||
format('woff2'); | ||
font-weight: normal; | ||
font-style: normal; | ||
} | ||
|
||
html { | ||
margin: 0; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100vh; | ||
} | ||
|
||
body { | ||
font-family: 'RixXladywatermelonR', sans-serif; | ||
max-width: 500px; | ||
padding: 10px; | ||
text-align: center; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
scrollbar-width: thin; | ||
margin: 0; | ||
} | ||
|
||
button { | ||
font-family: 'RixXladywatermelonR'; | ||
display: flex; | ||
justify-content: center; | ||
align-content: center; | ||
flex-wrap: wrap-reverse; | ||
} | ||
`} | ||
/> | ||
); |
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.
Global style을 통해서 하나의 디자인 컴포넌트에 한번에 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.
주현님에게 전역 스타일에 대해 리뷰를 들었는데 이렇게 사용하는 거군요! 확실히 훨씬 깔끔한 것 같아요 다음 미션 때 참고해서 적용해보겠습니다😆
//입력값이 없으면 alert 띄우기 | ||
if (todoInput === '') { | ||
alert('한 글자 이상 입력해주세요.'); | ||
return; | ||
} |
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.
빈 문자열 방지하는 alert 좋은 것같아요~!👍
export default function TodoList({ todoList, handleComplete, handleDelete }) { | ||
// todoList에서 완료되지 않은 todoList와 완료된 todoList를 분리 | ||
const doList = todoList.filter((todo) => !todo.isCompleted); | ||
const doneList = todoList.filter((todo) => todo.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.
filter 사용해서 doList와 doneList에 나눠서 처리해주시는거 좋은 것같아요!🤩
|
||
//todoList가 변경될 때마다 localStorage에 저장 | ||
useEffect(() => { | ||
localStorage.setItem('todoList', JSON.stringify(todoList)); | ||
}, [todoList]); |
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.
useEffect 사용해서 재랜더링 조건 부여하신거 좋은 것 같습니다!
const newTodo = { | ||
createTime: Date.now(), |
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.
🙋질문 있습니다~!
혹시 id(key)의 변수명을 createTime
으로 명명하신 이유가 있을 까요?
지금 위치에서 변수명의 뜻을 바로 이해할 수 있지만,
해당 props를 받아서 사용하는 다른 컴포넌트에서는 id값이라고 명확하게 이해하기가 쉽지 않을 것 같아서요..!
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.
저도 같은 이유로 궁금합니다..!
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 GlobalStyles = () => ( | ||
<Global | ||
styles={css` | ||
@font-face { | ||
font-family: 'RixXladywatermelonR'; | ||
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/RixXladywatermelonR.woff2') | ||
format('woff2'); | ||
font-weight: normal; | ||
font-style: normal; | ||
} | ||
|
||
html { | ||
margin: 0; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100vh; | ||
} | ||
|
||
body { | ||
font-family: 'RixXladywatermelonR', sans-serif; | ||
max-width: 500px; | ||
padding: 10px; | ||
text-align: center; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
scrollbar-width: thin; | ||
margin: 0; | ||
} | ||
|
||
button { | ||
font-family: 'RixXladywatermelonR'; | ||
display: flex; | ||
justify-content: center; | ||
align-content: center; | ||
flex-wrap: wrap-reverse; | ||
} | ||
`} | ||
/> | ||
); |
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 newTodo = { | ||
createTime: Date.now(), |
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.
저도 같은 이유로 궁금합니다..!
content: todoInput, | ||
isCompleted: false, | ||
}; | ||
setTodoList([...todoList, newTodo]); |
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.
스프레드 연산자를 활용해서 추가하는 방법이 훨 간단하고 보기에도 편하네요👍
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를 직접 참조하여 사용하는 것보다는 콜백 함수를 작성하는 방식이 조금 더 안정적인 방식입니다 🙂
setTodoList([...todoList, newTodo]); | |
setTodoList((prev) => [...prev, newTodo]); |
그 이유가 궁금하다면...
프로젝트를 진행하다 보면 state 변경 -> 렌더링 -> state 변경 -> 렌더링 -> ...
과정을 반복하게 됩니다.
setState(state + 1)
은 현재의 state 값을 기반으로 상태를 업데이트 합니다. 하지만 이 방식은 state 값이 변경되는 과정에서 다른 setState 호출이 있을 경우, 이전 렌더링 시점의 상태를 기반으로 하기 때문에 최신 상태값을 반영하지 못할 수 있습니다.
반면 setState(prev => prev + 1)
은 콜백 함수를 사용하여 이전 상태(prev)를 기준으로 상태를 업데이트 합니다. 이 방법은 상태 업데이트가 비동기적으로 처리되는 동안에도 항상 최신 상태를 반영하므로, 상태가 여러 번 업데이트 될 때에도 정확한 결과를 보장한다고 합니다!
<ListLiStyle> | ||
{todo.isCompleted ? ( | ||
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle> | ||
) : ( | ||
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle> | ||
)} | ||
{todo.isCompleted ? ( | ||
<DoneSpanStyle>{todo.content}</DoneSpanStyle> | ||
) : ( | ||
<span>{todo.content}</span> | ||
)} | ||
<DeleteBtnStyle onClick={() => handleDelete(todo)}>x</DeleteBtnStyle> | ||
</ListLiStyle> |
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.
오 삼항연산자로 조건부 렌더링하니까 정말 간편하네요.. 이야 깔끔하다~ !
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.
안녕하세요 여은 님!
메모장 UI가 볼수록 귀엽네요 ㅎㅎ 네이밍 컨벤션도 잘 지켜 주시고, 코드도 간결하게 잘 작성해 주셔서 리뷰하기 굉장히 편했습니다!
이번주 미션도 고생하셨습니다 🙌
const GlobalStyles = () => ( | ||
<Global | ||
styles={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.
오호 Global
컴포넌트로 전역 스타일 설정하신 점 아주 좋습니다!!
이 부분을 별도의 컴포넌트로 분리하고 import해서 사용하면 더욱 깔끔할 것 같아요 👍🏻
function App() { | ||
return <div>React Todo</div>; | ||
const userName = '옹헤'; |
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.
상수화 하여 사용하신 점 아주 좋습니다!!
네이밍 컨벤션 측면이긴 하지만 상수값을 사용할 때에는 UPPER_SNAKE_CASE
를 사용한답니다! (참고)
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 App() { | |
return <div>React Todo</div>; | |
const userName = '옹헤'; | |
const USER_NAME = '옹헤'; | |
function App() { | |
// 생략 | |
} |
content: todoInput, | ||
isCompleted: false, | ||
}; | ||
setTodoList([...todoList, newTodo]); |
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를 직접 참조하여 사용하는 것보다는 콜백 함수를 작성하는 방식이 조금 더 안정적인 방식입니다 🙂
setTodoList([...todoList, newTodo]); | |
setTodoList((prev) => [...prev, newTodo]); |
그 이유가 궁금하다면...
프로젝트를 진행하다 보면 state 변경 -> 렌더링 -> state 변경 -> 렌더링 -> ...
과정을 반복하게 됩니다.
setState(state + 1)
은 현재의 state 값을 기반으로 상태를 업데이트 합니다. 하지만 이 방식은 state 값이 변경되는 과정에서 다른 setState 호출이 있을 경우, 이전 렌더링 시점의 상태를 기반으로 하기 때문에 최신 상태값을 반영하지 못할 수 있습니다.
반면 setState(prev => prev + 1)
은 콜백 함수를 사용하여 이전 상태(prev)를 기준으로 상태를 업데이트 합니다. 이 방법은 상태 업데이트가 비동기적으로 처리되는 동안에도 항상 최신 상태를 반영하므로, 상태가 여러 번 업데이트 될 때에도 정확한 결과를 보장한다고 합니다!
const newTodoList = todoList.filter((item) => { | ||
return item.createTime !== todo.createTime; | ||
}); |
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.
함수가 직접 표현식을 반환하는 경우에는 중괄호와 return
키워드를 생략하여 사용할 수 있습니다!
const newTodoList = todoList.filter((item) => { | |
return item.createTime !== todo.createTime; | |
}); | |
const newTodoList = todoList.filter( | |
item => item.createTime !== todo.createTime | |
); |
import styled from '@emotion/styled'; | ||
import { useState } from 'react'; | ||
|
||
const InputFormStyle = styled.form` |
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.
InputForm
컴포넌트가 메인이다 보니 스타일 관련 코드는 InputForm
아래에 위치시키는 게 좋을 것 같아요!
export default function InputForm({ handleAddTodo }) { | ||
const [todoInput, setTodoInput] = useState(''); | ||
|
||
const handleInputChange = (e) => { |
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.
네이밍 아주 좋습니다 👍🏻
{todo.isCompleted ? ( | ||
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle> | ||
) : ( | ||
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle> | ||
)} | ||
{todo.isCompleted ? ( | ||
<DoneSpanStyle>{todo.content}</DoneSpanStyle> | ||
) : ( | ||
<span>{todo.content}</span> | ||
)} |
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.
todo.isCompleted
값에 따라 렌더링 하는 부분을 2개로 나누어 작성하신 이유가 있으신가요?!
아래처럼 작성하시면 더욱 깔끔할 것 같아요! 👍🏻
{todo.isCompleted ? ( | |
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle> | |
) : ( | |
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle> | |
)} | |
{todo.isCompleted ? ( | |
<DoneSpanStyle>{todo.content}</DoneSpanStyle> | |
) : ( | |
<span>{todo.content}</span> | |
)} | |
{todo.isCompleted ? ( | |
<> | |
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle>{' '} | |
<DoneSpanStyle>{todo.content}</DoneSpanStyle> | |
</> | |
) : ( | |
<> | |
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle> | |
<span>{todo.content}</span> | |
</> | |
)} |
<SectionTitleStyle>📋 Todo ({doList.length})</SectionTitleStyle> | ||
<ListUlStyle> | ||
{doList.map((todo) => | ||
<TodoItem | ||
key={todo.createTime} | ||
todo={todo} | ||
handleComplete={handleComplete} | ||
handleDelete={handleDelete} | ||
/> | ||
)} | ||
</ListUlStyle> | ||
</SectionStyle> | ||
<SectionStyle> |
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.
요 부분을 컴포넌트로 한 번 더 분리해도 좋을 것 같아요!
미션 소감
안녕하세요, LG U+유레카 1기 프론트엔드 반 심여은입니다.
이번에는 저번 주차에 바닐라 js로 만들었던 todo 리스트를 리액트로 변환하는 작업을 해봤습니다. 직접 DOM 엘리먼트를 가져올 필요가 없어서 코드가 더 단순화되었고, 부모 컴포넌트인 App에서 자식 컴포넌트들에게 업데이트된 state를 넘기는 방식으로 진행하여 새로운 todoList를 렌더링하는 방식이 더 간편해졌습니다! 또한 css-in-js인 emotion 라이브러리로 스타일을 작성하니 컴포넌트 이름으로 스타일을 적용하여 클래스 이름이 중복될 일이 없어 좋았어요!
하지만 아직 컴포넌트의 랜더링 시점, useEffect의 실행 시점 등 상태에 대해서는 익숙하지 않은 부분이 많은 것 같네요ㅠㅠ 작성한 코드에 미숙한 부분이 많을텐데, 리뷰 마구 날려주시면 정말 감사하겠습니다!
+이전 js 코드에서 구현했던 기능이 현재 제대로 작동하지 않는 경우가 있습니다. 추후 수정해서 올리겠습니다! => 수정했습니다!
++ react-todo 배포했습니다! https://react-todo-ongheong.vercel.app/
미션에서 특별히 신경 쓴 부분들
KEY QUESTION
1. 지난주 미션과 비교했을 때, React로 구현하는 과정에서 어떤 점이 더 편리하거나 복잡했나요?
직접 DOM 엘리먼트를 조작하지 않아 const 변수들을 추가할 필요가 없어져 코드가 단순해졌습니다. 또한 부모 컴포넌트인 App에서 state, setState를 정의하고, 자식 컴포넌트들에게 업데이트된 state를 넘기는 방식으로 진행하여 새로운 todoList를 렌더링하는 방식이 더 간편해졌습니다!
하지만 원하는 리랜더링을 위해서 해당 컴포넌트가 가진 state를 변경하는 부분에 더욱 주의하게 되었습니다. 또한 props로 값을 전달할 때 자식에서 부모로 전달하는 방식을 생각하는 것이 쉽지 않았습니다🥲
2. React의 Virtual DOM(가상 돔)이란 무엇이고, 이를 사용했을 때의 장점은 무엇이 있나요?
3. React에서 state와 props는 무엇이고 어떻게 사용하나요?
4. React에서 컴포넌트를 분리하는 기준은 무엇일까요?
이 부분에 대해서는 사람마다 정의가 조금씩 다를 것이라고 생각합니다! 다음의 나열된 기준들 중에서 저는 재사용성과 단일 책임 원칙에 주의하며 분리하는 편입니다.