Skip to content
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주차] 조유담 미션 제출합니다 #8

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

youdame
Copy link

@youdame youdame commented Mar 22, 2024

배포 링크

구현 기능

지난 주차와 같습니다.
이번 주에 바빠서 추가 기능 구현을 못했는데, 시간이 되면 스터디 전까지 추가하고 싶습니다..!!
스타일드 컴포넌트를 너무 오랜만에 사용해서 ㅋㅋ 낯설더라구요

부가 설명

  1. GlobalStyle 컴포넌트를 이용해서 reset, global css 적용
  • 근데 여기에 reset css까지 넣는 게 맞나 싶어요..
  • 찾아보니 styled-reset 패키지를 설치하면 자동으로 reset.css가 적용된다고 하네요.
  1. useReducer를 이용한 상태 관리
  • todo 추가, 이동, 삭제와 같은 모든 상태 변경 로직을 옮겨 한 곳에서 관리할 수 있기 때문에 useReducer를 사용했습니다
  • context API로 전역 상태를 관리할까 했지만, 제 코드에서는 prop drilling이 심하지 않아 사용하지 않았습니다.
  1. local 폰트 사용
  • 웹 폰트보다는 local 폰트가 가볍기 때문에 적용했습니다.
  • 또한, 더 용량을 줄이려면 subset 추출을 해야하는데 시간 상 거기까지 하진 못했습니다.
  1. 절대 경로 사용
  • import 시 ../../ 이런 게 싫어서 jsconfig.json에서 절대경로 지정해줬습니다.
  1. eslint 설치
  • eslint를 설치하고, 설정파일을 생성했습다.
  • prettier와의 충돌을 막기 위해 eslint-config-prettier를 설치했습니다.

=> 이해하기 쉬운 코드를 짜려고 노력하긴 했는데, 혹시라도 이해 안 가시는 부분이 있다면 질문해주세요 ~!

Key Questions

Virtual-DOM은 무엇이고, 이를 사용함으로써 얻는 이점은 무엇인가요?

  • Virtual DOM
    Virtual DOM (VDOM)은 UI의 이상적인 또는 “가상”적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화하는 프로그래밍 개념입니다. 이 과정을 재조정(Reconciliation)이라고 합니다.

이 접근방식이 React의 선언적 API를 가능하게 합니다. React에게 원하는 UI의 상태를 알려주면 DOM이 그 상태와 일치하도록 합니다. 이러한 방식은 앱 구축에 사용해야 하는 어트리뷰트 조작, 이벤트 처리, 수동 DOM 업데이트를 추상화합니다.

리액트에서 Virtual DOM 사용하는 이유

리액트 이전

기존 웹페이지에서 서버와 통신하며 요청을 주고 받으면, 받은 데이터를 DOM 객체에 속성값들을 변화시키거나 DOM을 추가 또는 제거하도록 만들었습니다. 그리고 이러한 변경이 있을 때마다 DOM 객체의 변경에 대한 코드를 만들어줘야 했습니다. 뿐만 아니라 DOM 객체에 대한 연산이 많아질수록 reflow, repaint 연산이 많아지면서 브라우저가 컴퓨팅 자원을 많이 사용해야 했습니다.

reflow, repaint

reflow란 화면구조가 변경되었을 때 문서의 일부 또는 전체를 다시 렌더링하기 위해 문서에서 요소의 위치와 크기등의 형상을 다시 계산하는 것을 의미합니다. 대표적으로 화면의 크기 변화, 페이지 초기 렌더링, DOM의 동적 변화가 있는 경우 reflow가 발생합니다. 또한 경우에 따라서 문서의 단일 요소를 reflow하려면 상위 요소와 그 뒤에 오는 모든 요소를 reflow해야 할 수 도 있습니다.

repaint란 이름에서 알 수 있듯이 요소의 보이는 것에 영향을 주지만 레이아웃에는 영향을 주지 않는 요소 변경으로 화면에 다시 그려주는 작업입니다. reflow가 발생하면 변경된 렌더 트리를 화면에 다시 그려야해서 repaint가 발생합니다. 또한 레이아웃에 영향을 주지 않지만 color, background-color, visibility 등과 같은 스타일 속성의 변경에는 reflow는 발생하지 않고, repaint만 발생합니다.

살펴본 reflow, repaint는 컴퓨팅 파워를 많이 필요로 하는 비싼 작업입니다. reflow, repaint가 많이 일어날수록 하드웨어에 많은 부담을 주게 되고, 그 결과 사용자 경험이 나빠질 수 있습니다.

