To-Do App 최적화하기

dev bourgeois·2023년 11월 11일
0

React A-Z Study

목록 보기
3/10
post-thumbnail

React Hooks

class 없이 state를 사용할 수 있는 새로운 기능

*필요한 이유❔
React Hooks는 주로 Class Component로 사용되어온 React에서
느껴왔던 불편함이나 문제점들을 해결하기 위해서 개발되었다.

원래 React는 주로 Class Component를 사용하고
React Hooks는 Functional Component를 사용하기 때문에 먼저 그 부분 부터 비교해보겠다.

☑️ 함수형 컴포넌트에서 더 적은 기능을 제공한다면, 어떤 기능을 클래스 컴포넌트에 비해서 쓰지 못한다는 것인가❓

이렇게 중요한 생명 주기를 함수형 컴포넌트에서 사용 못했기 때문에 함수형 컴포넌트가 더 간결하고 빠르더라도 클래스형 컴포넌트를 써왔다.

❇️ React 16.8 Hooks 업데이트
이로 인해 함수형 컴포넌트에서도 생명주기를 사용할 수 있기에 데이터를 가져오고 컴포넌트 시작하자마자 API도 호출하고 많은 부분을 할 수 있게 되었다.

☑️ Hooks로 인한 이점

왼쪽 코드와 오른쪽 코드를 보면 선명하게 코드가 간결해진걸 볼 수 있다.
그 이유는 Class Component 에서는 생명주기를 이용할 때
componentDidMount, ComponentDIdUpdate, componentWillUnmount
이렇게 다르게 처리를 해주지만
리액트 훅을 사용할 때는 useEffect 안에서 다 처리를 해줄수 있기 때문이다.

☑️
HOC 컴포넌트를 Custom React Hooks 로 대체해서 너무나 많은 Wrapper
컴포넌트를 줄일 수 있게 되었다.

HOC(Higher Order Component) 란, 화면에서 재사용 가능한 로직만을 분리해서 component로 만들고, 재사용 불가능한 UI와 같은 다른 부분들은 parameter로 받아서 처리하는 방법이다.

A, B 두 페이지에서 같은 소스를 사용하고 있는 부분은 유저 리스트를 가져오는 부분이다. 어떠한 페이지에서든 유저 리스트를 가져와야 하는 애플리케이션을 만드려고 한다.
하지만 모든 페이지에서 유저 리스트를 가져오기 위해서 똑같은 소스를 넣어준다면 너무 많은 중복이 되기 때문에 중복이 되는 부분은 따로 HOC 컴포넌트에 넣어주고 그 HOC 컴포넌트로 각각의 컴포넌트를 감싸주면 모든 컴포넌트에 따로 인증을 위한 부분은 넣어주지 않아도 된다.
Hooks가 나오기 전에는 이 방법이 추천되는 방법이었다. 하지만 여기서도 문제가 있다. ⚠️Wrapper 컴포넌트가 생길수 있다는 것이다❕


❇️ Custom React Hooks를 이용해서 해결


바벨에서 보면 classComponent와 functionComponent의 코드 길이 차이를 확인할 수 있다.

📍정리- Hook과 관련된 문제들

1. HOC란?
Higher Order Component의 약자로
컴포넌트를 인자로 받아서 새로운 리액트 컴포넌트를 리턴하는 함수

2. 너무나 많은 HOC를 사용하게 되면 Wrapper가 너무 많아지게 된다. 그걸 Hooks에서는 어떻게 처리하는가?

React Hooks에서는 HOC대신에 따로 Custom Hooks를 이용해서 컴포넌트
를 만들어서 처리를 해준다. 그로인해 Wrapper가 많아지는 일을 방지한다.

3. 생명주기를 위해서 Hooks에서는 어떠한 api를 사용하는가?
componentDidMount, componentDidUpadte, componentWillUnmount 모두를 Hooks 에서는 useEffect를 이용해서 처리해준다.

4. Hooks에서 state을 업데이트 해주려면 어떻게 해야 하는가?
state을 정의해줄 때 const [ name, setName ] = useState("");
여기서 setName 을 이용해서 state를 업데이트 시킨다.

5. Class 컴포넌트와 비교했을 때 함수형 컴포넌트의 장점은 ?
1. 더 짧고 간결한 코드 2. 더 빠른 성능 3. 더 나은 가독성


