-
Notifications
You must be signed in to change notification settings - Fork 10
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주차] 김류원 미션 제출합니다. #6
base: master
Are you sure you want to change the base?
Conversation
src/components/Editor.jsx
Outdated
type="text" | ||
placeholder="새로운 Todo..." | ||
onChange={onChangeContent} | ||
onKeyDown={onkeydown} |
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.
onKeyUp={(e) => { if (e.key === 'Enter') { e.preventDefault(); // 기본 Enter 키 동작 막기 onSubmit(); } }}
이렇게 하시면 아마 되실 것 같아요! 그 차이가 onKeyDown, onKeyUp의 차이인데 onKeyUp은 키를 뗀 후에 트리거가 되어서 이미 input의 내용이 DOM에 적용된 후에 트리거가 발생합니다. 반대로 onKeyDown은 키를 누르는 순간에 트리거가 되기 때문에 글자가 아직 DOM에 반영되지 않았는데도 트리거가 될 수도 있는 경우가 있기에(그래서 가끔은 되는 경우도 있는 것 같습니다) 마지막 글자가 포함되는 것 같습니다!
https://codevil.tistory.com/277 참고자료도 남길게요!
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.
헉 너무 감사합니다ㅠㅠ 이거 때매 정말 뭐가 문제지 계속 생각했었는데...
onKeyDown보다 onKeyUp을 사용해야 할 거 같네요... !!!
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.
전체적으로 구조가 명확하고 깔끔하게 코드를 작성해주셔서 많이 배워가는 것 같습니다! 특히 상태 관리와 애니메이션 적용이 잘 되어있어서 배포 링크에서 테스트 해볼 때 눈이 즐거웠어요!
/* 모바일 반응형 */ | ||
@media (max-width: 768px) { | ||
height:100vh; | ||
width:100%; | ||
} | ||
} | ||
`; |
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 Input = styled.input` | ||
flex: 1; | ||
padding: 15px; | ||
border: 1px solid rgb(220, 220, 220); |
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.
Input 컴포넌트에 border 설정이 중복해서 들어가있는 것 같습니다! border를 none으로 설정하시려면 요 라인은 삭제하셔도 좋을 것 같아요!
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.
앗 제가 border를 넣고 이후에 None을 했네요ㅠㅜ 알려주셔서 감사합니다💗
const onChangeSearch = (e) => { | ||
setSearch(e.target.value); | ||
}; |
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 getFilteredDate = () => { | ||
if (search === "") { | ||
return todos; | ||
} | ||
return todos.filter((todos) => { | ||
return todos.content.toLowerCase().includes(search.toLowerCase()); | ||
}); | ||
}; |
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.
지금 getFilteredDate 함수를 보면 검색에 따라 todos를 필터링하는 기능을 수행하는 함수로 생각됩니다! 함수명을 getFilteredDate로 설정하셔서 이름만 보고는 날짜와 관련된 기능을 수행하는 것처럼 받아들여질 수도 있을 것 같아요! 함수/변수명 작명은 개발자의 평생 숙제죠...🥹 조금 더 직관적이고 가독성 높은 코드를 위해서 함수명을 getFilteredTodos와 같이 함수 기능에 더 가깝게 네이밍해보는 건 어떨까요?
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.
해당 함수를 수정하면서 변수명도 같이 수정을 했었나봐요ㅠㅠ 함수명 연관되게 작성하도록 명심하겠습니당!!
{...todos} | ||
onUpdate={onUpdate} | ||
todos={todos} | ||
setTodos={setTodos} |
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.
TodoItem 컴포넌트에 onUpdate와 removeTodo를 전달하여 투두 업데이트와 삭제를 처리하고 있기 때문에 setTodos를 전달하지 않아도 괜찮을 것 같습니다! 실제로 TodoItem 컴포넌트에서 setTodos를 사용하고 있지 않기도 하고요
// 할 일 추가 애니메이션 정의 | ||
const slideDownFadeIn = keyframes` | ||
from { | ||
opacity: 0; | ||
transform: translateY(-20px); | ||
} | ||
to { | ||
opacity: 1; | ||
transform: translateY(0); | ||
} | ||
`; | ||
|
||
// 할 일 삭제 애니메이션 정의 | ||
const fadeOutScaleDown = keyframes` | ||
from { | ||
opacity: 1; | ||
transform: scale(1); | ||
} | ||
to { | ||
opacity: 0; | ||
transform: scale(0.9) translateY(+10px); | ||
} | ||
`; |
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.
투두 추가 및 삭제 기능에 애니메이션을 넣으셔서 서비스의 완성도가 엄청 높아보여요! 저도 다음 과제에서는 애니메이션 기능을 한 번 추가해봐야겠네용👍👍
animation: ${({ isRemoving }) => | ||
isRemoving ? fadeOutScaleDown : slideDownFadeIn} | ||
0.3s ease-out forwards; |
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.
애니메이션에서 시간을 정의하는 부분이 하드코딩으로 들어가있는데 이번 과제처럼 간단한 애니메이션 구현에서는 전혀 문제가 되지 않지만 같은 수 또는 문자열 등을 여러 번 사용해야하는 프로젝트에서는 상수를 따로 정의해주는 게 가독성이나 유지보수 측면에서 훨씬 나을거예요! 위에서 사용하신 handleRemove 함수에서도 마찬가지로 상수를 사용하면 // 애니메이션이 끝난 후 200ms 대기 후 삭제
라는 주석도 쓰지 않아도 되겠죠!
animation: ${({ isRemoving }) => | |
isRemoving ? fadeOutScaleDown : slideDownFadeIn} | |
0.3s ease-out forwards; | |
const ANIMATION_TIME = 300; | |
animation: ${({ isRemoving }) => | |
isRemoving ? fadeOutScaleDown : slideDownFadeIn} | |
${ANIMATION_TIME}ms ease-out forwards; |
import styled, { keyframes } from "styled-components"; | ||
|
||
const TodoItem = ({ id, isDone, content, date, onUpdate, removeTodo }) => { | ||
const [isRemoving, setIsRemoving] = useState(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.
PR에 질문 남겨주신 부분이 이 부분인 것 같은데 제 생각에는 useRef를 사용하는 것이 리렌더링을 발생시키지 않아 효율적이지만 그렇기때문에 keyframes를 활용한 애니메이션의 상태가 제대로 반영되지 않아 애니메이션이 정상적으로 동작하지 않을 수도 있을 것 같아요! 저라면 그냥 useState로 관리할 것 같은데 더 효율적인 방법이 있을 수도 있으니 조금 더 공부해보고 괜찮은 자료를 찾으면 다시 공유하겠습니다!😃
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 mockDate = [ | ||
{ | ||
id: 0, | ||
isDone: false, | ||
content: "React 공부하기", | ||
date: new Date().getTime(), | ||
}, | ||
{ | ||
id: 1, | ||
isDone: false, | ||
content: "고양이 밥 주기", | ||
date: new Date().getTime(), | ||
}, | ||
{ | ||
id: 2, | ||
isDone: false, | ||
content: "고양이 놀아주기", | ||
date: new Date().getTime(), | ||
}, | ||
]; |
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.
모의 객체를 넣으신 것 같아요! (저두 류원님 고양이 만나고 싶어요🤍)
다만 보통 모의 객체는 데이터가 아직 준비되지 않은 상황이나, 서버가 연결되지 않은 상황에서 개발을 미리 진행하기 위해 넣는 것으로 알고 있습니다. 지금 투두리스트 프로젝트에서는 이미 할 일을 추가하고 삭제하는 기능이 잘 구현되어 있기 때문에 굳이 모의 객체가 있어야 하는지에 대해 살짝 의문이 듭니다! 더군다나 지금 모의 객체가 전부 고정된 데이터로 이루어져 있기 때문에 실제 해당 할 일을 하고 완료로 체크해도 새로고침하면 안 했다고 뜨게 됩니다. (is Done이 false로 고정되어 있으니까요..!) '투두리스트'라는 서비스의 목적을 고려하면 이 부분은 살짝 적절하지 않은 것 같습니다.
만약 저 세 가지 할 일을 단순히 모의 객체가 아니라 매일 하는 일들로 규정하고 싶으신 거면 아예 일반 투두리스트들과 선으로 분리해서 상단에 고정해놓으시는 것도 좋을 것 같아요! 그리고 mockDate라는 변수명은 배열의 내용을 제대로 설명하지 못하는 것 같습니다! mockTodos 등으로 수정해 보는 건 어떨까요?
const onChangeSearch = (e) => { | ||
setSearch(e.target.value); | ||
}; |
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 filteredTodos = getFilteredDate(); | ||
|
||
const { doneCount, notDoneCount } = useMemo(() => { |
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.
doneCount와 notDoneCount 계산 부분을 useMemo를 활용해서 효율적으로 코드를 작성해주신 것 같아요! todos 상태가 변경될 때만 계산이 일어나니까 불필요한 연산이 많이 줄어들 것 같습니다. 다만 지금 filteredTodos는 getFilteredDate() 함수에서 계속 계산되고 있는 것 같아요. 이 부분도 useMemo로 캐싱해서 todos나 search가 바뀔 때만 계산하도록 수정하면 더욱 좋을 것 같습니다..!!
const filteredTodos = getFilteredDate(); | |
const { doneCount, notDoneCount } = useMemo(() => { | |
const filteredTodos = useMemo(() => getFilteredDate(), [todos, search]); | |
const { doneCount, notDoneCount } = useMemo(() => { |
const handleRemove = () => { | ||
setIsRemoving(true); // 삭제 애니메이션 시작 | ||
setTimeout(() => { | ||
removeTodo(id); // 애니메이션 후 삭제 | ||
}, 200); // 애니메이션이 끝난 후 200ms 대기 후 삭제 | ||
}; |
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가 더욱 예뻐 보이네요! 투두 추가/삭제 이벤트에 맞춘 적절한 애니메이션이 잘 들어간 것 같습니다. 👍🏻
다만 지금은 setTimeout을 통해서 일정 시간 후에 투두를 삭제하는 방식인데, animationend 이벤트를 사용하시는 것도 좋은 방법일 것 같아요. 해당 이벤트는 css 애니메이션이 끝나는 지점을 감지하는 이벤트라 애니메이션이 끝나는 순간에 정확하게 삭제를 할 수 있어요! setTimeout로 하면 우려되는 단점이, 만약 나중에 애니메이션 시간을 변경하게 되면 setTimeout에 설정한 시간도 일일히 변경해야 하기 때문에 유지보수가 약간 번거로울 것 같습니다..!
animationend 이벤트를 사용하려면 TodoItme DOM 요소에 접근할 수 있도록 useRef로 참조를 설정해야 할 듯해서.. PR에 남겨주신 질문에 저는 useRef도 나쁘지 않은 선택이라고 답변 드리고 싶습니다..! (다만 저도 최적화에 대해 잘 아는 건 아니라 참고만 해주세요. 🥲)
<TodoItem | ||
key={todos.id} | ||
{...todos} | ||
onUpdate={onUpdate} | ||
todos={todos} | ||
setTodos={setTodos} | ||
removeTodo={removeTodo} | ||
/> |
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.
저도 같은 가빈 님과 같은 생각입니다! 지금 setTodos를 props로 내보내고 있는데, 실제 TodoItem 컴포넌트에서 setTodos를 사용하지 않고 있어요. 불필요한 props는 제거하는 게 좋을 것 같습니다.
그리고 {...todos} 부분과 todos={todos} 부분도 중복된 것 같습니다. 이미 {...todos}로 todos 안의 모든 속성들을 전달하고 있기 때문에 굳이 todos={todos}로 두 번 전달할 필요가 없어요..! 이 부분도 삭제하면 더욱 깔끔한 코드가 될 것 같습니다 🙃
<TodoItem | |
key={todos.id} | |
{...todos} | |
onUpdate={onUpdate} | |
todos={todos} | |
setTodos={setTodos} | |
removeTodo={removeTodo} | |
/> | |
<TodoItem | |
key={todos.id} | |
{...todos} | |
onUpdate={onUpdate} | |
removeTodo={removeTodo} | |
/> |
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 GlobalStyle = createGlobalStyle` | ||
body { | ||
background-color: #788bff; | ||
margin: 0; | ||
padding: 0; | ||
font-family: 'Arial', sans-serif; | ||
display:flex; | ||
height:100vh; | ||
justify-content:center; | ||
align-items:center; | ||
|
||
/* 모바일 반응형 */ | ||
@media (max-width: 768px) { | ||
height:100vh; | ||
width:100%; | ||
} | ||
} | ||
`; |
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.
styled-components 라이브러리에서 createGlobalStyle
함수를 이용하여 전역적으로 공통 css 를 적용하는 방법을 선택해 주셨네요!
류원님께서도 배포하신 element 개발자 도구 탭을 열어보시면 알겠지만, head 태그 내부에 <style data-styled="active" data-styled-version="6.1.13"></style>
라는 태그가 생긴 것을 알 수 있어요. 오히려 소스 탭에는 따로 css 코드가 존재 하지 않는 것까지 볼 수 있네요!
추후에 저희가 nextJS 과제를 진행할 때에 이렇게 클라이언트에서 새로운 style
과 같은 요소를 만드는 방식은 네트워크 환경이 느린 경우에는 약점으로 꼽힐 수도 있다는 점을 유의하시면 좋을 것 같아요!
또한 현재의 배포본에서는 페이지가 하나이고 요소도 많지 않기 때문에 app.jsx
파일 내에서 전역 스타일링을 정해주셨는데, 애플리케이션이 커지는 경우에는 이를 분리하는 것도 괜찮다고 생각합니다 :)
const onCreate = (content) => { | ||
dispatch({ | ||
type: "CREATE", | ||
data: { | ||
id: idRef.current++, | ||
isDone: false, | ||
content: content, | ||
date: new Date().getTime(), | ||
}, | ||
}); | ||
}; | ||
|
||
const onUpdate = (targetId) => { | ||
dispatch({ type: "UPDATE", targetId: targetId }); | ||
}; | ||
|
||
const removeTodo = (targetId) => { | ||
dispatch({ | ||
type: "DELETE", | ||
targetId: targetId, | ||
}); | ||
}; |
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.
현재 위의 onCreate
, onUpdate
, removeTodo
함수에서는 dispatch를 내부적으로 사용하고 있네요. 제가 류원님의 생각을 다 알 수는 없지만, dispatch
를 활용하는 함수들의 선언이기에 App 컴포넌트에서 이 함수들을 만들어준 뒤, 이들을 활용하는 Editor
, List
컴포넌트에 prop으로 넘겨주는 방식을 택하신 것 같아요.
물론 이 방식도 좋지만 어차피 prop 속성으로 무언가를 전달하실 것이라면 dispatch
함수를 내리고, 기능이 필요한 함수들은 컴포넌트 단에서 정의하여 사용하는 것이 더 낫다고 생각합니다.
물론 개발자가 깔끔하게 코드를 머릿 속에 넣고 있다면 좋겠지만, 다른 사람이 관련 코드를 다른 컴포넌트에 사용하고 수정한다면 관련된 컴포넌트들이 모두 영향을 받을 수도 있기 때문이에욥. 이에 관해서 나중에 얘기해보면 좋겠습니다 :)
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.
App컴포넌트에서 상태 관리 함수가 많아질 경우 useReducer 를 활용해서 컴포넌트 외부에 따로 정리하는 것으로 알고 있었습니다! 혹시 사용기준을 잘 못 이해하고 있을까요?ㅠ
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.
앗 아닙니다! 제가 말씀드린 의도는, useReducer()
훅 자체를 사용하시는 것은 app.jsx 파일 내에서 진행하되, onCreate
, onUpdate
, removeTodo
와 같은 함수들은 app 컴포넌트가 아닌 다른 컴포넌트에서 사용되고 있는 것 같아서 차라리 dispatch 함수들을 관련 컴포넌트에 넘긴 뒤, 기능을 자세히 구현한 함수를 거기에서 만들어지는 방식을 말씀드린 것이었어요.
아무래도 함수는 사용되는 컴포넌트 코드 쪽에서 선언, 활용이 모두 이루어지는 것이 나중에 가독성 측면에 있어서 더 좋다고 생각해서 그렇게 리뷰 달았습니다 😁
const [content, setContent] = useState(""); | ||
|
||
const contentRef = useRef(); |
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.
사용자가 아무 것도 입력하지 않은 상태에서 엔터를 눌러 입력하려고 할때, 여전히 dom 요소에 focus on 해주기 위해 useRef()
훅을, input의 양방향 바인딩을 위해 useState()
훅을 사용해주셨네요!
너무 좋은 활용법이고, 나중에 form을 다룰 때에 양방향 바인딩 외에 useRef()
를 통한 방식도 있다는 것을 공부해보시면 좋을 것 같습니다. 레퍼런스 남길게요 :)
const notDoneCount = totalCount - doneCount; | ||
|
||
return { | ||
totalCount, |
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.
혹시 totalCount
변수까지 구조 분해할당으로 반환해주시는 이유가 있을까요? 이는 따로 받아서 사용하고 있지 않는 것 같아서요!
💜배포링크💜
https://react-todo-20th-taupe.vercel.app/
해결 안 되는 문제 및 궁금한 점 OTL...
"ㅁㄴㅇㄹ"를 작성 후 엔터를 치면 "ㅁㄴㅇㄹ"과 "ㄹ" 두개가 추가되는 화면...ㅠㅠ
useMemo(), react.memo(), context 사용을 더 자유자재로 할 수 있도록 연습을 해야겠다고 느꼈습니다.
💗[Key Questions]💗
1. Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?
Virtual DOM은 JavaScript 객체로 메모리 상에 가상으로 존재하는 DOM 구조입니다. 실제 DOM과는 달리, UI를 효율적으로 업데이트하기 위해 사용하는 개념입니다. React와 같은 라이브러리에서 사용되며, 전체 UI를 다시 렌더링하지 않고 변경된 부분만 효율적으로 업데이트할 수 있게 해줍니다.
Virtual-DOM이 필요한 이유 : 웹 페이지의 특정 요소의 색상을 변경하려면, 해당 요소를 찾고 색상을 변경한 후, 그 변경 사항을 적용하기 위해 해당 요소부터 하위 요소까지 브라우저가 화면을 다시 그리는데 가장 많은 비용이 발생합니다. 즉, 리플로우(Reflow) 및 리페인트(Repaint)에 대한 비용이 많이 발생하게 됩니다. 위에서 말한 문제를 해결하기 위해서 React는 가상 돔(Virtual)이라는 개념을 도입했습니다.
React는 렌더링이 발생될 상황에 놓이게 되면 새로운 화면에 들어갈 내용이 담긴 가상 돔을 생성하게 됩니다.
가상 돔은 실제 DOM의 가벼운 복사본으로 메모리 상에 존재하며, JavaScript 객체 형태로 존재합니다.
리액트는 항상 렌더링 이전의 화면 구조와 렌더링 이후의 화면 구조를 가진 두 개의 가상 돔 객체를 유지하고 있습니다.
이 두 가상 돔을 비교하여 변경된 부분만 실제 DOM에 반영합니다.
렌더링 이전에 화면의 내용을 담고있는 첫번째 가상돔과 업데이트 이후에 발생할 두번째 가상돔을 비교해 정확히 어떤 Element가 변했는지를 비교합니다. 이를 리액트에선 Diffing이라고 표현합니다. Diffing은 효율적인 알고리즘을 사용해 진행되기 때문에 어떤 Element에 차이가 있는지를 매우 신속하게 파악할 수 있게 됩니다. 리액트는 이를 통해 차이가 발생한 부분만을 (브라우저상의) 실제 DOM에 적용하게 되는 것입니다. 이 과정을 Reconciliation(재조정)이라고 합니다. 이 과정이 매우 효율적인 이유는 변경된 모든 Element들을 집단화시켜 이를 한번에 실제 DOM에 적용하는 방식하는 방식이기 때문입니다.
Virtual DOM을 사용함으로써 얻는 이점 : 실제 DOM 조작은 비용이 많이 드는 작업입니다. Virtual DOM을 사용하면 변경된 부분만 DOM에 반영할 수 있기 때문에, 성능 저하를 방지하고 더욱 효율적인 렌더링을 할 수 있습니다. 또한 Virtual DOM을 사용하면, 복잡한 DOM 조작 코드 없이 React의 상태 관리와 컴포넌트 기반 구조를 활용하여 더 간결하고 읽기 쉬운 코드를 작성할 수 있습니다.
2. React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요. 다른 방식이 있다면 이에 대한 소개도 좋습니다.
동작 원리
React.memo()는 전달된 컴포넌트에 대한 프롭스를 **얕은 비교(shallow comparison)**를 통해 프롭스가 변경되지 않으면 리렌더링을 건너뜁니다. 따라서, 객체나 배열과 같은 참조형 데이터의 변경을 감지할 때는 참조값이 달라지지 않으면 리렌더링을 하지 않습니다.
주의사항
프롭스가 객체나 배열일 경우: 얕은 비교만 하기 때문에 참조형 데이터가 변경되지 않으면 렌더링이 발생하지 않을 수 있습니다. 이때는 객체나 배열이 변경될 때마다 새로운 참조값을 주어야 합니다.
복잡한 비교가 필요하다면 두 번째 인수로 커스텀 비교 함수를 사용할 수 있습니다.
부모 컴포넌트가 자주 렌더링되는데 자식 컴포넌트의 프롭스는 자주 변하지 않을 때 사용하면 효과적입니다.
비교적 많은 데이터를 받아 처리하는 컴포넌트에 사용하면 성능 최적화를 할 수 있습니다.
useMemo()는 값을 메모이제이션하여, 비싼 연산이 매번 수행되는 것을 방지하는 훅입니다. 주로 연산량이 큰 함수나 자주 변경되지 않는 값을 캐싱해서 성능을 최적화합니다. React는 의존성 배열을 비교하여, 값이 변경되지 않으면 이전 값을 재사용합니다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
동작 원리
useMemo()는 특정 값이 변경되었을 때만 메모이제이션된 값을 다시 계산합니다. 즉, 의존성 배열 [a, b] 안에 있는 값이 변경되지 않으면 computeExpensiveValue(a, b) 함수가 재실행되지 않고 이전의 계산된 값을 반환합니다.
주의사항
useMemo()는 메모이제이션된 값을 반환합니다. (메모이제이션된 함수가 아님)
메모이제이션된 값이 자주 변하지 않는다면 유용하지만, 자주 변하는 값이라면 오히려 성능이 저하될 수 있습니다. 의존성 배열에 전달된 값들이 변경되면 매번 연산이 이루어지기 때문입니다.
언제 사용하나?
연산 비용이 큰 계산을 할 때 사용하면 성능 최적화에 유리합니다. 예를 들어, 복잡한 필터링, 정렬, 데이터 변환 로직 등을 메모이제이션할 수 있습니다.
동일한 결과를 반복해서 사용하는 경우 유용합니다. 불필요한 반복 연산을 방지할 수 있습니다.
동작 원리
useCallback()은 의존성 배열에 지정된 값이 변경되지 않는 한, 같은 함수 참조값을 반환합니다. 즉, 이전에 생성된 함수를 재사용하게 됩니다.
주의사항
함수를 프롭스로 전달하는 경우: 자식 컴포넌트가 React.memo()로 최적화되어 있더라도, 부모 컴포넌트가 리렌더링되면 자식에게 전달되는 함수도 새롭게 생성됩니다. 이때 useCallback()을 사용하면 이전에 생성된 함수 참조값을 재사용하므로 자식 컴포넌트의 불필요한 리렌더링을 방지할 수 있습니다.
메모이제이션 자체가 오버헤드일 수 있으므로, 함수가 간단하거나 자주 변경되는 상황에서는 오히려 성능이 저하될 수 있습니다.
언제 사용하나?
자식 컴포넌트가 React.memo()로 최적화된 경우: 부모 컴포넌트의 리렌더링 시, 자식 컴포넌트에게 전달되는 함수가 매번 새로 생성되는 것을 방지하기 위해 사용합니다.
함수를 의존성으로 전달해야 하는 경우: useEffect 등 훅에서 함수가 의존성 배열에 있을 때, 그 함수가 불필요하게 매번 재생성되지 않도록 메모이제이션할 수 있습니다.
React 컴포넌트 생명주기는 컴포넌트가 생성되고, 업데이트되고, 제거되는 과정을 나타내는 일련의 단계입니다. 이 생명주기는 함수형 컴포넌트와 클래스형 컴포넌트에서 약간 다르게 관리되며, React의 중요한 개념 중 하나입니다. 각각의 생명주기 단계는 특정 작업을 수행하기에 적합한 시기를 제공하여, 컴포넌트의 상태 관리나 외부 리소스와의 상호작용을 더 효율적으로 할 수 있습니다.
생명주기는 크게 세 단계로 나눌 수 있습니다:
마운팅(Mounting): 컴포넌트가 DOM에 처음으로 삽입되는 단계.
업데이트(Updating): 상태나 프롭스가 변경되어 리렌더링이 발생하는 단계.
언마운팅(Unmounting): 컴포넌트가 DOM에서 제거되는 단계.