[토이 프로젝트] TO DO LIST 학습 기록

yiseo·2023년 4월 22일
0

react

목록 보기
1/1
post-thumbnail


1. 학습 방법

  1. 멋진 투두리스트 만들기 미리보기를 보고 혼자 구현해보기 (모르거나 부족한 부분 체크)
  2. 멋진 투두리스트 만들기 완성코드 따라하기
  3. 개선 또는 새로운 기능 추가

2. 주요 사용 기술

React styled-components Context API


3. 주요 구현사항

  1. styled-components 를 통한 컴포넌트 스타일링
  2. Context API 를 사용한 전역 상태 관리

4. 학습 내용

📌styled-component 는 <body> 스타일을 어떻게 지정할까?

특정 컴포넌트를 만들어서 스타일링 하는게 아니라
<body>와 같은 글로벌 스타일을 추가하고 싶으면 createGlobalStyle사용

import styled, { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    background-color: #e9ecef;
  }
`

return (
  <>
    <GlobalStyle />

📌 styled-component 작명법

  • 최상위 컴포넌트 : 컴포넌트 이름+TAG ex) <TodoListBlock/>
  • 하위 컴포넌트 : 일반 클래스명
const TodoListBlock = styled.div`
  padding-top: 48px;
  h1 {
    margin: 0;
  }
	.day {
    margin-top: 4px;
  }
`

cf1. 조건부 스타일링을 할 필요가 없고, 기능적으로 중요하지 않은 내용이라면 CSS Selector 를 사용하고, 기능적으로 중요한 내용은 별도의 컴포넌트로 만들자


📌 fix : <input type=”text">에 텍스트 입력할 때 마다 포커스를 잃는 문제

contextAPI를 사용해서 state가 변경될 때 마다 rerender 되어서 발생하는 문제

  • 방법 1 : styled-component를 컴포넌트 안이 아닌 컴포넌트 밖에 위치시키기
  • 방법2 : styled-component를 하나의 js파일로 import해서 사용

📌 styled-component 스타일 작성 시 컴포넌트 셀렉터 선택하기

const Remove = styled.div`
   display: flex;
   align-items: center;
   justify-content: center;
   display: none;
`

const TodoItemBlock = styled.div`
  &:hover {
      ${Remove} {
         display: initial;
      }
  }