React Hooks를 이용해서 To-Do 앱을 함수형 컴포넌트로 바꾸기

  1. 컴포넌트 자체를 바꾸기

  • class 컴포넌트에서는 render() 안에 return()
    (class 컴포넌트에서 render()는 컴포넌트를 렌더링하는(화면에 나타내는) 메소드)
  • functional 컴포넌트에서는 render() 없이 바로 return()
  1. state를 useState Hook을 이용해서 표현하기





import React, {useState} from "react"; 
// React -> React 컴포넌트를 생성하기 위해
// useState -> 함수형 컴포넌트 상태 관리 위해 사용
import "./App.css";

// 함수형 컴포넌트
export default function App() { 
  const [todoData, setTodoData] = useState([]);
  const [value, setValue] = useState("");  
  // useState를 사용하여 두 가지 상태를 생성한다.
  // todoData -> 할 일 목록 저장하기 위한 배열
  // value -> 새로운 할 일을 추가하기 위한 입력값 저장 
  // state 처음에 빈 string값
  
  const btnStyle = {
    color: "#fff",
    border: "none",
    padding: "5px 9px",
    borderRadius: "50%",
    cursor: "pointer", // 마우스 커서 모양 
    float: "right" // 요소의 플로팅 설정 -> 값을 오른쪽으로 플로팅
  }
  
 // 완료된 항목 취소선 가지도록 동적 설정
  const getStyle = (completed) => { 
    return {
      padding: "10px",
      borderBottom: "1px #ccc dotted",
      textDecoration: completed ? "line-through" : "none" 
    }
  }

  // 삭제버튼 클릭하면 해당 할 일 목록에서 제거
  const handleClick = (id) => {
    let newTodoData = todoData.filter((data) => data.id !== id);
    // filter -> 현재 할 일 목록에서 클릭한 일을 제외한 나머지 필터링
    // console.log("newTodoData", newTodoData);  디버깅
    setTodoData(newTodoData); 
    // state를 새로운 값으로 업데이트
    // 새로운 할 일 목록으로 기존의 할 일 목록을 교체
  }

  // 입력 필드의 값이 변경될 때마다 호출(value 상태를 업데이트)
  const handleChange = (e) => { 
    setValue(e.target.value);
    // 이벤트 객체에서 입력 필드의 현재 값에 해당하는 부분 추출, 추출한 값으로 value 업데이트
    // e.target은 이벤트가 발생한 html 요소 가리킴(이벤트 종류따라 가리키는 대상 다름)
  }

  // 폼을 제출할 때(이벤트 발생) 호출되며, 새로운 할 일을 추가하고 입력값을 초기화
  const handleSubmit = (e) => {
    e.preventDefault();
    // form 안에 input 전송할 때 페이지 리로드 막아줌
    // 기본 폼 제출 동작을 막음 -> 리로드 하지 않고도 폼 제출 가능하도록

    // 새로운 할 일 데이터(객체 생성)
    let newTodo = {
      id: Date.now(), // 고유한 값
      title: value, // 입력값에서 가져옴
      completed: false // 초기에는 완료x
    };

    // 원래 있던 할 일에 새로운 할 일 더해주기
    setTodoData((prev) => [...prev, newTodo]);
    // 이전의 할 일 목록(prev)에 새로운 할 일(newTodo) 추가하여 새로운 배열 생성
    setValue(""); 
    // 입력값 초기화하여 새로운 할 일을 추가한 후에는 입력 필드를 비움
  };

  // 체크박스를 클릭했을 때 호출(해당 할 일의 완료 상태를 토글)
  const handleComleteChange = (id) => { // id는 할 일의 고유 식별자
    
    // map 함수를 사용하여 현재 할 일 목록 순회하면서 새로운 배열 생성
    let newTodoData = todoData.map((data) => {
      if (data.id === id) { // 클릭된 할 일의 id와 일치하는 항목 찾아
        data.completed = !data.completed; 
      }
      return data; // completed 속성 토글
    });
    setTodoData(newTodoData); // 업데이트된 할 일 목록으로 상태 업데이트
  };

  // React 컴포넌트의 JSX를 이용해서 UI를 정의하고 렌더링
  // return 문 안의 JSX는 화면에 표시되는 구조 정의
    return (
      <div className="container">
        <div className="todoBlock">
          <div className="title">
            <h1>할 일 목록</h1>
          </div>

            // 할 일 목록 표시
            {todoData.map(data => ( 
            // todoData 배열을 map함수로 순회하여 각 할 일 항목을 JSX로 표시
               <div style={getStyle(data.completed)} key={data.id}> // 스타일 동적으로 
                 <input // 체크박스 생성
                   type="checkbox" 
                   defaultChecked={false} 
                   onChange={() => handleComleteChange(data.id)}  
                 />
                 {data.title} // 화면에 표시
                <button 
                   style={btnStyle} 
                   onClick={() => handleClick(data.id)}
                >
                 x 
                </button>
              </div>
            ))}
      
            // 할 일 추가 폼
            <form style={{display: 'flex'}} onSubmit={handleSubmit}>
              <input // 텍스트 입력 필드
                type="text" 
                name="value" 
                style={{flex: '10', padding: '5px'}}
                placeholder="해야 할 일을 입력하세요."
                value={value}
                onChange={handleChange}
              />

              <input // 폼 제출 -> 클릭시 handleSubmit 호출
              type="submit"
              value="입력"
              className="btn"
              style={{flex: '1'}}
              />
            </form>

        </div>
    </div>
    );
}

