-
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주차] 강다혜 미션 제출합니다. #9
base: master
Are you sure you want to change the base?
Conversation
Feature/setup
Feature/basic layout
Feature/add subject
Feature/remove subject
Feature/add task
feat: remove task
Feature/toggle task
Feature/error handle & add counter
Feature/use local storage
Refactor/codebase
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.
체고!! 많이 고민하신게 느껴지는 것 같아요
@@ -0,0 +1,17 @@ | |||
import { Title } from './styles'; | |||
|
|||
export default function Header() { |
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.
jsx 문법의 react component는 파일 확장자가 jsx인게 조금더 다른 순수 js랑 나중에 구분하기 편할 것 같아요!
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.
헉 그렇네용 다음 플젝부터는 jsx를 명시해야겠어요!!
<Container> | ||
<Header> | ||
<h3> | ||
{subject.title}{' '} |
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.
요런 {' ' } 친구는 지워주셔도 좋을 것 같아용!
import TaskList from 'components/TaskList'; | ||
import { Container, Header, TaskCount } from './styles'; | ||
|
||
export default function Subject({ state, subject, deleteSubject, taskHooks }) { |
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.
보니까 subject props가 object 형식인 것 같은데,
const { id, taskList, title } = subject
처럼 구조분해할당으로 시작하셔도 좋을 것 같아요!
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 onAdd(event) { | ||
event.preventDefault(); | ||
if (!inputRef.current.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.
input부분 ref로 쓰신거 진짜 자잘한 건데 많이 고민하신 것 같아요! 쨩쨩
const inputRef = useRef(null); | ||
|
||
function onAdd(event) { | ||
event.preventDefault(); |
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.
아 맨날 까먹는 이것,,ㅠㅠㅠ
} from './styles'; | ||
|
||
export default function KanbanBoard() { | ||
const subjectHooks = useSubject(); |
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 currentSubjectList = subjectList[state]; // state in which current state is located | ||
const currentSubject = currentSubjectList.find( | ||
(subject) => subject.id === subjectId | ||
); |
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 currentSubjectList = subjectList[state]; // state in which current state is located | |
const currentSubject = currentSubjectList.find( | |
(subject) => subject.id === subjectId | |
); | |
const currentSubject =subjectList[state]?.find( | |
(subject) => subject.id === subjected | |
); | |
if(!currentSubject) 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.
코드 짜신 방식이 저와 달라 새로워서 흥미롭게 보았습니다! subject 내에 또다시 task를 추가하는 형식이 복잡하셨을텐데 구현하시느라 수고많으셨습니다!👍🔥
"htmlWhitespaceSensitivity": "css", | ||
"cssWhitespaceSensitivity": "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.
프리티어 속성 찾아보면서 못 봤던 속성값인데 덕분에 찾아보고 알아갑니다👍👍
gap: 1rem; | ||
|
||
padding: 2rem; | ||
width: 100%; | ||
max-width: 1200px; | ||
|
||
@media (max-width: 1000px) { | ||
max-width: 600px; | ||
} |
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.
rem 단위와 px 단위를 혼용하신 이유가 특별히 있는지 궁금합니다!
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.
미디어 쿼리는 개발자 도구에서 화면을 조정할 때 나타나는 px 값을 참고해서 사용하다 보니 혼용된 것 같아요! 미디어 쿼리에서는 보통 px 단위를 많이 사용하는 것 같은데, rem을 사용하는 것도 여러 장점이 있다고 하네요.
저는 이 링크를 읽어봤어요! : https://onlydev.tistory.com/128
지원님도 px과 rem을 혼용하셨던데 지원님은 어떤 이유로 같이 사용하고 계신가요?
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.
저는 rem 단위로 모두 통일하고자 했는데 혹시 px을 발견하셨다면 제가 깜빡 익숙한 px로 잘못 쓴게 아닐까..싶습니다 😅😅 달아주신 링크도 잘 읽어보았습니다! 감사합니다
return ( | ||
<header> | ||
<Title>{getCurrentDate()}</Title> | ||
</header> | ||
); | ||
} | ||
|
||
function getCurrentDate() { | ||
return new Intl.DateTimeFormat('ko-KR', { |
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문을 먼저 작성하고 그 뒤에 함수를 정의하는 방식이 특이한 것 같아 찾아보았는데, 간단한 컴포넌트이고 규모가 작아서 UI구조를 바로 볼 수 있다는 점에서 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.
맞아요 또는 호이스팅 관련해서 오류가 생길 수도 있다고 들었는데, 이 부분에 대해서도 더 알아보겠습니당!
const newSubjectList = { | ||
...subjectList, | ||
[state]: subjectList[state].map((subject) => | ||
subject.id !== subjectId ? { ...subject } : newSubject | ||
), | ||
}; |
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 newSubjectList = { | |
...subjectList, | |
[state]: subjectList[state].map((subject) => | |
subject.id !== subjectId ? { ...subject } : newSubject | |
), | |
}; | |
const newSubjectList = { | |
...subjectList, | |
[state]: subjectList[state].map((subject) => | |
subject.id == subjectId ? { ...subject } : newSubject | |
), | |
}; |
배포된 링크에서 subject 내에 task 추가가 안되고 있어서 이래저래 디버깅해보다가 위와 같이 바꾸니 제대로 뜨는 것을 확인했습니다! 새로 추가할 task의 subject id가 동일한 해당 subject 내의 태스크를 업데이트 해야한다고 저는 생각해서 수정해보았는데 처음 코드 짜신 의도까진 파악이 안되어서 한 번 더 직접 디버깅 해보시면 좋을 것 같습니다! 😊
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.
🤦🤦 사실 목표 subject의 id를 찾아서 변경하는거니 원래 코드대로 subject.id !== subjectId ? { ...subject } : newSubject
(이것도 subject.id !== subjectId ? subject : newSubject)
로 쓰면 되긴합니당,,)라고 쓰면 되는데
더 엄청난 문제가 있었네요... 해당 함수의 인자로 subjectId
를 넘겨주지 않아서 subjectId
= undefined인 채로 조건문이 돌아 변경이 안됐습니다🤣🤣🤣
꼼꼼히 확인해주셔서 감사합니다😭😭😭😭
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.
|
||
setSubjectList(newSubjectList); | ||
localStorage.setItem(KEY, JSON.stringify(newSubjectList)); | ||
toggleSubjectState(state, newState, currentSubjectList, newSubject); |
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.
이 부분에서, subjectId를 전달하지 않아서 상태 업데이트가 제대로 이루어지지 않는 것 같습니다!! 이 부분이 누락되어 있으면,
할일을 삭제 버튼을 눌러도 삭제된 상태가 반영이 안되는 것 같아요!!
toggleSubjectState(state, newState, currentSubjectList, newSubject,subjectId);
이런식으로 수정이 필요할 것 같습니다!
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.
헉 디버깅 하시느라 너무고생하셨어요...!🥹🥹🥹
|
||
setSubjectList(newSubjectList); | ||
localStorage.setItem(KEY, JSON.stringify(newSubjectList)); | ||
toggleSubjectState(state, newState, currentSubjectList, newSubject); |
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.
할일 삭제와 마찬가지로 이부분에서도 subjectid가 전달이 안되어서 할일을 추가해도 반영이 안되는 것 같습니다!!
toggleSubjectState(
state,
newState,
currentSubjectList,
newSubject,
subjectId
);
이런식으로 수정해야할 것 같습니다!
|
||
export default function useSubject() { | ||
const [subjectList, setSubjectList] = useState([]); | ||
|
||
const addSubject = (newSubjectTitle) => { | ||
const newSubject = { | ||
id: uuidv4(), |
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를 생성하기 위해 설계된 라이브러리가 있는 줄 처음 알았어요! 배워갑니다!
🌊 결과물
배포 링크 :
https://react-todo-20th-six.vercel.app/
기능 구현
🌊 후기
useSubject
훅 안에 목표 관련 함수들과 할 일 관련 함수들을 몰아넣었습니다. 대신 hook 사용 시const { subjectList, addSubject, deleteSubject, ...taskHooks } = subjectHooks;
,const { addTaskToSubject, deleteTaskFromSubject, toggleTaskInSubject } = taskHooks;
처럼 subjectHook 부분과 taskHook 부분을 각각 묶어서 props로 전달할 때 갯수가 너무 많아지지 않도록 했습니다.useSubject
훅을 호출하는 컴포넌트와, 호출 결과로 받은 배열을 이용해 실제로 데이터를 표시하는 컴포넌트 사이가 멀어지면서 엄청난 props drilling이 일어나고 말았습니다🫠 이 부분은 다음에 리팩토링하면서 전역 상태 관리 라이브러리를 붙이면 해결될 것 같습니다.memo
,useMemo
,useCallback
등을 이용한 최적화였지만... 저는 최적화는 따로 적용하지 않았습니다😭 투두리스트는 비교적 간단한 앱이라서 최적화를 따로 하지 않아도 될 것 같다는 판단이었는데, 나중에 저런 최적화를 적용하고 성능 측정도 해보고 싶네요😋🌊 Key Questions
Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?
Virtual DOM은 UI의 가상적인 표현이 메모리에 저장되고, 브라우저의 실제 DOM과 동기화하는 프로그래밍 개념이다.
장점
isActive
상태를 기반으로class
를 적절하게 변경할 수 있다.과정
가상 DOM을 실제 DOM에 동기화하는 과정을 재조정(reconciliation) 이라고 하고, 여러 방법이 있다. React에서는 내부적으로
ReactDom
이라는 라이브러리를 사용해서 가상 DOM과 실제 DOM을 동기화시킨다. Vanilla JS에서도 Virtual DOM을 이용하고 싶다면Snabbdom
같은 라이브러리를 이용할 수 있다.재조정은 아래와 같은 방식으로 진행된다.
상태 변경(State Change): 상태가 변경될 때마다 (setState 등을 통해) DOM 트리가 다시 생성된다. 즉, 메모리에는 두 개의 가상 DOM 트리가 동시에 존재하게 된다.
비교(Diffing): 전의 Virtual DOM과 새로운 Virtual DOM을 비교하여 어떤 부분이 변경되었는지 확인한다.
리렌더링(Re-render / Patching): 변경 사항이 실제 DOM에 적용된다. 이때 React는 실제 DOM을 업데이트하는 데 필요한 최소한의 작업 수를 찾아내고, 변경 사항을 한 번에 모아서 처리한다.
React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요. 다른 방식이 있다면 이에 대한 소개도 좋습니다.
memo()
,useMemo()
,useCallback()
을 이용하면 컴포넌트의 불필요한 렌더링을 방지함으로써, 성능을 높일 수 있다.memo
memo
를 사용하면 컴포넌트의 props가 변경되지 않은 경우, 리렌더링을 건너뛸 수 있다.React는 일반적으로 부모 컴포넌트가 리렌더링될 때, 자식 컴포넌트도 함께 리렌더링된다. 하지만 자식 컴포넌트로 전달되는 props가 이전과 동일하다면, 자식 컴포넌트는 리렌더링되지 않도록 할 수 있다.
호출
SomeComponent
: 첫 번째 인자로는 메모제이션하고자 하는 컴포넌트를 넘겨준다.arePropsEqual
: 필요한 경우, 두 번째 인자로는 사용자 정의 비교 함수를 넘겨줄 수 있다.true
를, 아닌 경우false
를 반환한다.주의점
memo
는 기본적으로 props를 얕은 비교(shallow comparison)로 비교한다. 따라서 객체나 배열을 props로 전달한 경우, 내용이 같더라도 참조가 변경되면 다시 렌더링이 발생할 수 있다.이런 경우, 위에서 언급한 사용자 정의 비교 함수를 통해 이전 배열과 새로운 배열을 비교하도록 정할 수 있다.
useMemo
useMemo
는 리렌더링 사이에 계산 결과를 캐싱할 수 있게 해주는 React Hook이다.호출
calculateValue
: 캐싱할 값을 계산하는 함수이다.calculateValue
함수가 호출된다.dependencies
가 변경되지 않았다면 다음 렌더링에서는calculateValue
함수 호출 없이 동일한 값을 반환하도록 값을 저장한다.dependencies
:calculateValue
함수 안에서 참조된 모든 값들의 목록이다. 해당 값들이 변경된 경우, 다음 렌더링에서는 새로calculateValue
함수를 호출해 값을 재계산하게 된다.주의점
useMemo
는 Hook이기 때문에 컴포넌트의 최상위 레벨 또는 자체 Hook에서만 호출할 수 있다.useCallback
리렌더링 사이에 함수 정의를 캐싱할 수 있게 해주는 React Hook이다.
호출
fn
: 캐싱할 함수이다.fn
함수를 반환한다. (호출하는 것은 아니다.)dependencies
가 변경되지 않았다면 다음 렌더링에서는 같은 함수를 다시 반환한다.dependencies
:fn
함수 안에서 참조된 모든 값들의 목록이다.주의점
useCallback
는 Hook이기 때문에 컴포넌트의 최상위 레벨 또는 자체 Hook에서만 호출할 수 있다.React 컴포넌트 생명주기에 대해서 설명해주세요.
컴포넌트의 생명주기(LifeCycle)이란, 컴포넌트가 생성되고 제거되기까지의 여러 단계를 이른다.
각 생명주이게서는 특정한 메소드가 호출되며, 이러한 메소드들을 오버라이딩할 수도 있다.
Mouting (생성 단계)
constructor
static getDerivedStateFromProps
render
componentDidMount
Updating (업데이트 단계)
static getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
Unmouting (제거 단계)
componentWillUnmount
Error Handling
static getDerivedStateFromError
componentDidCatch
함수 컴포넌트에서는 Hook을 이용하여 상태 및 생명주기 기능을 사용할 수 있다.
useState
useEffect
useContext
useReducer
useMemo
useCallback
useRef
useLayoutEffect
참고