티스토리 뷰
React로 Todo-List 만들기
▶ 구현 순서
1) Form 개발하기
2) 리스트 표현하기
3) 유연하게 State 변경하기
4) CSS로 꾸미기
5) 부가 기능 구현
▶ Form 개발하기
- Form 컴포넌트 내부에서 State를 이용해 사용자의 입력을 저장/관리한다.
- Form의 Submit 이벤트가 발생되면 부모 컴포넌트로 그 데이터를 전달한다.
// InsertForm.js
import React, { useState } from 'react';
function InsertForm({ onInsert }) {
const [inputValue, setInputValue] = useState("");
const handleSumbit = (e) => {
e.preventDefault();
if (typeof onInsert === "function" && inputValue) { // onInsert가 정상적인 함수인지 확인하여 에러를 방지
onInsert(inputValue);
}
setInputValue(""); // 등록 후 input의 value값 초기화
}
return (
<form onSubmit={handleSumbit}>
<input value={inputValue} onChange={(e) => {
setInputValue(e.target.value)
}} />
<button type="submit">등록</button>
</form>
);
}
export default InsertForm;
▶ 리스트 표현하기
Form으로부터 전달받은 값들을 리스트에 저장하고 리스트의 값을 순차적으로 화면에 출력한다.
- Form으로부터 전달받은 값을 todoList array에 push하는 로직
- todoList를 props로 전달받아 화면에 출력하는 ListView 컴포넌트
// ListView.js
import React from "react";
const ListView = ({todoList}) => {
return (
<div>
<ol>
{todoList.map((item) => {
return (
<li key={item.key}>
<span>{item.value}</span>
<button type="button">완료</button>
<button type="button">삭제</button>
</li>
);
})}
</ol>
</div>
)
}
export default ListView;
▶ 유연하게 State 변경하기
리스트의 값을 변경 및 삭제하는 기능을 구현한다.
- ListView 내 Item의 완료 버튼을 클릭할 때 부모 컴포넌트로 완료 명령을 내릴 수 있는 이벤트 작성
- ListView 내 Item의 삭제 버튼을 클릭할 때 부모 컴포넌트로 삭제 명령을 내릴 수 있는 이벤트 작성
- App 컴포넌트에서 ListView의 완료 및 삭제 이벤트 핸들링 함수 작성
// ListView.js
import React from "react";
const ListView = ({todoList, onComplete, onRemove}) => {
return (
<div>
<ol>
{todoList.map((item, index) => {
return (
<li key={item.key}>
<span>{item.value}</span>
<button type="button" onClick={() => {
if(typeof onComplete === "function") {
onComplete(index);
}
}}>완료</button>
<button type="button" onClick={() => {
if(typeof onRemove === "function") {
onRemove(index);
}
}}>삭제</button>
</li>
);
})}
</ol>
</div>
)
}
export default ListView;
// App.js
import React, { useState } from 'react';
import InsertForm from "./components/InsertForm";
import ListView from "./components/ListView";
function App() {
const [todoList, setTodoList] = useState([]);
const handleInsert = (value) => {
setTodoList((current) => {
const newList = [...current];
newList.push({
key: new Date().getTime(),
value,
isCompleted: false,
});
return newList;
})
}
const handleComplete = (index) => {
setTodoList((current) => {
const newList = [...current];
newList[index].isCompleted = true;
return newList;
})
}
const handleRemove = (index) => {
setTodoList((current) => {
const newList = [...current];
newList.splice(index, 1);
return newList;
})
}
return (
<div className="App">
<ListView todoList={todoList} onComplete={handleComplete} onRemove={handleRemove} />
<InsertForm onInsert={handleInsert} />
</div>
);
}
export default App;
▶ CSS로 꾸미기
인라인 스타일 및 CSS 파일을 활용하여 스타일을 적용한다.
- ListView 컴포넌트는 별도의 CSS 파일을 import하여 style 적용하기
- InsertForm 컴포넌트는 인라인 스타일을 입력해서 style 적용하기
// ListView.js
import "./ListView.css";
...
// ListView.css
.listview {
display: block;
position: relative;
min-height: 120px;
margin-bottom: 8px;
background-color: #ffffff;
padding: 16px;
border-radius: 16px;
}
.listview ol {
margin: 0;
padding-left: 2em;
}
.listview ol li {
clear: both;
margin-bottom: 8px;
}
.listview ol li span{
float: left;
font-size: 16px;
}
.listview ol li.completed span {
text-decoration: line-through;
}
.listview ol li button {
float: right;
margin-left: 4px;
cursor: pointer;
border: none;
color: #ffffff;
background-color: #524FA1;
padding: 4px 8px;
}
.listview ol li button.remove {
background-color: #FA466A;
}
// InsertForm.js
import React, { useState, useCallback } from "react";
const InsertForm = ({ onInsert }) => {
const [inputValue, setInputValue] = useState("");
const handleSubmit = useCallback((event) => {
event.preventDefault();
if(typeof onInsert === "function" && inputValue) {
onInsert(inputValue);
}
setInputValue("");
},[onInsert, inputValue])
return (
<form onSubmit={handleSubmit} style={{
backgroundColor: "#ffffff",
borderRadius: 16,
marginBottom: 16,
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}>
<input value={inputValue} onChange={(event) => {
setInputValue(event.target.value);
}} style={{
flex: 1,
border: "none",
color: "#000000",
padding: "6px 12px"
}} />
<button type="submit" style={{
border: "none",
borderRadius: 16,
backgroundColor: "#3ab6bc",
cursor: "pointer",
padding: "8px 16px",
color: "#ffffff"
}}>등록</button>
</form>
)
}
export default InsertForm;
▶ 부가기능 구현
- [완료] 버튼 클릭 시 이펙트 추가
- 할일 개수 제한하기
1) 완료버튼 클릭 시 할일 목록에 취소선 애니메이션 추가
// ListView.css
...
.listview ol li span{
float: left;
font-size: 16px;
position: relative;
}
.listview ol li span::after {
content: "";
position: absolute;
left: 0;
top: 50%;
transition: width 0.2s ease-in-out;
width: 0%;
height: 3px;
background-color: #524FA1;
}
.listview ol li.completed span {
text-decoration: line-through;
}
.listview ol li.completed span::after {
width: 100%;
}
...
2) 할일 목록이 10개 이상일 시 메세지를 띄우고 input 입력을 비활성화
// App.js
function App() {
...
const isLimitReached = useMemo(() => { // isLimitReached 변수 선언
return todoList.length >=10;
}, [todoList]);
...
return (
<div className="App">
<ListView todoList={todoList} onComplete={handleComplete} onRemove={handleRemove} />
{isLimitReached {/* isLimitReached가 true일 때 경고메시지 띄우기 */}
&& <div style={{
padding: "8px 16px",
border: "1px solid #FA466A",
backgroundColor: "#feecf0",
color: "#FA466A",
marginBottom: 16
}}>
※ 할일 목록이 너무 많습니다.
</div>
}
<InsertForm onInsert={handleInsert} disabled={isLimitReached}/> {/* disabled이라는 props 넘겨주기 */}
</div>
);
}
// InsertForm.js
const InsertForm = ({ onInsert, disabled }) => { // props에 disabled 추가
...
return (
...
<input value={inputValue} onChange={(event) => {
setInputValue(event.target.value);
}} style={{
flex: 1,
border: "none",
color: "#000000",
padding: "6px 12px"
}} disabled={disabled}/> {/* input 태그에 disabled 속성 추가 */}
...
)
}
이 글은 엘리스의 AI트랙 5기 강의를 들으며 정리한 내용입니다.
'개발공부 > 🟦 React.js' 카테고리의 다른 글
[React] SPA와 라우팅 (0) | 2022.08.13 |
---|---|
[React] React 스타일링 (0) | 2022.08.12 |
[React] Hooks (0) | 2022.08.11 |
[React] 이벤트 처리 (0) | 2022.08.11 |
[React] JSX와 컴포넌트 (0) | 2022.08.10 |
프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그