State와 Props


컴포넌트 분리

-할 일 목록 부분을 위한 컴포넌트 생성하기

먼저 할 일 목록 부분(리스트)를 새로운 컴포넌트에 넣어주겠다.

  1. 리스트 컴포넌트 생성
    -components 폴더 안 List.js 파일 생성

    *설치
    rce + tab -> class component 생성
    rf + tab -> functional component 생성

  2. 리스트에 관련된 부분 가져오기(UI 부분)
    -컴포넌트를 날 때 정답X, 최대한 재사용성이 높을 수 있는 방법으로 나눈다.

  3. 필요한 State, 함수 가져오기

  4. List 컴포넌트에 Props 내려주기
    -App.js 파일

  1. Props로 필요한 데이터 함수 가져오기
    -List.js 파일

List.js


구조 분해 할당(Destructuring)

배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 Javascript 표현식

clean code를 위해!

*객체 구조 분해 할당

*깊게 들어간 객체 구조 분해 할당

*배열 구조 분해 할당



Form 부분을 위한 컴포넌트 생성하기

  1. Form 컴포넌트 생성
  1. Form에 관련된 부분 가져오기
    (1) UI 부분 가져오기

(2) 필요한 함수 가져오기

☑️ handleSubmit()은 state가 안에 있기 때문에 App.js 안에서 처리하는 것이 좋다. -> props로 내려주기만 한다.

(3) Form 컴포넌트에 Props 내려주기

(4) Props로 필요한 데이터 함수 가져오기


Form.js

App.js


TailWindCss

HTML 안에서, CSS 스타일을 만들 수 있게 해주는 CSS 프레임 워크

*CSS 프레임 워크
레이아웃 및 여러 컴포넌트 구성, 브라우저 호환성을 보장하는데 소요되는 시간을 최소화하기 위해 여러 웹 개발/디자인 프로젝트에 적용할 수 있는 CSS 파일 모음

*Tailwind CSS의 장점

Tailwind CSS는 부트스트랩과 비슷하게 m-1, flex와 같이 미리 세팅된 Uitility Class 를 활용하는 방식으로 HTML 에서 스타일링을 할 수 있다.
1. 빠른 스타일링 작업이 가능하다.
2. class 혹은 id 명을 작성하기 위한 고생을 하지 않아도 된다.
3. 유틸리티 클래스가 익숙해지는 시간이 필요할 수 있지만 IntelliSense 플러그인이 제공돼서 금방 익숙해질 수 있다.

TilwindCSS 살펴보기
CRA에 TailWindCSS 적용하기
reference


  1. Create your project : npx create-react-app my-project


    "devDependencies" -> 개발을 할 때만 사용된다.

  2. Install Tailwind CSS : npm install -D tailwindcss


  1. Configure your template paths
    content 부분

  2. Add the Tailwind directives to your CSS
    index.css가 없고 App.css가 있기 때문에 App.css에 지시자 넣어주기

  3. Start your build process -> npm run start

  4. Start using Tailwind in your project

 <h1 className="text-3xl font-bold underline">Hello world</h1>

App.js에 적용

다시 실행하면 이렇게 css가 적용된 것을 확인할 수 있다.


TailWindCss로 앱 스타일링

원래 스타일링 지우기
app.css
btnStyle
getStyle
Form.js
Inline style

다 지우니까 굉장히 옹졸해졌다.

바꾸게 될 UI

App.js
스타일링 전



Form.js
-px의 x는 left, right y는 top, bottom

스타일링 전


List.js
스타일링 전


  • div태그로 checkbox와 data.title을 묶고 / button을 묶음(안 묶어도 되긴 함)
    / 이 두 div 태그를 div 태그로 한 번 더 묶음
  • justify-between 밥 먹기와 x를 떨어뜨림

