-
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
- 기본 스타일 설정용 - Style Reset용
- 사유) 재사용할 일이 없을 것 같고, 쓸데없이 분리해서 props를 전달하게 되었다고 판단
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: 0; | ||
font-size: 100%; | ||
vertical-align: baseline; | ||
} |
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.
css 리셋해주신 점 좋습니다!!👍🏻
리액트에서 사용할 수 있는 styled-normalize
도 있으니 다음에 한 번 사용해보셔도 좋을 것 같습니다~~
참고자료 남겨드려용 https://www.npmjs.com/package/styled-normalize
<Input | ||
onKeyDown={(e) => { | ||
e.key === "Enter" && handleSubmit(); | ||
}} | ||
onChange={handleOnchange} | ||
value={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.
입력창에 할 일을 입력후 +버튼을 누르면 문제 없이 잘 랜더링 되는데, 엔터키를 눌렀을 때는 맨 마지막 글자가 2번 랜더링 되고 있어요!
onKeyDown
을onKeyPress
로 바꾸거나- 밑에처럼
event.nativeEvent.isComposing === false
를 추가
해보세용~
<Input | |
onKeyDown={(e) => { | |
e.key === "Enter" && handleSubmit(); | |
}} | |
onChange={handleOnchange} | |
value={value} | |
/> | |
<Input | |
onKeyDown={(e) => { | |
if (e.key === "Enter" && !e.nativeEvent.isComposing) { | |
handleSubmit(); | |
} | |
}} | |
onChange={handleOnchange} | |
value={value} | |
/> |
<span>{doneCount + " / " + totalCount}</span> | ||
</ProgressWrapper> | ||
|
||
<TodoWrapper> |
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-radius: 10px; | ||
height: inherit; | ||
position: absolute; | ||
transition: width 0.4s ease-in-out; |
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.
progressBar 애니메이션 디테일이 너무 귀엽네요:):)
@@ -0,0 +1,198 @@ | |||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | |||
import { faPaw, faCirclePlus } from "@fortawesome/free-solid-svg-icons"; |
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.
fa아이콘들은 react-icons 라이브러리 이용하면 더 편하게 불러올 수 있을 것 같아요~
다양하고 귀여운 아이콘들이 많답니당 링크 두고 가용~
react-icons
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-bottom: 1px solid #7d7e7e; | ||
font-size: 20px; | ||
padding: 3px 2px; | ||
margin-bottom: 3px; |
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보다 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 단위를 사용하는데요!
html {
font-size : 62.5%
}
를 global style로 설정해주면 10px = 1rem으로 계산이 되어서 단위 계산을 훨씬 덜 하고 css 코드를 작성할 수 있어요!
rem을 사용하면 좋은 이유에 대한 아티클 남기고 갑니다 !
px 대신 rem을 사용해야하는 이유
const GlobalStyle = createGlobalStyle` | ||
@font-face { | ||
font-family: "UhBeepuding"; | ||
src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/[email protected]/UhBeepuding.woff") | ||
format("woff"); | ||
font-weight: normal; | ||
font-style: normal; | ||
} |
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에 로컬 폰트가 더 가볍다고 적어뒀었는데, 무조건은 아닌 거 같아요! 저는 주로 local 폰트 + subset 추출 방식을 사용해서 더 가벼워졌던 거 같습니다 ㅎㅎ 웹폰트 최적화 기법에 대한 글 남기고 가요!
Local Font 적용 방법과 Web Font 최적화 기법
const getTodayDate = () => { | ||
const today = new Date(); | ||
const week = ["일", "월", "화", "수", "목", "금", "토"]; | ||
|
||
const year = today.getFullYear(); | ||
const month = ("0" + (today.getMonth() + 1)).slice(-2); | ||
const day = ("0" + today.getDate()).slice(-2); | ||
const dayOfWeek = week[today.getDay()]; | ||
|
||
return `${year}.${month}.${day} ${dayOfWeek}요일`; | ||
}; |
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.
보통 이런 함수는 util 폴더의 함수이름.js 파일로 따로 빼는 경우가 많아요! 지금은 Header 컴포넌트 안에 상태(state)가 존재하지 않아서 리렌더링과 함께 함수가 새로 만들어질 일이 없지만, 컴포넌트가 복잡해지고 커지면 setState가 동작함에 따라 함수가 계속해서 다시 만들어져요. 그래서 따로 파일 분리해서 함수의 재생성을 막아주는 게 좋습니다 :)
const initialData = localStorage.getItem("data") | ||
? JSON.parse(localStorage.getItem("data")) | ||
: []; |
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 initialData = localStorage.getItem("data") | |
? JSON.parse(localStorage.getItem("data")) | |
: []; | |
const initialData = JSON.parse(localStorage.getItem("data") || []); |
const initialData = localStorage.getItem("data") | |
? JSON.parse(localStorage.getItem("data")) | |
: []; | |
const initialData = JSON.parse(localStorage.getItem("data") ?? []); |
이렇게 두 방법으로 더 간단하게 나타낼 수 있겠네요~
<TodoList> | ||
{data.map( | ||
(el) => | ||
el.isDone || ( | ||
<TodoItem | ||
key={el.id} | ||
item={el} | ||
toggleTodo={toggleTodo} | ||
deleteTodo={deleteTodo} | ||
/> | ||
) | ||
)} | ||
</TodoList> | ||
</div> | ||
<Line></Line> | ||
<div> | ||
<Title>DONE</Title> | ||
<TodoList> | ||
{data.map( | ||
(el) => | ||
el.isDone && ( | ||
<TodoItem | ||
key={el.id} | ||
item={el} | ||
toggleTodo={toggleTodo} | ||
deleteTodo={deleteTodo} | ||
/> | ||
) | ||
)} | ||
</TodoList> | ||
</div> |
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.
저라면 이 부분은 꼭 컴포넌트로 분리를 했을 거 같아요! 저는 1) prop만 잘 조절한다면 UI를 그려내는 로직이 똑같은 경우 2) 하나의 컴포넌트에 다 몰아놨을 때 너무 코드가 길어지는 경우
2)번 같은 경우는 컴포넌트의 역할에 따라 구분하는 거 같아요!
const TodoSection = ({ title, items, toggleTodo, deleteTodo }) => (
<div>
<Title>{title}</Title>
<TodoList>
{items.map((el) => (
<TodoItem key={el.id} item={el} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
))}
</TodoList>
</div>
);
이렇게요! ㅎㅎ
그리고 TodoItem으로 또 prop 넘겨주는 게 부담스러우면 저라면 그냥 TodoSection의 부피가 그리 크지 않으니, TodoItem을 따로 분리 안하고 TodoSection 안으로 넣을 거 같긴 합니다..!!
<Input | ||
onKeyDown={(e) => { | ||
e.key === "Enter" && handleSubmit(); | ||
}} | ||
onChange={handleOnchange} | ||
value={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.
form 태그를 사용하면 버튼 클릭, 엔터키 누름이라는 두 가지 이벤트로 input 값을 리스트에 추가하는 식으로 처리 해줄 필요없이 form 태그의 onSubmit 하나에만 함수를 걸어주면 한 번에 처리가 가능해요..!
제 코드 참고하시라고 올려드려요
const handleSubmit = (e) => {
e.preventDefault();
if (!inputValue?.trim()) {
setIsError(true);
return;
}
dispatch({
type: 'ADD_TODO',
payload: {
id: uuid(),
text: inputValue,
},
});
setIsError(false);
setInputValue('');
};
const handleInputValueChange = (e) => {
setInputValue(e.target.value);
};
return (
<InputForm onSubmit={handleSubmit}>
<InputContainer>
<InputWrapper>
<input className="input" placeholder="할 일 입력" value={inputValue} onChange={handleInputValueChange} />
<button className="input-button">입력</button>
</InputWrapper>
<ErrorMessage $isError={isError}>할 일을 입력해주세요</ErrorMessage>
</InputContainer>
</InputForm>
참고하면 좋은 글 첨부해드려요~
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도 정성스럽게 작성하시고 엄청 세심하게 작업하신게 느껴졌어요 파비콘까지 신경쓰신 디테일… 넘 멋졌어요 디자인 진짜진짜 제 취향이에요ㅠㅠ
배포 과정에서 저도 뭔가… 예상치 못한 문제에 맞닥뜨려서 시간을 낭비했어요 그래서 배포 이후에 스타일 수정을 하지 못한게 아쉬웠는데 남겨주신 느낀점에 크게 공감하고 가요 .. 😂 좋은 코드 잘 보고 갑니다 ㅎㅎ
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,198 @@ | |||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | |||
import { faPaw, faCirclePlus } from "@fortawesome/free-solid-svg-icons"; |
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 week = ["일", "월", "화", "수", "목", "금", "토"]; | ||
|
||
const year = today.getFullYear(); | ||
const month = ("0" + (today.getMonth() + 1)).slice(-2); |
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.
저는 날짜 데이터를 숫자로 다뤄서 달을 +1 해주는 방식을 사용해서 구현해봤는데,
const month = today.getMonth() + 1;
나현님이 구현하신 문자열 포맷팅 (2023-03-24 처럼 두글자씩 출력되게끔) 부분에서 디자인적으로 신경을 많이 쓰셨음이 느껴졌어요! 이런 디테일.. 너무 좋아요
날짜 관련해서 잘 정리된 블로그 공유합니당! ㅎㅎㅎ
Date메소드
<FontAwesomeIcon icon={faPaw} size="lg" /> | ||
<Input | ||
onKeyDown={(e) => { | ||
e.key === "Enter" && handleSubmit(); |
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 initialData = localStorage.getItem("data") | ||
? JSON.parse(localStorage.getItem("data")) | ||
: []; | ||
const [data, setData] = useState(initialData); |
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.
저는 로컬스토리지에서 초기값 불러올 때 useState
에 함수형 업데이트 방식을 사용해서 구현하였는데요,
이렇게 하면 초기 랜더링 과정에만 실행되어서 중복 호출 없이 데이터 초기화가 가능해진다고 하더라구요..
참고하셔서 리팩토링 해보셔도 좋을 것 같습니다!!
const [data, setData] = useState(initialData); | |
const [data, setData] = useState(() => { | |
const storedData = localStorage.getItem("data"); | |
return storedData ? JSON.parse(storedData) : []; | |
}); |
setData([ | ||
...data, | ||
{ | ||
id: new Date().getTime() + Math.random(), |
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: Date.now()
만을 사용해서 구현하였는데요,
now()
메소드만 사용해도 충분히 고유값 설정히 가능할 것 같아요!
Date에는 메소드가 너무 많아서 숙지하는게 넘 힘든 것 같아요 .. ㅜㅠ
Date 메소드 링크 첨부하겠습니당!
toggleTodo={toggleTodo} | ||
deleteTodo={deleteTodo} | ||
/> | ||
) |
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.
el.isDone
의 true, false 로 컴포넌트를 반환하는 방식도 창의적인 것 같아요~!
그런데 .filter()
메소드로 필요한 항목들만 먼저 필터링 하고,
필터링 된 배열에 .map()
을 적용해서 화면에 랜더링하는 방식으로 리팩토링 해보셔도 깔끔할 것 같아요!
filter메소드 참고하시면 좋을 것 같은 블로그 첨부합니다~!!
2주차 미션: React-Todo
🐻 배포 링크
https://agijagi-todo-react.vercel.app/
👩💻 구현 기능
기본 기능
추가 기능
🛠️ 이전과 달라진 부분
🥳 후기
React로 하니까 진짜 편하고 좋네요.. Vanilla JS로 구현했을 때 보기 불편했던 부분이 해결돼서 정말 기쁩니다~~🥰
🔥 어려웠던 부분 / 의문점
컴포넌트 분리
상태 변경 관련: 화면에 "todo / done 개수"를 보여줄 때
todoCount
,doneCount
변수를 이용했습니다. 처음에는 doneCount랑 totalCount가 업데이트 될 때마다 화면이 리렌더링 되려면doneCount
,totalCount
를useState
로 관리해야 한다고 생각했습니다. 그러나 둘 다 일반 변수로 선언했음에도 리렌더링이 정상적으로 잘 동작하였습니다😮 왜 이런 현상이 발생한 것일까요?doneCount
,totalCount
모두useState
로 관리되는 "data
"에 의존하는 변수이기 때문에data
의 상태가 변경될 때마다 컴포넌트가 리렌더링 되고, 그에 따라doneCount
,totalCount
도 업데이트된 상태로 화면에 보여지는 것입니다.삽질 n시간 하기.
Vite 프로젝트는 Vercel로 배포할 때 종종 오류가 나는 것 같습니다.. 덕분에 삽질 n시간~..
어쩌다 그렇게 된건지 모르겠지만 본 레포지토리 & fork 해온 레포지토리의 master 브랜치와, 제 계정명으로 판 브랜치가 커밋 내역이 아예 달라서(master 브랜치의 커밋 내역이 보이지 않았음) PR이 안됐습니다.. 덕분에 여기서도 삽질 n시간~..
배포했을 때까지만 해도 배포 해놓고 여유롭게 추가적인 기능을 구현하려고 했는데 쓸데없는 삽질 때문에 못한게 너무 아쉬워요ㅠㅠ 하
✨ 더 구현해보고 싶은 기능
❓ Key Questions
1. Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?
Virtual은 말 그대로 가상이라는 뜻이고, React에서는 이 Virtual DOM을 통해 리렌더링 한다.
그런데 왜 Virtual DOM을 사용해야 할까?
먼저 React에서 언제 리렌더링이 일어나는지부터 알아보자.
기존 Vanilla JavaScript 개발은 DOM 요소를 하나하나 가지고 와서, 하나가 변경되면 하나를 업데이트 해주는 방식이었다. 즉, 100개의 요소에 업데이트가 발생하면, 100번 리렌더링 되는 방식이었다. 즉, DOM 조작 비용이 컸다. 최근에 웹은 DOM과의 상호작용이 많기 때문에 더욱 심각한 문제이다.
하지만, Virtual DOM은 메모리에서 Virtual DOM과 Real DOM을 비교하는 과정을 거치기 때문에, 현저히 빠른 연산을 수행하고, 변경사항을 한번에 모아서(버퍼링) 감지하여 리렌더링 시키기 때문에 연산 횟수를 최소화하여 효율적이다. 위의 경우라면 100번의 리렌더링을 Virtual DOM에 모두 반영하고, Real DOM과 1회 비교하여 연산을 수행하고 리렌더링한다.
2. 미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요?
Component-based Development
가 가능하여, 재사용성이 높고, 효율적인 유지보수가 가능하다.기존에는 하나의
index.html
파일에 한 페이지에 있는 모든 UI 요소를 넣어서 개발했지만, 리액트는 UI를 컴포넌트 단위로 나누어서 개발한다.Virtual DOM을 통해 리렌더링을 효율적으로 수행하도록 도와준다.
SPA(Single Page Application)이기 때문에 사용자 경험과 성능을 향상시키는데 도움이 된다.
Vanilla JS(MPA)라면 페이지가 여러개로 구성되어 있다.
A.html
,B.html
,C.html
... 각 html의 이동은, 새로운 페이지로의 이동을 의미하고, 새로운 페이지로 갈 때마다 서버에서 리소스(html
,css
,js
등)를 로드하고 실행한다.하지만 규모가 커지고 사용자와의 상호작용이 많아짐에 따라, 매번 서버에서 리소스를 로드하는 것은 과부하로 인한 속도 저하가 발생하기에 문제점으로 지적되었다.
SPA는 이를 해결하기 위한 방법으로, 첫 페이지에서 필요한 리소스 파일을 모두 로드하고, 페이지가 이동함에 따라 리소스를 실행하며 보여준다. 즉, 뷰 렌더링을 서버가 아닌 웹 브라우저가 담당한다. 즉, Vanilla JS에서는 페이지가 여러개인데 반해, React에서는 하나의 페이지에, 다양한 뷰를 보여준다. (React에서는 html 파일이
index.html
파일 하나밖에 없다. 하나의 페이지에서 다른 컴포넌트들을 보여주는 방식이다.)JSX 문법을 사용하여 UI를 작성하므로 가독성이 뛰어나며, 코드의 유지보수성을 높여준다.
One-way Data Flow(단방향 데이터 흐름)을 강조하기 때문에 일관적인 데이터 관리가 가능하다.
단방향 데이터 흐름은 부모에서 자식으로만 데이터 전달이 가능하다.
양방향 데이터 흐름은 부모 → 자식 / 자식 → 부모 모두 데이터를 보내줄 수 있는 흐름이다. 이 흐름은 작은 프로젝트에서는 간단히 사용할 수 있다는 장점이 있지만, 프로젝트가 거대해질수록 더욱 복잡해지고, 어디서 어떤 데이터를 받아왔는지 소재 파악이 어렵다. 그러므로, 디버깅과 코드를 이해하기 어려워지고, 예측하기 어려운 코드가 된다.
그러므로 React는 보다 일관된 데이터 관리를 위해 단방향 데이터 흐름을 채택했다.
React로 코드를 작성하며 직접적으로 느낀 장점은 1, 4번입니다.
3. React에서 상태란 무엇이고 어떻게 관리할 수 있을까요?
상태(state)는 동적 데이터를 다루는 방식으로서, 뷰에 렌더링 돼야 하는 컴포넌트의 데이터를 저장하는 데 사용한다. 이 상태(state)는
useState
Hook을 통해서 관리할 수 있다. state는 변경이 가능하고,setState
를 통해 변경한다.왜 변수를 사용하지 않고, state를 사용할까? 이는 React의 Rendering 방식에 주목할 필요가 있다.
4. Styled-Components 사용 후기 (CSS와 비교)
일반 CSS의 단점은 아래와 같다.
CSS
와JS
파일이 분리되어 있어, 동적 스타일링이 어렵다.CSS
로 분리하여 작성한다고 하더라도, 웹팩에서 빌드하게 될 때 하나의 파일로 합쳐지게 되면 class가 전역에서 작동하게 된다. 그러므로, 중복을 피하는 노력을 기울여야 한다.반면 styled-components의 특성 및 장점은 아래와 같다.
CSS
가JS
에 들어가 있기 때문에, 동적 스타일링이 간단하다.🔗 참고 자료