[React.js 1팀] Project 2 [할 일 관리] 앱 만들기 2 (Read: 할 일 리스트 렌더링하기 ~ Delete: 할 일 삭제하기)
저번 할 일 관리 앱 만들기- 할 일 추가하기에 이어서 이번에는 할 일 리스트를 렌더링, 수정, 삭제 하는 법에 대해 알아보도록 하겠다.
Read 기능을 이용하면 배열에 저장한 할 일 아이템을 여러 개 반복해서 페이지에 렌더링할 수 있다. 배열 todo를 TodoList 컴포넌트에 Props로 전달한다.
<TodoList /> | <TodoList todo={todo}/> |
TodoList 컴포넌트에서는 App에서 Props로 전달된 todo를 리스트로 렌더링해야 한다. 이때 배열 메서드 map을 이용해서 HTML이나 컴포넌트를 순회하면서 매 요소를 반복하며 렌더링할 수 있다.
아래는 배열 메서드 map을 이용해서 HTML 요소를 반복해 렌더링한 코드이다. <TodoItem />을 삭제하고 map을 삽입하였다. 그리고 TodoItem 컴포넌트에 전달된 Props를 이 컴포넌트에서 사용할 수 있도록 TodoItem.js도 수정한다.
<div className="TodoList"> <h4>Todo List 🌱</h4> <input className="searchbar"placeholder="검색어를 입력하세요"/> <div className="list_wrapper"> {todo.map((it)=>( <TodoItem {...it}/> ))} </div> </div> |
<div className="TodoItem"> <div className="checkbox_col"> <input checked={isDone} type="checkbox" /> </div> <div className="title_col">{content}</div> <div className="date_col">{new Date(createdDate).toLocaleDateString()}</div> <div className="btn_col"> <button>삭제</button> </div> </div> |
실행 시 아래 그림처럼 ‘공부하기’,‘독서하기’ 아이템을 추가할 수 있다.
1-(1) key 설정하기
key란, 리스트에서 컴포넌트를 구분하기 위해 사용하는 값이다. 특정 컴포넌트를 수정, 추가, 삭제할 경우 이 key로 어떤 컴포넌트에 업데이트할지 결정하게 된다. 성능을 높이거나 효율적으로 탐색하기 위해서는 key로 구분해 주는 것이 좋다.
이번에는 고유 id를 key로 전달해 보도록 하겠다. 설정 후 콘솔 탭에 key가 새로 생긴 걸 볼 수 있다.
<div className="list_wrapper"> {todo.map((it)=>( <TodoItem key={it.id} {...it} /> ))} </div> |
이처럼 map을 이용해 컴포넌트를 리스트 형태로 반복적으로 렌더링하려면 리스트 내 고유한 key를 Props로 전달해야 한다.
1-(2) 검색어에 따라 필터링하기
이 파트에서는 검색 폼에서 검색어를 입력하면 해당 문자열을 포함하는 아이템만 필터링해서 보여주는 기능을 구현해 보겠다.
TodoList.js를 먼저 수정한다. 우선, react 라이브러리에서 useState 리액트 훅을 불러온 뒤, 아래 첫 번째 코드와 같이 수정한다. 그 후 함수 getSearchResult를 이용해서, search가 빈 문자열 (“”)이면 todo를 그대로 반환하고, 그렇지 않으면 todo배열에서 search의 내용과 일치하는 아이템만 필터링해 반환하도록 수정한다.
두번째 코드처럼 마지막으로 getSearchResult의 결괏값을 map 메서드를 이용해 리스트로 렌더링한다.
const TodoList = ({todo}) => {
const [search, setSearch] = useState("");
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
(...)
<input
value={search}
onChange={onChangeSearch}
className="searchbar"
placeholder="검색어를 입력하세요"
/>
const getSearchResult = () => {
return search === ""
? todo
: todo.filter((it) => it.content.includes(search));
};
(...)
<div className="list_wrapper">
{getSearchResult().map((it)=>(
<TodoItem key={it.id} {...it} />
))}
</div>
이제 검색 기능이 만들어졌다.
하지만 아직 대소문자 구별하지 못하기 때문에, 첫 번째 처럼 코드를 삽입해서 대소문자를 구별하지 않도록 수정해야 한다. toLowerCase() 메서드는 문자열에 있는 모든 대문자를 소문자로 바꿔주는 기능을 한다.
const getSearchResult = () => { return search === "" ? todo : todo.filter((it) => it.content.toLowerCase().includes(search.toLowerCase())); }; |
할 일을 생성했으면, 이제 완료한 할 일을 체크하는 기능을 만들 차례이다. 이번에는 함수 onUpdate를 생성해서 TodoList 컴포넌트에 Props로 전달하는 코드를 만들어보자. 이어서, TodoList에서 TodoItem 컴포넌트에 함수 onUpdate를 전달하는 코드를 만들어보자. 첫 번째 코드는 src/App.js에 삽입한 코드이고 두 번째 코드는 src/component/TodoList.js에서 수정한 코드이다.
const onUpdate = (targetId) => {
setTodo(
todo.map((it) =>
it.id === targetId ? { ...it, isDone: !it.isDone } : it
)
);
};
const TodoList = ({todo, onUpdate}) =>
(...)
<div className="list_wrapper">
{getSearchResult().map((it)=>(
<TodoItem key={it.id} {...it} onUpdate={onUpdate} />
))}
</div>
현재 작성한 코드의 TodoList는 TodoItem 컴포넌트에 함수 onUpdate를 전달해야 하므로 Props로 받아 다시 전달하는 일종의 매개 역할을 수행하게 된다.
리액트 컴포넌트는 바로 한 단계 아래의 자식 컴포넌트에만 데이터를 전달할 수 있다. 한 단계 이상 떨어져 있는 자식 컴포넌트에 데이터를 전달하기 위해서, 어쩔 수 없이 전달에 전달을 반복해야 한다. 위와 같은 현상을 ‘Props가 마치 땅을 파고 내려가는 것 같다’라고 하여, ‘Props Drilling’이라고 부르게 된다. 다소 비효율적인 방식이지만, 현재 파트에서는 Props Drilling으로 코드를 생성하였다.
2-(1) TodoItem 컴포넌트에서 아이템 수정 함수 호출
이제 TodoItem이 TodoList한테 전달받아서 onUpdate를 호출할 차례이다.
const TodoItem = ({ id, content, isDone, createdDate, onUpdate }) => {
const onChangeCheckbox = () => {
onUpdate(id);
};
return (
<div className="TodoItem">
<div className="checkbox_col">
<input onChange={onChangeCheckbox}
checked={isDone} type="checkbox" />
</div>
<div className="title_col">{content}</div>
<div className="date_col">
{new Date(createdDate).toLocaleDateString()}
</div>
<div className="btn_col">
<button>삭제</button>
</div>
</div>
);
};
Props를 구조 분해 할당한 후 함수 onUdate를 추가한다. 호출할 onChangeCheckbox 함수를 만들어서, onUpdate를 호출하고 인수로 현재 체크가 발생한 할 일 아이템의 id를 전달하도록 한다. 이후 체크박스 입력 폼의 onChage 이벤트 핸들러를 함수 onChangeCheckbox로 설정한다.
모든 코드가 올바르게 적용이 되었다면, 아래 그림처럼 체크박스를 체크했을 때 완료 여부를 표시하는 체크 표시가 나타나며 Components 탭에 isDone 프로퍼티가 true로 변경될 것이다.
지금은 리스트 바로 옆에 있는 ‘삭제’ 버튼을 클릭해도 할 일이 삭제되지 않는다. 체크 기능과 유사한 흐름으로 ‘삭제’ 기능을 제작해 보겠다.
3-(1) 아이템 삭제 함수
App 컴포넌트에서 할 일을 삭제하는 함수 onDelete를 만든다.
const onDelete = (targetId) => {
setTodo(todo.filter((it) => it.id !== targetId));
};
return (
<div className="App">
<Header />
<TodoEditor onCreate={onCreate}/>
<TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete}/>
</div>
);
}
‘삭제’ 버튼을 클릭하면 onDelete 함수가 호출이 되고, onDelete는 매개변수 targetId에 삭제 할 일 아이템의 id를 저장하게 된다. 해당 id 요소를 뺀 새 배열로 todo를 업데이트 함으로써 대상 아이템을 삭제할 수 있다.
그 후, Props로 받은 함수 onDelete를 다시 TodoItem 컴포넌트에 전달하는 코드를 작성한다. Props를 구조 분해 할당하고 함수 onDelete를 추가한다.
const TodoList = ({todo, onUpdate, onDelete}) =>
<div className="list_wrapper">
{getSearchResult().map((it)=>(
<TodoItem
key={it.id} {...it}
onUpdate={onUpdate}
onDelete={onDelete}
/>
))}
</div>
3-(2) TodoItem 컴포넌트에서 삭제 함수 호출하기
이제 마지막으로 TodoItem에서 삭제 버튼을 클릭하면 함수 onDelete를 호출하도록 구현하자.
const TodoItem = ({ id, content, isDone, createdDate, onUpdate, onDelete }) => {
const onChangeCheckbox = () => {
onUpdate(id);
};
const onClickDelete = () => {
onDelete(id);
};
<div className="btn_col">
<button onClick={onClickDelete}>삭제</button>
</div>
Props를 구조 분해 할당하고, 삭제 버튼을 클릭하면 호출할 함수 onClickDelete를 만든다. 위와 같이, 이제 삭제 버튼을 누르면 함수 onDelete를 호출하고 인수로 해당 아이템의 id를 전달해서 삭제할 수 있게 된다.
‘react 공부하기’의 ‘삭제’ 버튼을 클릭하게 되면 공부하기 리스트가 사라진다. 또한 콘솔 창을 확인했을 때도 사라진 것을 볼 수 있다.
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).
Corner React.js 1
Editor: IJin
[React.js 1팀] Project2 [할 일 관리] 앱 만들기 (0) | 2024.12.27 |
---|---|
[React.js 1팀] 8장. Hooks (1) | 2024.11.29 |
[React.js 1팀] project 1 [카운터] 앱 만들기 ~ 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2024.11.22 |
[React.js 1팀] 5장. 리액트의 기본 기능 다루기 (2) (0) | 2024.11.15 |
[React.js 1팀] 5장 리액트의 기본 기능 다루기 (1) (1) | 2024.11.08 |