Drag and Drop

List.js
좀 어렵다..

*Drag and Drop 기능 구현 순서

  • HTML 드래그 앤 드롭 API를 사용하여 원하는 목록을 드래그 가능하게 만든다.
    html drop and drop API
  • 사용자가 드래그를 할 때 적절한 애니메이션을 준다.
  • 사용자가 드래그를 멈췄는지 확인하고 여기에도 애니메이션을 준다.
  • 클라이언트가 목록을 재정렬한 경우 항목의 위치를 새 항목으로 업데이트한다.
    ➡️ 이것을 쉽게 구현할 수 있게 도와주는 모듈이 react-beautiful-dnd

모듈 설치하기 npm install react-beautiful-dnd --save

https://www.npmjs.com/package/react-beautiful-dnd

*Error
id 값과 관련해서 오류가 발생했다.
index.js에서 StrictMode를 제거하면 해결된다.
➡️ drag&drop 가능하나 실제로 위치가 바뀌지 않고, 움직임이 부자연스럽다
❇️ placeholder 사용




*API를 이용한 틀 만들어주기

provided object에는 스타일 지정 및 조회를 위한 속성이 포함되어 있다.
사용자가 요소를 드래그하는 경우 className 속성을 selected 로 변경한다. 스타일을 적용하는 데 사용할 것이다.
placeholder 속성은 목록에 빈 공간을 만들면 드래그 작업이 자연스럽게 된다.

*Dragging 하는 요소의 스타일링 변경

*Array.prototype.splice()
splice() 메서드는 배열의 기존 요소를 삭제 또는 교체하거나 새 요소를 추가하여 배열의 내용을 변경한다.
reference

*Dragging 한 후 데이터 순서 적용(persistence)


*Lists.js

import React from 'react'
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; // 자동 생성

