이번주는 본격 리액트 입문 주차로써 제공된 강의 + 실시간 강의 + 개인과제를 통해 리액트를 접했다.
생각보다 과제할 시간이 촉박할 것 같아서 제공된 강의는 금,토,일 동안 완강했다!
아직은 jsx 가 새롭고 익숙하지 않지만, 확실히 바닐라 자바스크립트로 기능구현하다가 리액트를 사용하니 뭔가... 문법도 더 정리된 느낌이고 틀이 있어서 편하다.!!
어려울거라고 생각했는데 오히려 재밌고 편하다고 느끼면서 공부하고 있다.
지금까지 배운바로는 리액트에서 가장 중요한 부분이
-> props 와 useState 를 통한 리렌더링, 불변성유지,컴포넌트 분리 같다.
레이아웃에 대한 CSS는 스타일링 부분만 묶어서 속성과 개념에 익숙해지기위해 따로 글을 작성할 계획이다!
이번주는 Todo List 개인과제를 통해 기능적인 부분으로 props 와 useState, 불변성을 위한 메서드 사용을 익혔다.
useState 사용법
import { useState } from 'react';를 통해 useState 함수를 불러오기const [state, setState] = useState(initialValue);와 같이 사용하기
- state: 상태 값을 나타내는 변수
- setState: 상태 값을 업데이트하는 함수
- initialValue: 초기 상태 값
나는 계속 변경되어야하는 전체 계획 리스트인 plans / 사용자 입력을 받는 부분인 title 과 body 까지 총 3개의 useState 를 사용했다.
const [plans, setPlan] = useState([]);
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
React는 상태 변화를 정확히 감지하고 컴포넌트를 올바르게 리렌더링하기 위해 상태 객체나 배열을 업데이트할 때는 불변성을 유지해야 한다. 따라서 기존 상태를 복사하여 새로운 객체나 배열을 만든 후 업데이트를 했다.
계획 리스트 추가 (setPlan([...plans, newPlan])) : 스프레드 연산자 [...] 사용
새로운 계획을 추가할 때, 기존의 plans 배열에 직접적으로 변화를 주는 것이 아니라,
기존 배열을 펼치고(...plans), 새로운 계획 객체(newPlan)를 배열의 끝에 추가하여 새로운 배열을 생성했다. 이렇게 함으로써 기존의 상태 배열은 변경되지 않고, 새로운 상태 배열이 생성되어 불변성이 유지된다.
계획 삭제 (setPlan(deletedPlans)) : filter( )
특정 계획을 삭제할 때, filter 함수를 사용하여 해당 ID를 제외한 새로운 배열을 생성.
이 과정에서 기존의 plans 배열을 직접 수정하지 않고, 조건에 맞는 새로운 배열을 생성함으로써 불변성을 유지된다.
계획 완료 상태 토글 (setPlan(completedPlans)) : map( )
계획의 완료 상태를 토글할 때, map 함수를 사용하여 특정 ID의 계획만 isDone 상태를 변경하고, 나머지 계획은 그대로 유지하는 새로운 배열을 생성했다. 여기서도 map 함수는 기존 배열을 직접 변경하지 않고, 새로운 배열을 반환함으로써 불변성을 유지하도록 했다.
이러한 방식으로 상태 업데이트 시 기존 상태를 직접 변경하지 않는 연습을 했다.
상태 값(plans, title, body)과 상태를 업데이트하는 함수(setPlan, setTitle, setBody)는 InputForm / PlansList 컴포넌트에 props로 전달된다. props 를 통해 상위 컴포넌트인 App에서 관리되는 상태 값을 하위 컴포넌트가 접근하고 사용할 수 있다.
InputForm 컴포넌트 :
사용자가 입력한 새로운 제목과 내용을 상태로 관리하기 위해 title, setTitle, body, setBody를 props로 받는다.
그 다음 사용자의 입력에 따라 이 함수들을 호출하여 상위 컴포넌트인 App의 상태를 업데이트한다.
PlansList 컴포넌트 :
계획 목록을 표시하고, 계획의 삭제 / 완료 처리 기능을 한다.
이 컴포넌트는 plans, deleteUserHandler, completedHandler를 props로 받아, 해당 상태 및 함수들을 사용하여 삭제, 완료처리 등을 통해 계획목록의 상태를 변경한다.
이러한 방식으로 App 컴포넌트는 자신의 상태를 하위 컴포넌트에 전달하고, 하위 컴포넌트는 이 상태를 사용하거나 변경하는 역할을 한다.
Working 계획과 Done 계획을 분리시킬때, 처음에는 완료 버튼을 누르면 버튼 누른 plan 들만 따로 모으는 배열을 만들었었다.
-> 불변성 지키기 위해
filter / find사용.
-> On / Off 왔다갔다 한다. -> 토글링.
-> isDone 값을 토글하는 방식은 주어진 id에 해당하는 계획을 찾아서 해당 계획의 isDone 값을 반전시키는 것을 말한다.
자바스크립트에서 토글
classList.toggle() 메서드를 사용하여 요소의 CSS 클래스를 토글할 수 있다.
이 메서드는 해당 클래스가 존재하면 제거하고, 존재하지 않으면 추가한다.
이를 통해 요소의 표시/숨김, 스타일 변경 등을 쉽게 구현할 수 있다.
리액트에서 토글
리액트에서는 상태(state)를 이용하여 토글 기능을 구현한다.
컴포넌트의 상태를 true/false로 관리하고, 이를 기반으로 UI를 렌더링한다.
사용자 이벤트(클릭 등)에 따라 상태를 변경하여 토글 기능을 구현할 수 있다.
리액트 훅(Hooks)인 useState를 사용하면 간단하게 토글 기능을 구현가능하다.
-> isDone 값을 토글하는 방식을 사용했다.
const completedPlans = plans.map((plan) => plan.id === id ? { ...plan, isDone: !plan.isDone } : plan );
- plans 배열을 순회하면서 각 계획의 주어진 id 와 일치하는지 확인.
- 일치하는 경우 해당 계획의 isDone 값을 반전시킨 객체를 반환하고, 일치하지 않는경우에는 기존의 객체를 그대로 유지한다. (기존 plan 객체 복사 / isDone 속성만 반전시킴)
->불변성을 유지하며 특정 속성만 변경할 수 있다.
조건부 스타일링을 사용했다.// isDone 상태에 따라 클래스 이름 결정 const squareClass = isDone ? "square done" : "square"; return ( <div className={squareClass}> <div className="content-wrapper"> <p className="title">{title}</p> <p className="body">{body}</p> </div> <div className="twoButton"> <button onClick={() => deleteUserHandler(id)}>삭제</button> <button onClick={() => completedHandler(id)}> {isDone ? "취소" : "완료"} </button> </div> </div> );
- 삼항연산자는 표현식이기때문에 값을 반환할 수 있다.
-> 따라서 변수에 할당이 가능하므로 squareClass 라는 변수에 "square done" 또는 "square" 문자열이 할당된다.
-> 그 다음 squareClass 라는 변수의 값이 div 태그의 className 속성에 사용되어 클래스명이 동적으로 결정된다.- 또한, 버튼의 텍스트도
{isDone ? "취소" : "완료"}사용하여 true/false 에 따라 다른 텍스트가 보이도록 했다.