리액트의 문제 해결

이를 해결하기 위해 리액트는 Virtual DOM을 활용해 UI의 이상적인 “가상”적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화 합니다. 이 과정을 재조정(Reconciliation)이라고 합니다.
이렇게 연산이 끝난 뒤 최종적인 변화를 실제 DOM에 반영해서 여러 번 reflow, repaint가 발생할 수 있는 작업을 한번으로 줄여줍니다. 또한, Virtual DOM은 변화에 대한 관리를 자동화하고 추상화해서 작업의 편의성을 제공합니다.
재조정에 대해 조금 더 알아보면, 기존에 하나의 트리를 가지고 다른 트리로 변환하기 위한 최소한의 연산 수를 구하는 최첨단 알고리즘도 n개의 엘리먼트가 있는 트리에 대해 O(n^3)의 복잡도를 가지고 있는 한계가 있었습니다. 이에 대해 리액트는 1) 서로 다른 두 엘리먼트는 서로 다른 트리를 만들어낸다. 2) 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있습니다. 두 가정을 기반하여 O(n) 복잡도의 비교 알고리즘을 구현했습니다.

참고 자료

참고자료 1
참고자료 2
참고자료 3
참고자료 4
참고자료 5

미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요?

재사용성

리액트는 컴포넌트 기반 아키텍쳐입니다. 예를 들어 UI 개발을 레고라고 한다면, 컴포넌트는 블록이라고 생각하면 됩니다. 블록을 조립해 하나의 완성품을 만드는 것과 같습니다. 이런 특징은 컴포넌트를 여러 곳에서 재사용할 수 있게 함으로써 생산성과 유지보수를 용이하게 합니다.

JSX 문법

JSX(Javascript + xml)는 자바스크립트에 대한 확장 구문으로서, 리액트에서 element(요소)를 제공해 줍니다. 따라서 익숙한 HTML문법과 유사한 JSX를 통해 컴포넌트를 생성할 수 있게 됩니다.

바닐라 자바스크립트의 단점을 커버

자바스크립트를 이용해서 DOM을 제어하다보면 DOM을 선택하고, 특정 이벤트가 발생하면 변화를 주도록 만들어야합니다. 사용자 인터랙션이 별로 없는 서비스라면 상관없지만, 인터랙션이 자주 발생하고 동적으로 UI를 표현해야한다면 규칙이 다양해지고 관리하기 어려워집니다. 즉, 처리해야할 이벤트와 상태값, DOM이 다양해지면 업데이트 하는 규칙이 복잡해지게 됩니다.
따라서 리액트는 상태가 바뀌면 컴포넌트를 날려버리고 리렌더링해서 화면을 그려주면 어떨까?라는 아이디어에서 생겨난 라이브러리입니다.

React에서 상태란 무엇이고 어떻게 관리할 수 있을까요?

기본적으로 useState와 useReducer로 상태를 관리할 수 있습니다.
리액트는 단방향으로 데이터 흐름이 진행되며 이를 바탕으로 flux 패턴이 등장하게 됩니다. 이 flux 패턴을 구현하기 위해 만들어진 라이브러리가 리덕스입니다. React query와 swr는 http 요청에 특화된 상태관리 라이브러리입니다. 이외에도 recoil, zustand, jotai, valtio등이 존재합니다.

Styled-Components 사용 후기 (CSS와 비교)

CSS-in-JS란 자바스크립트 또는 타입스크립트 코드에서 직접 CSS를 작성하여 스타일링 하는 방법을 말합니다. 저는 개인적으로, 스타일링을 위한 컴포넌트와, 실제 컴포넌트가 육안으로 분리되지 않는다는 점에서 스타일드 컴포넌트를 별로 좋아하지 않습니다..ㅎㅎ css에 js 로직이 섞여있는 것도 싫더라구요.

👍장점

▪️ 지역 스코프 스타일 : CSS로 스타일링 할 경우 전역 스코프 스타일에 영향을 미치기 때문에 클래스 이름이 중복되는 일이 없도록 신경써야하고, 어떤 CSS파일에서 스타일링 적용된 것인지 찾는 것도 어려울 수 있습니다. CSS-in-JS는 기본적으로 스타일을 지역 스코프로 지정하여 이 문제를 완전히 해결합니다.
▪️ 자바스크립트 변수를 style에 사용 : 스타일 규칙을 작성할 때 스크립트 변수를 참조할 수 있습니다. 동일한 상수를 CSS 변수와 자바스크립트 상수로 나눠 정의할 필요가 없기 때문에, 때에 따라 스타일에서 중복을 줄일 수 있습니다.