export default function List({ todoData, setTodoData }) {

  const handleClick = (id) => {
    let newTodoData = todoData.filter((data) => data.id !== id);
    setTodoData(newTodoData); // state를 새로운 값으로 업데이트
  }

  const handleComleteChange = (id) => {
    let newTodoData = todoData.map((data) => {
      if (data.id === id) {
        data.completed = !data.completed;
      }
      return data;
    });
    setTodoData(newTodoData);
  };

  const handleEnd = (result) => { // result 매개변수에는 source 항목 및 대상 위치와 같은 드래그 이벤트에 대한 정보 포함
    console.log('result', result);

    // 목적지가 없으면(이벤트 취소) 이 함수 종료
    if (!result.destination) return;

    // 리액트 불변성을 지켜주기 위해 새로운 todoData 생성
    const newTodoData = [...todoData];

    // 1. 변경시키는 아이템을 배열에서 지워준다.
    // 2. return 값으로 지워진 아이템을 잡아준다.
    const [reorderedItem] = newTodoData.splice(result.source.index, 1);

    // 원하는 자리에 reorderedItem을 Insert 해준다.
    newTodoData.splice(result.destination.index, 0, reorderedItem);
    setTodoData(newTodoData);
  }

  return (
    <div>
      <DragDropContext onDragEnd={handleEnd}>
        <Droppable droppableId="todo">
          {(provided) =>
            <div {...provided.droppableProps} ref={provided.innerRef}>
              {todoData.map((data, index) => (
                <Draggable
                  key={data.id}
                  draggableId={data.id.toString()}
                  index={index}
                >
                  {(provided, snapshot) => (
                    <div
                      key={data.id}
                      {...provided.draggableProps}
                      ref={provided.innerRef}
                      {...provided.dragHandleProps}
                      className={`${
                        snapshot.isDragging ? "bg-gray-400" : "bg-gray-100"
                      } flex items-center justify-between w-full px-4 py-1 my-2 text-gray-600  border rounded`}
                    >

                      <div className="items-center">
                        <input
                          type="checkbox"
                          defaultChecked={false}
                          onChange={() => handleComleteChange(data.id)}
                        />
                        <span className={data.completed ? "line-through" : undefined}>{data.title}</span>
                      </div>
                      <div>
                        <button className="px-4 py-2" float-right onClick={() => handleClick(data.id)}>x</button>
                      </div>
                    </div>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </div>
          }
        </Droppable>
      </DragDropContext>
    </div>
  );
}

리액트 불변성 지키기

불변성이란 사전적 의미로는 값이나 상태를 변경할 수 없는 것을 의미한다.

*자바스크립트 타입을 통한 불변성 의미 살펴보기

원시 타입은 불변성(immutable)을 가지고 있고 참조 타입은 그렇지 않기 때문에(mutable) 둘을 비교하며 불변성의 의미를 더 자세히 알아보겠다.

원시 타입 : Boolean, String, Number, null, undefined, Symbol (불변성을 가지고 있다.)
참조 타입: Object, Array

기본적으로 Javascript는 원시 타입에 대한 참조 및 값을 저장하기 위해 Call Stak 메모리 공간을 사용하지만 참조 타입의 경우 Heap이라는 별도의 메모리 공간을 사용한다. 이 경우 Call Stack은 개체 및 배열 값이 아닌 메모리에만 Heap 메모리 참조 ID를 값으로 저장한다.

*불변성을 지켜야 하는 이유
1. 참조 타입에서 객체나 배열의 값이 변할 때 원본 데이터가 변경되기에 이 원본 데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있어서 프로그래밍의 복잡도가 올라간다.

2. 리액트에서 화면을 업데이트할 때 불변성을 지켜서 값을 이전 값과 비교해서 변경된 사항을 확인한 후 업데이트하기 때문에 불변성을 지켜줘야 한다.


*불변성을 지키는 방법
참조 타입에서는 값을 바꿨을 때 Call Stack 주소 값은 같은데 Heap 메모리 값만 바꿔주기에 불변성을 유지할 수 없었다.

➡️ 새로운 배열을 반환하는 메소드를 사용
spread operator, map, filter, slice, reduece

✅ 원본 데이터를 변경하는 메소드 => splice, push


List 컴포넌트 생성하기

*List.js 파일 생성

Lists.js를 지금껏 List.js로 잘못 표기해서 다 바꿔줬다..
할 일 목록 전체가 Lists 컴포넌트, 각각을 List 컴포넌트로 !

*함수형 컴포넌트 생성
rafce + tab

*UI 부분 List 컴포넌트로 이동
Lists.js -> List.js

*함수도 List 컴포넌트로 이동

*Lists 컴포넌트에서 List 컴포넌트 import 및 props 내려주기
자동으로 import 하는 법
ex) 먼저 써주기 이때 List 다음 enter

*List 컴포넌트에서 Props 받아오기

data.id ==> id
data.completed ==> completed


React.memo를 이용한 컴포넌트 렌더링 최적화

*현재 Todo 앱의 문제점
현재 Todo 앱에서 App , Lists, List, Form 컴포넌트로 나눠져 있다. 이렇게 나눠준 이유는 재사용성을 위해서도 이지만 각 컴포넌트의 렌더링의 최적화를 위해서 이기도 하다.
Form에서 글을 타이핑을 할 때 원래는 Form 컴포넌트와 그 State 값을 가지고 있는 App 컴포넌트만 렌더링이 돼야 하는데 현재는 렌더링하지 않아도 되는 Lists 컴포넌트와 List 컴포넌트까지 다시 렌더링되고 있다.

✅ React.memo() 적용으로 문제 해결
➡️ 변화가 생긴 경우, 즉 필요한 경우에만 렌더링할 수 있도록 해준다.

*적용 후 다시 입력

렌더링할 필요 없는 Lists, List 컴포넌트가 없어졌다.


useCallback을 이용한 함수 최적화

원래 컴포넌트가 렌더링 될 때 그 안에 있는 함수도 다시 만들게 된다.
하지만 똑같은 함수를 컴포넌트가 리 렌더링 된다고 해서 계속 다시 만드는 것은 좋지 않다.
그리고 이렇게 컴포넌트가 리 렌더링 될 때마다 함수를 계속 다시 만든다고 할 때 만약 이 함수가 자식 컴포넌트에 props로 내려 준다면
함수를 포함하고 있는 컴포넌트가 리 렌더링 될 때마다
자식 컴포넌트도 함수가 새롭게 만들어지니 계속 리 렌더링 하게 된다.
(부모 컴포넌트의 함수를 props로 내려줬기 때문에 자식 컴포넌트 입장에서는 없었다가 생기는 현상이 반복)

  • 함수는 주로 최상위 컴포넌트에 넣어준다(App 컴포넌트)

*삭제 버튼 함수 App 컴포넌트로 이동
List.js -> App.js

*props로 함수 넘겨주기

App 컴포넌트가 렌더링될 때 handleClick 함수가 재생성된다.
이때 List 컴포넌트에서 props로 받아오기 때문에 각각 리렌더링된다.

*input에 입력...
원래는 React.memo로 감싸줘서 리렌더링 되지 않던 컴포넌트들이 한 글자 입력 시마다 Lists 컴포넌트와 List 컴포넌트까지 다시 리렌더링 되는 걸 볼 수 있다.

useCallback 적용은 useCallback 안에 콜백함수와 의존성 배열을 순서대로 넣어주면 된다.
함수 내에서 참조하는 state, props가 있다면 의존성 배열에 추가해주면 된다.
useCallback으로 인해서 todoData가 변하지 않는 다면 함수는 새로 생성되지 않는다.
➡️ 메모리에 새로 할당되지 않고 동일 참조 값을 사용할 수 있다.

의존성 배열에 아무것도 없다면 컴포넌트가 최초 렌더링 시에만
함수가 생성되며 그 이후에는 동일한 참조 값을 사용하는 함수가 된다.

*React.useCallback 적용으로 문제 해결
App.js

useCallback(함수, [todoData]);
todoData가 바뀔 때만 다시 생성되도록 바뀜.

*적용 후 다시 타이핑...

렌더링 할 필요 없는 List, Lists 컴포넌트가 렌더링 되지 않는다.


useMemo를 이용한 결과 값 최적화

*Memoization
비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐시된 결과를 반환하여 컴퓨터 프로그램의 속도를 높이는 데 주로 사용되는 최적화 기술이다.

Component 내의 compute 함수가 만약 복잡한 연산을 수행하면 결과 값을 리턴하는데 오랜 시간이 걸리게 된다.
컴포넌트가 계속 리 렌더링 된다면 연산을 계속 수행하는데 오랜 시간이 걸려서 성능에 안 좋은 영향을 미치게 되며, UI 지연 현상이 일어난다.
➡️ useMemo로 해결❕
compute 함수에 넘겨주는 a, b의 값이 이전과 동일하다면 컴포넌트가 리 렌더링 되더라도 연산을 다시 하지 않고 이전 렌더링 때 저장해두었던 값을 재활용하게 된다.

*useMemo 적용하기
useMemo로 감싸준 후에 첫번째 인수에 의존성 배열에 compute 함수에서 사용하는 값을 넣어준다.


리액트 확장 프로그램 추가하기

https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?

*익스텐션을 이용해서 렌더링 되는 부분 표시하기
-Components 탭에서 Highligh updates 부분을 체크해주면 쉽게 컴포넌트가 렌더링 되는 것을 확인할 수 있다.


할 일 리스트 모두 지우기 버튼 생성

App.js

Delete All 버튼 생성


할 일 목록을 수정하는 기능 추가하기


*다른 UI 제공을 위한 State 생성
List.js

*Edit 버튼 추가 & 클릭 시 isEditing State 변경

*조건에 따른 UI 렌더링
if ~ else 문


edit을 클릭하면

*editing 시 UI 작성
<form>태그 : 수정할 때 text 입력
setIsEditing(false) : false -> 작동x


드래그한 코드 때문에 중복 출력 -> 해당 코드 삭제하면 해결된다.

해결

*editing 입력할 때 editedTitle State 변경
handleEditChange 함수



수정은 되지만 저장은 안되는 상태 -> save 버튼 기능 만들어주기

*editing 입력 후 Save

save 버튼에 handleSubmit 함수 넣어준다.

save 기능 o


localStorage에 todoData값 담기

localStorage에 todoData 값을 담아서 페이지를 refresh 해도 todoData가 계속 남아 있을 수 있게 해주기

*local storage 를 사용해서 데이터를 저장하기
reference
localStorage.setItem(key, value);

*setTodoData를 이용해서 todoData State를 바꿔줄 때 localStorage에도 같이 바꿔주기

newTodo 오타 주의/위의 setTodoData 함수 인수에 맞춰줘야 된다.




  • 객체나 배열을 저장해 줄 때 JSON.stringfy를 이용해서 텍스트로 변환해준 후 저장한다. 그렇지 않으면 [object, Object] 이런식으로 나온다.

*localStorage에 저장된 todoData 활용하기

⬇️ 수정
todoData 있으면 가져오고 없으면 빈 배열

계속 오류가 나서 해결 못하고 있었는데 useState에 initialTodoData를 넣을 때 []를 넣어줘서 그런거였다.. 코딩할 때는 오타를 조심하자..❕❕

✅ 새로고침해도 리스트들이 그대로 저장되어 있다.

실행화면

0개의 댓글