new Date().getTime() 로 id를 생성했는데, 리팩토링하면서 이 방법 말고도 고유한 식별자를 만들수있는 uuid 로 바꿨다.
UUID 란?
네트워크 상에서 고유성이 보장되는 ID를 만들기 위한 표준 규약.
128비트 숫자로 이루어져 있으며, 일반적으로 8-4-4-4-12의 형태로 표현된다.
UUID 사용법
npm install uuid
import { v4 as uuidv4 } from 'uuid';const newPlan = { id: uuidv4(), // UUID를 사용하여 고유한 ID 생성 title: title, body: body, isDone: false, };
- 다른 예시
import React from 'react'; import { v4 as uuidv4 } from 'uuid'; function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li key={uuidv4()}>{todo.text}</li> // 각 todo 아이템에 고유한 key 할당 ))} </ul> ); }
컴포넌트 분리하다가 알게된 사실인데, 리액트에서 이제 더이상 import React from 'react'; 를 명시적으로 작성하지 않아도 된다.
이 변화는 JSX 코드를 변환할 때 필요한 리액트 패키지의 React.createElement 함수를 자동으로 포함시키는 새로운 JSX 변환 방식 덕분에 가능해졌다. 이전에는 JSX 코드를 자바스크립트로 변환할 때 React.createElement 호출이 필요했기 때문에, 모든 파일 상단에 리액트를 임포트하는 구문이 필요했다. 하지만 리액트 17 이후부터는 JSX를 사용하는 파일에서 리액트를 직접 임포트하지 않아도 자동으로 처리된다.
InputForm 을 통해 입력받은 제목과 내용으로 계획을 생성할때, 빈칸을 제출해도 생성되는 것을 막기위해 유효성 검사를 추가했다. title이나 body 중 하나라도 빈 문자열인 경우, 모달 알림창이 뜨고 바로 return 되도록 했다.
const onSubmitHandler = (event) => { event.preventDefault(); // title 또는 body가 비어있으면 추가하지 않음 if (!title.trim() || !body.trim()) { swal("실패!", "제목과 내용을 모두 입력해주세요.", "error"); return; // 함수를 여기서 종료시켜 추가 로직을 실행하지 않음 } const newPlan = { id: new Date().getTime(), title: title, body: body, isDone: false, }; // 계획리스트 추가. setPlan([...plans, newPlan]); // 입력칸 빈칸으로 만들기 setTitle(""); setBody(""); };
trim( ) 메서드사용 : title과 body 값의 앞,뒤 공백을 제거하여 사용자가 공백만 입력한 경우에도 유효성 검사를 통과하지 못하게 했다.- alert 대신
swal사용