0) 설계
인턴 첫 주차 과제로 지금까지 공부한 react를 활용하여 간단한 todo list를 만드는 과제를 수행했다.
마침 react docs를 공부하면서 표를 표현하는 방법을 배웠으니, 이를 활용해서 과제를 수행하기로 했다.
조건은 크게 ①입력을 통해 todolist에 값을 추가할 수 있을 것 ② 내용을 클릭하면 취소선이 그어질 것 ③ Delete 버튼 구현이 있었다.
첫 설계는 다음과 같았다.
① todolist를 모든 함수에서 사용할 수 있도록 TODO = []를 전역변수로 선언.
② <EditableToDoList>에 insert box와 <ToDoList>를 선언하고, <ToDoList>에는 TODO의 내용을 <ToDoItem>을 반복문처럼 반환.
③ <ToDoItem>은 index, ToDo의 내용 및 취소선 여부를 저장하는 object로 구현.
하지만, 첫 react 설계이고 명령형 언어가 아닌 선언형을 처음 다루다 보니 여러모로 문제가 많았다..
1) hierarchy 관리
React Docs에서 보았던 상품 array의 구조를 참고하여 구현하려고 했더니 Insert함수에서 TODO를 처리하는 방법을 알 수 없었다. 괜히 React에서는 data flow가 위에서 아래로 움직인다고 했던 내용도 보았었고, Insert가 TODO를 업데이트 하는 방법이 도저히 생각이 안 났던 것.
지금까지 사용했었던 명령형 언어에서는 전역변수나 로컬변수를 내 마음대로 다룰 수 있었지만, data flow가 존재하고 state를 통해 dynamic한 변수를 관리해야한다는 점이 너무 어색하게 다가왔다. 결과적으로는 TODO를 전역변수로 선언한 것이 패착이었다.
2) 전역변수에 접근하는 방법?
앞서 말한 것 처럼 가장 큰 문제는 'TODO'라는 할 일 array를 관리하는 것이었는데, insert로 update된 array를 어떻게 부모 컴포넌트인 <EditableToDoList>와 형제 컴포넌트인 <ToDoList>에 반환하는가 였다.
결국 알게 된 것은 부모 컴포넌트에서 state와 setter함수를 props로 넘겨주면 자식 컴포넌트에서도 사용할 수 있다는 것이었다. 이게 아마 이번 to do list를 구현하면서 알게 된 가장 큰 내용이지 않을까.
3) Array와 Object
다른 언어들과 마찬가지로 array라는 type이 따로 존재할 줄 알았는데, array도 결국 object라고 하더라.
ToDo list를 반복문을 통해 하나씩 출력하는 코드를 처음에 이렇게 작성했었다.
위와 같은 오류메시지가 나타났고, 나는 array로 선언했는데 왜 object라고 하는지 이해할 수 없어서 좀 오래 해맸었다.
이는 props와 component에 대한 이해가 부족했던 탓이었다.
4) 구현
여러모로 해매면서 어떻게 state를 다른 component로 넘겨줄까 고민하다가 낸 해결책은 모든 함수를 하나의 component에 구현하는 것이었다.(...)
import { useState } from "react";
import { useCallback, useState } from "react";
import React, { Component } from "react";
import { NextPage } from "next";
let nextIdx = 0;
function EditableToDoList({ todos }) {
const [todo, setTodo] = useState([]);
const [inputs, setInputs] = useState("");
const onChange = (e) => {
setInputs(e.target.value);
};
const addData = (e) => {
if (e.key === "Enter") {
console.log(e.target.value);
setTodo([...todo, { idx: nextIdx++, content: e.target.value, done: false }]);
setInputs("");
console.log(todo);
}
};
return (
<div>
<InsertBar inputText={inputText} />
<ToDoList todos={todos} />
<div>
<input
type="text"
value={inputs}
placeholder=" Input here..."
onChange={(e) => onChange(e)}
onKeyPress={(e) => addData(e)}
/>
</div>
<div>
{todo.map((todoitem) => (
<ToDoItem todoitem={todoitem}/>
))}
</div>
</div>
);
}
function ToDoItem({ todoitem }) {
return (
<div>
<ui>{content}</ui>
<b>- {todoitem.content}</b> <span>[Delete]</span>
</div>
);
}
const TODO = [
{ idx: 0, content: "First", done: false },
{ idx: 1, content: "Second", done: false },
{ idx: 2, content: "Third", done: false },
];
const Home: NextPage = () => {
return <EditableToDoList todos={TODO} />;
};
export default Home;
참 멋대로 짠 코드이다.
5) component 분할
이제 이 component를 분할하는 것이 일이었다. 전역변수로 선언했던 array를 삭제한 후, 삭제했던 array와 이를 관리하는 useState를 EditableToDoList(최상위 컴포넌트)에 선언했다. 이 setter함수와 todo를 통째로 InsertBar와 ToDoList에 넘겨주었다.
- index.tsx
import { useCallback, useState } from "react";
import React, { Component } from "react";
import { NextPage } from "next";
import InsertBar from "../components/InsertBar";
import ToDoList from "../components/ToDoList";
const Home: NextPage = () => {
const [todo, setTodo] = useState([]);
return (
<>
<div>
<b>ToDo</b>
<InsertBar todo={todo} setTodo={setTodo} />
<ToDoList todo={todo} setTodo={setTodo} />
</div>
</>
);
};
export default Home;
이제 각 기능을 InsertBar 및 ToDoList에 구현하면 끝.
- InsertBar.tsx
import React, { useRef, useState } from "react";
let nextIdx = 0;
const InsertBar = ({ todo, setTodo }) => {
const [inputs, setInputs] = useState("");
const onChange = (e) => {
setInputs(e.target.value);
};
const addData = (e) => {
if (e.key === "Enter") {
const newTodoList = todo.concat({
idx: nextIdx++,
content: e.target.value,
done: false,
});
setTodo(newTodoList);
setInputs("");
}
};
return (
<form onSubmit={(e) => e.preventDefault()}>
<input value={inputs} placeholder=" Insert Here ... " onChange={onChange} onKeyPress={addData} />
</form>
);
};
export default InsertBar;
이때 onSubmit={(e) => e.preventDefault()를 사용하지 않으면 계속 리디렉션이 일어났다. 이는 고대 php에 있던 문제라고 하는데, 자세한 것은 추후 찾아보기로..
- ToDoList
import React from "react";
import ToDoItem from "./ToDoItem";
import DoneItem from "./DoneItem";
const ToDoList = ({ todo, setTodo }) => (
<div>
<ul>
{todo.map((todoitem) => {
if (todoitem.done) return <DoneItem key={todoitem.idx} todoitem={todoitem} todo={todo} setTodo={setTodo} />;
else return <ToDoItem key={todoitem.idx} todoitem={todoitem} todo={todo} setTodo={setTodo} />;
})}
</ul>
</div>
);
export default ToDoList;
원래는 ToDoItem으로 하위 component를 구성하려고 했는데, 클릭했을 때 취소선을 구현하는 부분을 어떻게 해야할 지 모르겠어서 아예 취소선을 포함한 component인 DoneItem을 추가했다.
이때 각 Item component는 클릭된 경우 본인의 boolean 값을 toggle한다.
import React, { useEffect, useRef, useState } from "react";
const ToDoItem = ({ todoitem, todo, setTodo }) => {
console.log("ITEM");
const toggleDone = () => {
const newTodoList = todo.map((item) => ({
...item,
done: item.idx === todoitem.idx ? !item.done : item.done,
}));
setTodo(newTodoList);
};
return (
<div>
<button onClick={toggleDone}> - {todoitem.content} </button>
{/* DoneItem의 경우 <s>- {todoitem.content}</s> */}
<button
type="button"
onClick={() => {
setTodo(todo.filter((e) => e.idx !== todoitem.idx));
}}
>
[DELETE]
</button>
</div>
);
};
export default ToDoItem;
구현 결과
'개발 > React' 카테고리의 다른 글
React - 7) DOM, Rendering (0) | 2022.07.04 |
---|---|
React - 6) To-Do List ~ Local Storage (0) | 2022.07.01 |
React - 4) State (0) | 2022.06.24 |
React - 3) Interactivity (0) | 2022.06.24 |
React - 2) React Design (0) | 2022.06.23 |