오늘은 지난 시간에 이어 간단한 일기장 프로젝트의 추가 기능을 구현해보는 날이다
저번 시간에는 일기장의 틀만 구성했다면 오늘은 추가, 삭제, 수정 기능을 구현하고 Lifecycle을 제어하는 방법에 대해서 배워볼 것이다
CRUD는 모든 서비스에서 가장 기본적인 기능이기 때문에 오늘 강의를 잘 듣고 확실하게 배워두어야겠다
오늘도 그럼 화이팅!
# React에서 배열 사용하기 2 - 데이터 추가
배열을 이용한 React의 List에 아이템을 동적으로 추가해보자
또한 React처럼 생각하는 방법을 배워보자
리액트로 프로젝트를 개발할 때 코드를 작성하는 것도 중요하지만 그 전에 컴포넌트 트리에 대해서 생각해보아야 한다
App 컴포넌트를 부모 컴포넌트로 하여 DiaryEditor 컴포넌트와 DiaryList 컴포넌트가 형제 관계로 이루어져있다
이 때 형제 관계 컴포넌트끼리는 props를 주고 받을 수 없다
따라서 DiaryEditor 컴포넌트에서 DiaryList 컴포넌트로 데이터를 주고 받을 때는 무조건 App 컴포넌트를 거쳐야 한다
즉, React는 무조건 단방향으로만 데이터가 흐른다
이와 같이 App 컴포넌트에서 하위 컴포넌트들의 데이터를 관리할 수 있다
# 최상위 컴포넌트에서 Props 관리
최상위 컴포넌트인 App.js 에서 자식 컴포넌트들의 데이터를 관리해보자
useState를 사용해서 일기 데이터 배열을 관리해보자
function App() {
//일기 데이터 배열 관리
const [data, setData] = useState([]);
//id 값 할당 변수
const dataId = useRef(0);
//새로운 일기 작성 메소드
const onCreate = (author, content, emotion) => {
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id: dataId.current,
};
dataId.current += 1;
setData([newItem, ...data]);
};
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<DiaryList diaryList={data} />
</div>
);
}
DiaryEditor 와 DiaryList 컴포넌트 동시에 Props 를 전달시켜주기 위해 data, setData를 할당하여 관리한다
# onCreate 메소드
author, content, emotion 값을 받는다
이때 생성 시간인 create_date는 일기가 작성될 때마다 생성해준다
새로운 일기 객체를 생성해주고 기존 id에서 1을 더해 다음 일기 작성 시 사용할 수 있도록 한다
setData 로 새로운 일기를 추가해주는데, 이때 새로운 일기가 가장 상단에 뜨기 위해서는
newItem 을 먼저 오도록 하고 기존의 일기 데이터는 spread 연산자를 사용하여 붙여준다
# DiaryEditor 에서 Props 사용
DiaryEditor에서는 Props로 받은 onCreate 메소드를 onClick 시 실행되도록 작성해준다
const handleSubmit = () => {
if (state.author.length < 1) {
authorInput.current.focus();
return;
}
if (state.content.length < 5) {
contentInput.current.focus();
return;
}
onCreate(state.author, state.content, state.emotion);
alert("저장 성공!");
};
여기까지 구현하니 일기가 잘 저장된 모습을 확인할 수 있
하지만 저장하기 버튼을 눌러서 새로운 일기를 등록하고 나면 기존의 폼의 내용은 초기화시켜야 한다
이를 위해서 setState를 사용해서 다시 기본값으로 초기화시킨다!
const handleSubmit = () => {
if (state.author.length < 1) {
authorInput.current.focus();
return;
}
if (state.content.length < 5) {
contentInput.current.focus();
return;
}
onCreate(state.author, state.content, state.emotion);
alert("저장 성공!");
setState({
author: "",
content: "",
emotion: 1,
});
};
# React에서 배열 사용하기 3 - 데이터 삭제
다이어리 리스트 아이템마다 삭제버튼을 추가하고 다이어리를 삭제해보자
삭제하기 버튼을 클릭하면 data state를 업데이트 시켜주어야 한다
전체적인 흐름은 다음과 같다
1. 최상위 컴포넌트인 App.js에서 다이어리 삭제 메소드를 생성하여 자식 컴포넌트에게 전달한다
2. DiaryList 컴포넌트에서 자식 컴포넌트인 DiaryListItem 컴포넌트로 해당 메소드를 전달한다
3. DiaryListItem 컴포넌트에서 해당 메소드를 사용한다
# Props 전달하기
# onDelete 메소드
삭제하려는 id를 가진 일기 데이터를 삭제한 후 filter 메소드를 사용해서 새로운 일기 배열을 생성한다
그리고 그 배열을 setData에 적용시켜 새로운 일기 배열로 변경한
// App.js 컴포넌트
// 일기 삭제 메소드 생성 후 DiaryList 컴포넌트에 전달
// 일기 삭제 메소드
const onDelete = (targetId) => {
console.log(`${targetId}가 삭제되었습니다`);
//삭제하려는 id의 리스트를 제외한 다른 다이어리 리스트들을
//filter 를 사용해서 새로운 다이어리 리스트 배열을 생성한다
const newDiaryList = data.filter((it) => it.id !== targetId);
//새로 생성된 다이어리 배열로 data 변경
setData(newDiaryList);
};
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<DiaryList diaryList={data} onDelete={onDelete} />
</div>
);
// DiaryList 컴포넌트는 자식 컴포넌트인 DiaryListItem 컴포넌트로 Props 전달
const DiaryList = ({ onDelete, diaryList }) => {
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다</h4>
<div>
{diaryList.map((it) => (
<DiaryListItem key={it.id} {...it} onDelete={onDelete} />
))}
</div>
</div>
);
};
//DiaryListItem 컴포넌트에서 해당 메소드 사용 가능
# DiaryEditorList 컴포넌트로 Props 전달
DiaryEditorList 컴포넌트에서는 onDelete 메소드를 사용해서 onClick 시 메소드가 실행되도록 한다
//DiaryListItem 컴포넌트 삭제 버튼에서
// onDelete 메소드 실행
<button
onClick={() => {
if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
onDelete(id);
}
}}
>
정상적으로 삭제가 완료된 모습이다!
# React에서 배열 사용하기 4 - 데이터 수정
배열을 이용한 React의 List에 아이템을 동적으로 수정해보고 조건부 렌더링에 대해서 알아보자
# 수정 메소드 Props 로 전달하기
데이터 추가, 삭제와 마찬가지로 데이터는 단방향으로 흐르기 때문에 App.js에 수정 메소드를 작성한 후 하위 컴포넌트에게 Props로 전달해주어야 한다
# App.js 에서 수정 메소드 작성
수정한 대상의 id를 가진 일기 내용을 수정해서 기존의 일기들과 합친 후 새로운 배열로 만들어 setData에 전달한다
자세한 내용은 아래의 코드를 참고하자
// App.js 수정 메소드 및 Props 전달
// 일기 수정 메소드
const onEdit = (targetId, newContent) => {
setData(
//수정 대상의 id의 일기 내용을 수정 후 기존의 일기들과
//새로운 배열로 생성하여 setData에 전달한다
data.map((it) =>
it.id === targetId ? { ...it, content: newContent } : it
)
);
};
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<DiaryList diaryList={data} onRemove={onRemove} onEdit={onEdit} />
</div>
);
# DiaryListItem 컴포넌트에서 구체적인 로직 작성하기 - 1
수정을 실행할 DiaryListItem 컴포넌트에서는 해당 메소드를 Props로 받아서 구체적인 로직을 작성한다
1. 수정 중인지 수정 중이 아닌지를 확인하여 각각 다른 버튼을 띄우기
수정하기 버튼을 누르면 isEdit의 상태를 true로 변경한다
이 때 토글 반전연산을 이용한다
//수정 state
const [isEdit, setIsEdit] = useState(false);
//토글 반전연산
const toggleIsEdit = () => setIsEdit(!isEdit);
{isEdit ? (
<>
<textarea
ref={LocalContentInput}
value={localContent}
onChange={(e) => setLocalContent(e.target.value)}
/>
</>
) : (
<>{content}</>
)}
</div>
{isEdit ? (
<>
<button onClick={handleQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
) : (
<>
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
해당 기능을 적용한 화면은 다음과 같다
# DiaryListItem 컴포넌트에서 구체적인 로직 작성하기 - 2
2. 기존에 입력된 내용을 수정 시에도 그대로 띄우기
useState를 이용해 기존 내용인 content로 초기값을 설정해준다
localContent 라는 변수를 이용해서 수정 시 내용을 관리한다
또한 수정 중에 수정 취소 버튼을 누르면 기존의 content 내용으로 다시 setLocalContent에 넣어준다
이 모든 내용을 담은 코드는 다음과 같다
//원래 내용을 수정 시에도 바로 띄워주기 -> useState에 content를 넣어주면 됨
const [localContent, setLocalContent] = useState(content);
// 수정 취소 버튼 눌러도 기존 content 내용이 뜨도록 하는 메소드
const handleQuitEdit = () => {
setIsEdit(false);
setLocalContent(content);
};
const LocalContentInput = useRef();
const handleEdit = () => {
if (localContent.length < 5) {
LocalContentInput.current.focus();
return;
}
if (window.confirm(`${id}번째 일기를 수정하시겠습니가?`)) {
onEdit(id, localContent);
toggleIsEdit();
}
};
또한 기존에 본문 내용이 5글자 이하라면 저장되지 않고 다시 textarea로 focus 하는 기능을 넣어주었는데
수정 시에도 마찬가지로 수정한 내용이 5글자보다 적다면 수정되지 않도록 하는 useRef 기능을 추가해주면 된다
따라서 수정 기능이 추가된 DiaryListItem의 최종 코드는 다음과 같다
import React, { useRef, useState } from "react";
const DiaryListItem = ({
id,
author,
content,
emotion,
created_date,
onRemove,
onEdit,
}) => {
//수정 state
const [isEdit, setIsEdit] = useState(false);
//토글 반전연산
const toggleIsEdit = () => setIsEdit(!isEdit);
//원래 내용을 수정 시에도 바로 띄워주기 -> useState에 content를 넣어주면 됨
const [localContent, setLocalContent] = useState(content);
const handleRemove = () => {
if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
onRemove(id);
}
};
// 수정 취소 버튼 눌러도 기존 content 내용이 뜨도록 하는 메소드
const handleQuitEdit = () => {
setIsEdit(false);
setLocalContent(content);
};
const LocalContentInput = useRef();
const handleEdit = () => {
if (localContent.length < 5) {
LocalContentInput.current.focus();
return;
}
if (window.confirm(`${id}번째 일기를 수정하시겠습니가?`)) {
onEdit(id, localContent);
toggleIsEdit();
}
};
return (
<div className="DiaryListItem">
<div className="info">
<span>
작성자 : {author} | 감정점수 : {emotion}
</span>
<br />
<span className="date">{new Date(created_date).toLocaleString}</span>
</div>
<div className="content">
{isEdit ? (
<>
<textarea
ref={LocalContentInput}
value={localContent}
onChange={(e) => setLocalContent(e.target.value)}
/>
</>
) : (
<>{content}</>
)}
</div>
{isEdit ? (
<>
<button onClick={handleQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
) : (
<>
<button onClick={handleRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};
export default DiaryListItem;
# React Lifecycle 제어하기 - useEffect
# Lifecycle이란?
프로그램이 실행되고 종료되는 과정
소프트웨어 개발에서도 Lifecycle 이라는 단어를 자주 사용한다
# React 컴포넌트의 Lifecycle
리액트 컴포넌트의 생애 주기는 다음과 같다
1. 탄생 : Mount, 화면에 나타나는 것
2. 변화 : Update, 업데이트(리렌더)
3. 죽음 : Unmount, 화면에서 사라짐
그렇다면 컴포넌트의 생애 주기를 왜 제어해야 하는 것일까?
컴포넌트의 변화 순간에 각각의 어떤 작업을 수행 시킨다 === 라이프 사이클을 제어한다
결국 소프트웨어를 더욱 견고하고 효율적으로 개발하기 위해 라이프 사이클, 즉 생애 주기를 제어해야 하는 것이다
# Class 형 컴포넌트 Lifecycle vs. 함수형 컴포넌트 Lifecycle
리액트 컴포넌트 각각의 라이프 사이클마다 실행할 수 있는 메서드가 있다
▶ ComponentDidMount -> 탄생시
▶ ComponentDidUpdate -> 변화 시
▶ ComponentWillUnmount -> 화면에서 사라질 때
하지만 이런 메서드들은 class 형 컴포넌트에서만 사용 가능하다
그럼 함수형 컴포넌트에서는 어떻게 할까?
React Hooks 로 가능하다!
# React Hooks
use 키워드를 붙여서 class 형 컴포넌트가 가지고 있는 메서드들을 사용하는 것이다
ex) useState, useEffect, useRef....
class형 컴포넌트에는 라이프 사이클 제어를 위한 다양한 메소드들이 있지만 함수형 컴포넌트보다 코드의 길이가 긴 문제가 존재했다
또한 코드의 중복 및 가독성 저하 등등의 문제점이 있었다
따라서 함수형 컴포넌트에서 Class형 컴포넌트의 장점을 사용하기 위해 Hooks 가 등장하였다
그 대표적인 예로 useEffect에 대해서 알아보자
# useEffect
useEffect 의 기본 형식은 아래와 같다
배열 내의 값이 변하면 useEffect는 그것을 감지해서 callback 함수를 실행시킨다
//useEffect 기본 형식
useEffect(() => {
// 2. 여기에 위치한 Callback 함수가 수행된다
.....
},
// 1. 배열 내에 들어있는 값이 변화하면
[]);
Lifecycle 을 확인해볼 새로운 컴포넌트를 생성하여 App.js에 import 시켜보자
# Mount
간단하게 Mount 되는 시점을 확인해보기 위한 코드를 작성 후 확인해보자
//마운트 시
useEffect(() => {
console.log("MOUNT!");
}, []);
컴포넌트가 마운트되는 시점에만 콘솔에 찍히기 때문에 + 버튼을 눌러도 찍히지 않는다
마운트 시점에만 실행되게 하려면 빈 배열만 넣자!
# Update
컴포넌트가 업데이트 되는 순간을 확인해보자
컴포넌트가 언제 업데이트 될까?
1. state 변경
2. 부모에게 전달받는 props 변경
3. 부모 컴포넌트 리렌더링
아래의 코드와 같이 배열을 아예 제거해주면 모든 변화에 콘솔이 찍힌다
//업데이트 시
useEffect(() => {
console.log("UPDATE!");
});
이와 같은 원리로 count 값과 text 값이 변할 때 useEffect 를 사용해서 확인해보자
//count 값이 변할 때
useEffect(() => {
console.log(`count is update ${count}`);
}, [count]);
//text 값이 변할 때
useEffect(() => {
console.log(`count is update ${text}`);
}, [text]);
count와 text 값이 변할 때마다 update 도 일어나고 있는 것을 확인할 수 있다
따라서 dependency array를 잘 활용하여 감지하고 싶은 값이 변화하는 순간에만 callback 함수를 실행시킬 수 있다
# Unmount
테스트를 해볼 Unmount 컴포넌트를 생성하고 확인해보자
버튼을 눌러 on 상태가 되면 하단에 Unmount 컴포넌트가 뜨고
다시 버튼을 눌러 off 상태가 되면 하단 컴포넌트가 사라진다
이를 useEffect를 이용해서 확인해보니 사라질 때 Unmount 가 콘솔에 찍히는 것을 확인할 수 있다
# 새로 알게 된 점
이번 강의를 통해서 리액트 컴포넌트의 props 전달 방식과 구현 방법에 대해 정말 많이 배웠다
그 중 수정 메소드를 구현한 것이 가장 어렵기도 하지만 기억에 많이 남는다
토글을 사용하여 수정 중인지 아닌지를 확인하는 방법은 나중에 어떤 메소드를 구현하더라도 유용하게 사용할 수 있을 것이라는 생각이 든다
또한 수정 중에 변하는 값을 저장하기 위해서는 localContent 를 선언하고 set 해주는 등의 생각보다 복잡한 작업이 필요하다는 것을 느꼈다
추가와 삭제는 무난하게 들었지만 수정 부분은 꼭 다시 한번 복습해야겠다
또한 class형 함수의 단점을 해소하고 장점을 사용하기 위해서 Hooks라는 것이 도입되었다고 하는데
이러한 생각을 어떻게 하고 기술적으로 어떻게 개발해서 도입시킨 것인지 놀랍다
리액트에 대한 근본적인 원리부터 시작하여 Hooks에 대해서 더 깊게 공부하여 단순히 리액트를 사용해서 개발할 줄 아는 개발자가 아니라 리액트를 완전히 이해하고 사용하는 개발자가 되어야겠다는 생각이 들었다
'TIL > 프로그래머스 데브코스' 카테고리의 다른 글
클라우딩 어플리케이션 엔지니어링 TIL Day 31 - 리액트 기본 간단한 일기장 만들기 (4) (2) | 2024.02.23 |
---|---|
클라우딩 어플리케이션 엔지니어링 TIL Day 30 - React 기본 간단한 일기장 만들기 (3) (1) | 2024.02.17 |
클라우딩 어플리케이션 엔지니어링 TIL Day 28 - React 기본 간단한 일기장 만들기 (1) (0) | 2024.02.09 |
클라우딩 어플리케이션 엔지니어링 TIL Day 27 - React 기초 (1) | 2024.02.09 |
클라우딩 어플리케이션 엔지니어링 TIL Day 25 - 프로토타입 제작하기 (1) | 2024.02.09 |