`;

cf1. RemoveTodoItemBlock보다 위에 위치해야함


📌 styled-component로 조건부 스타일링 할 때 css구문 작성하고 싶은 경우

import styled, { css } from 'styled-components';

${props =>
   props.done &&
   css`
      color:#ced4da;
   `}

📌 fontawesome보다 간단한 icon 라이브러리 : react-icons

React Icons

react-icons 설치

npm install react-icons --save

react-icons 사용방법

import { FaBeer } from 'react-icons/fa';

<FaBeer />

cf1. ‘react-icons/fa’ import시 fa로 시작하는 아이콘만 사용 가능


📌 autoFocus

autoFocus는 HTML과 React에서 사용되는 속성으로 페이지 로드 후 자동으로 포커스 지정

<input type="text" autoFocus />

📌 component 분리

📦src
 ┣ 📂components
 ┃ ┣ 📜TodoBottom.js
 ┃ ┣ 📜TodoList.js
 ┃ ┗ 📜TodoTop.js
 ┣ 📜App.css
 ┣ 📜App.js
 ┣ 📜data.js
 ┣ 📜index.css
 ┣ 📜index.js
 ┗ 📜styles.js
// 생략
<Todo>
  <Context.Provider 
	value={{ todoData, setTodoData }}>
    <TodoTop />
    <TodoList />
    <TodoBottom />
  </Context.Provider>
</Todo>

완성코드

📦src
 ┣ 📂components
 ┃ ┣ 📜TodoCreate.js
 ┃ ┣ 📜TodoHead.js
 ┃ ┣ 📜TodoItem.js
 ┃ ┣ 📜TodoList.js
 ┃ ┗ 📜TodoTemplate.js
 ┣ 📜App.css
 ┣ 📜App.js
 ┣ 📜index.css
 ┣ 📜index.js
 ┗ 📜TodoContext.js
<>
  <TodoProvider>
    <GlobalStyle />
    <TodoTemplate>
      <TodoHead />
      <TodoList />
      <TodoCreate />
    </TodoTemplate>
  </TodoProvider>
</>

cf1. 완성코드는 App 컴포넌트에 TodoTemplate.js라는 템플릿 컴포넌트를 만들고
안에 TodoHead, TodoItem,TodoCreate 등 컴포넌트를 props.child로 받음


📌 toLocaleString()함수를 사용하여 해당 날짜의 문자열 표현

ex)

const dataString = today.toLocaleString('ko-kr',{
      year:'numeric',
      month:'long',
      day:'numeric',
 })  // 2023년 4월 19일

const dayName = today.toLocaleString('ko-kr', { weekday : 'long'})  // 화요일
const dataString = today.toLocaleString('ko-kr',{
      year:'numeric',
      month:'long',
      day:'numeric',
 }) // 2023.4. 19

📌 context API

Notion : context 기록

참고 코드

 import React, { useReducer, createContext, useContext, useRef } from 'react';
 
 const initialTodos = [
   {
     id: 1,
     text: '프로젝트 생성하기',
     done: true
   },
   {
     id: 2,
     text: '컴포넌트 스타일링하기',
     done: true
   },
   {
     id: 3,
     text: 'Context 만들기',
     done: false
   },
   {
     id: 4,
     text: '기능 구현하기',
     done: false
   }
 ];
 
 function todoReducer(state, action) {
   switch (action.type) {
     case 'CREATE':
       return state.concat(action.todo);
     case 'TOGGLE':
       return state.map(todo =>
         todo.id === action.id ? { ...todo, done: !todo.done } : todo
       );
     case 'REMOVE':
       return state.filter(todo => todo.id !== action.id);
     default:
       throw new Error(`Unhandled action type: ${action.type}`);
   }
 }
 
 // context 분리
 const TodoStateContext = createContext();
 const TodoDispatchContext = createContext();
 const TodoNextIdContext = createContext();
 
 export function TodoProvider({ children }) {
   const [state, dispatch] = useReducer(todoReducer, initialTodos);
   const nextId = useRef(5);
 
   return (
     <TodoStateContext.Provider value={state}>
       <TodoDispatchContext.Provider value={dispatch}>
         <TodoNextIdContext.Provider value={nextId}>
           {children}
         </TodoNextIdContext.Provider>
       </TodoDispatchContext.Provider>
     </TodoStateContext.Provider>
   );
 }
 
 // 커스텀 hook
 export function useTodoState() {
   const context = useContext(TodoStateContext);
 	// 커스텀 Hook 에러 처리
   if (!context) {
     throw new Error('Cannot find TodoProvider');
   }
   return context;
 }
 
 export function useTodoDispatch() {
   const context = useContext(TodoDispatchContext);
   if (!context) {
     throw new Error('Cannot find TodoProvider');
   }
   return context;
 }
 
 export function useTodoNextId() {
   const context = useContext(TodoNextIdContext);
   if (!context) {
     throw new Error('Cannot find TodoProvider');
   }
   return context;
 }

cf1. throw new Error()는 자바스크립트에서 예외를 발생시키는 방법
예외가 발생되면 예외를 처리할 수 있는 try…catch 블록이 있는 곳까지 전파되어 예외를 처리할 수 있음

cf2. 1개의 Context 를 만들어서 state 와 dispatch 를 함께 넣는 것이 아닌, 2개의 Context로 분리하기
dispatch만 필요한 컴포넌트에서 불필요한 렌더링 방지 및 사용 편리

📢 `state` 와 `dispatch`를 2개의 Context로 분리 이해하기

statedispatch를 하나의 context로 만들었다면, TodoCreate를 open한 상태에서 이미 존재하는 todo를 toggle한다면 TodoCreate는 리렌더링이 일어난다.
왜냐하면 TodoCreate가 state도 useContext로 받아오고 있기 때문이다.
TodoCreate입장에선 TodoState는 불필요하기 때문에, 굳이 받아올 필요가 없고 더군다나 리렌더링이 일어나게 한다면 더더욱 분리하는 것이 좋다.

cf3. 커스텀 hook 사용방법

import React from 'react';
import { useTodoState, useTodoDispatch } from '../TodoContext';

function Sample() {
  const state = useTodoState();
  const dispatch = useTodoDispatch();
  return <div>Sample</div>;
}

cf4. 커스텀 Hook 에러 처리

만든 useTodoStateuseTodoDispatchuseTodoNextId Hook 을 사용하려면, 해당 컴포넌트가 TodoProvider 컴포넌트 내부에 렌더링되어 있어야 함
(예: App 컴포넌트에서 모든 내용을 TodoProvider 로 감싸기)
만약 TodoProvider 로 감싸져있지 않다면 에러를 발생시키도록 커스텀 Hook 을 수정

⇒ 추후 실수 방지


📌 useref

dom 요소에 직접 접근해서 값을 변경해야할 때 사용하는 리액트 hook (예 : input 요소에 포커스)

값이 변경되어도 재렌더링 되지 않음


📌 memo 사용해서 context의 단점을 보완

TodoContext 에서 관리하고 있는 state가 바뀔 때 때 TodoCreate 의 불필요한 리렌더링을 방지 하기

export default React.memo(TodoItem);
export default React.memo(TodoCreate);

5. 개선 또는 새로운 기능 추가

개선

  • 텍스트 클릭하면 체크 되게 하기
  • 타입스크립트로 리팩토링

기능 추가

  • 반응형으로 만들기
  • 웹 스토리지에 저장해서 반영구적으로 만들기
  • 텍스트 수정 기능 추가
profile
열정 가득 주니어 프론트엔드 개발자

0개의 댓글