👎단점

▪️ CSS-in-JS는 런타임 오버헤드를 더합니다 : 컴포넌트를 렌더링 할 때 CSS-in-JS 라이브러리는 document에 삽입할 수 있는 일반 CSS로 스타일 데이터를 변환해야 합니다. 이 과정에서 추가 CPU를 차지하고, 컴포넌트의 복잡도에 따라 성능에 영향을 줍니다.
▪️ CSS-in-JS는 번들 크기를 늘립니다 : 사용자는 CSS-in-JS 라이브러리를 다운로드 해야 합니다. 용량이 엄청 크진 않지만, 라이브러리에 따라서 8~13kB 정도의 용량이 추가됩니다.
▪️ CSS-in-JS를 사용하면 특히 SSR 또는 컴포넌트 라이브러리를 사용할 때 오류가능성이 커집니다.

참고 자료
참고자료 1
참고자료 2

Copy link

@noeyeyh noeyeyh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 유담님!

제가 자주 쓰던 방식으로만 코드 작성하다가
유담님 코드 보고 폰트 적용부터, 절대 경로 설정까지 새롭게 느낀 부분이 많았어요!
사용자 이름 설정 기능까지 섬세하게 구현해주셨더라구요..! 많이 배우고 갑니다.!!

스터디 시간 때 봬요!!

dispatch({
type: 'ADD_TODO',
payload: {
id: uuid(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 Date.now()로 현재 시간을 ID로 지정해줬는데요..!
동시에 여러 ID를 생성해야 하는 경우가 있다면 uuid 함수를 사용하는 것이 고유성 측면에서 더 적합한 것 같네요. 유담님 코드 보고 배우고 갑니다!

localStorage.setItem('userName', nameInput);
setUserName(nameInput);
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용자 이름 설정 기능까지 추가로 구현하시다니..!
그런데 사용자 이름 설정에서 취소 버튼을 누르면 localStorage에 문자열 null로 저장이 되어서요,
사용자가 취소 버튼을 눌렀을 때 null을 반환하는 것을 감지하고, 이 경우 localStorage에 값을 저장하지 않으며, 이름 입력을 다시 요청하는 로직이 있으면 좋을 것 같아요! 공백 예외 처리도 있으면 좋을 것 같아요~

import { flexCenter, flexColumn } from 'styles/commonStyle';
import { FaRegCheckCircle } from 'react-icons/fa';
import { FaRegCircle } from 'react-icons/fa';
import { FaRegTrashAlt } from 'react-icons/fa';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 항상 이미지로 사용했는데 유담님처럼 react-icons 라이브러리를 사용하는 방법도 좋은 것 같아요!
크게 차이는 없지만 위 코드 import { flexCenter, flexColumn } from 'styles/commonStyle'; 같이 세 개 아이콘 한번에 import해도 좋을 것 같아요!

export const flexColumn = css`
display: flex;
flex-direction: column;
`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공통으로 사용되는 스타일을 따로 작성한 부분이 유지 보수에 좋은 것 같아요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다~! 코드 가독성 향상에도 큰 도움이 될 것 같아요!

"compilerOptions": {
"baseUrl": "src"
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

절대경로를 설정해주셔서 매번 상대 경로를 작성하어 import하는 것보다 편리한 것 같아요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

절대 경로 설정 배워갑니다! 다음 과제부터 적용해봐야겠어요!

margin-left: 0.4rem;

visibility: ${({ $isError }) => ($isError ? 'visible' : 'hidden')};
`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조건부 연산자를 이용한 동적 스타일링 좋은 것 같아요~

@@ -0,0 +1,171 @@
import { createGlobalStyle } from 'styled-components';
import MeetmeRegular from 'font/meetme.woff';
Copy link

@noeyeyh noeyeyh Mar 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 항상 <link>를 사용하여 외부에서 폰트를 불러오는 방법으로 적용시켰는데요,
유담님 코드 보고 웹 폰트 파일 meetme.woff를 직접 JavaScript 모듈로 가져와서 사용하는 방법을 알게 되었어요!
외부 서비스에 대한 의존이 없어 프로젝트에 직접 폰트 파일을 포함시키는 것도 좋은 것 같아요~

Copy link

@geeoneee geeoneee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유담님 안녕하세요! 프론트엔드 운영진 김지원입니다 😀
깔끔한 코드와 컴포넌트 분리로 코드 리뷰하기 수월했습니다:))
과제 하느라 수고 많으셨어요! 다음 과제도 파이팅!!👍🏻

font: inherit;
color: inherit;

}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

css reset 해주신 점 너무 좋네요~

if (!userName) {
const nameInput = prompt('이름을 알려주세요!');
localStorage.setItem('userName', nameInput);
setUserName(nameInput);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용자 이름 설정 기능 신박하고 좋네요~~

<input className="input" placeholder="할 일 입력" value={inputValue} onChange={handleInputValueChange} />
<button className="input-button">입력</button>
</InputWrapper>
<ErrorMessage $isError={isError}>할 일을 입력해주세요</ErrorMessage>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

입력값이 없을 때 에러 메시지 띄우는 디테일 너무 좋네요~👍🏻

}
&:hover .trash-icon {
visibility: visible;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

할 일이 길게 입력되면 이런식으로 아이콘이 작아지는 것 같아요!
할 일 텍스트에 max-width를 설정하거나 아이콘 크기를 고정해서 해결하면 될 것 같습니다:)
스크린샷 2024-03-24 오전 12 47 41

Comment on lines +70 to +74
.input {
width: 50rem;
padding: 1rem;
border-radius: 1rem;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런식으로 영어가 길게 입력되었을 때 깨지고 있어요!
word-break: break-all로 해결할 수 있답니당

스크린샷 2024-03-24 오전 1 06 32

Suggested change
.input {
width: 50rem;
padding: 1rem;
border-radius: 1rem;
}
.input {
width: 50rem;
padding: 1rem;
border-radius: 1rem;
word-break: break-all;
}

Copy link

@CSE-pebble CSE-pebble left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드가 깔끔해서 읽기 너무 편했습니다! nullish 병합 연산자나 옵셔널 체이닝 문법 등 최신 JS 문법을 적극적으로 사용하신 부분이 인상 깊었고, 절대경로 설정해주신 부분 너무 유용한 것 같습니다! 많이 배워갑니다! 수고하셨어요~!🥳

export const flexColumn = css`
display: flex;
flex-direction: column;
`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다~! 코드 가독성 향상에도 큰 도움이 될 것 같아요!

"compilerOptions": {
"baseUrl": "src"
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

절대 경로 설정 배워갑니다! 다음 과제부터 적용해봐야겠어요!

import { getTodayDate } from 'util/getTodayDate';

function TodoHeader() {
const [userName, setUserName] = useState(localStorage.getItem('userName') ?? '');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 nullish 병합 연산자 ?? 대신에 ||를 사용했는데, 이런 방법도 있었군요!
둘의 차이점에 대해 알아두는게 좋을 것 같아 링크 남기고 갑니다!

Comment on lines 17 to 19
useEffect(() => {
showUserName();
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 코드에서는 showUserName 함수가 매 렌더링마다 실행되도록 되어있는데요, 의존성 배열에 빈 배열을 설정하는 대신 이렇게 작성한 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실수입니다..!! 감사해요 ㅎㅎ

export default TodoCreate;

const InputForm = styled.form`
display: flex;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${flexCenter}에 display: flex가 이미 있어서 요 부분은 빼줘도 될 것 같아요!

Comment on lines 48 to 59
const InputForm = styled.form`
display: flex;
width: 100%;
height: 6rem;
${flexCenter}
`;

const InputContainer = styled.div`
${flexColumn}
height: 100%;
gap: 1rem;
`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputFormInputContainer를 분리하신 이유가 있을까요? 둘을 하나로 합쳐도 될 것 같다는 생각이 드는데 혹시 제가 놓친 유담님의 의도가 있다면 말씀해주세요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인풋 + 버튼은 가로 방향으로 , 이 둘을 감싸고 있는 InputWrapper와 ErrorMessage는 세로 방향으로 배치가 되어야해서요..!!

src/App.jsx Outdated
Comment on lines 18 to 21
useEffect(() => {
localStorage.setItem('todoList', JSON.stringify(todoList));
localStorage.setItem('doneList', JSON.stringify(doneList));
}, [todoList]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의존성 배열을 todoList만 설정해주셨다 보니 doneList에 있는 아이템을 삭제하면 localStorage에 반영이 안되는 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시간이 없어서 QA를 제대로 못한 잘못이네요 ㅠㅠㅋㅋ done<->todo로 이동할 때만 doneList가 바뀐다고 생각해서 todoList만 의존성 배열에 넣어줬네요 ! 삭제를 고려 못했어요 감사합니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants