리액트를 어떻게 공부하면 좋을까 해서 시작한 TO DO LIST..🤔
대략 이런 형태의 리스트를 만들어 보았다.
이 순서로 진행했고, 처음엔 클래스형으로 작성했었는데 대세를 좇기위해 함수형으로 바꿨다.
가장 먼저 UI. 디자인을..내가 했어야했다는 점만 제외하면 어렵지 않게 작성할 수 있었다. 디자인은 너무 어렵다..💦
우선...
React18 버전에서는 ReactDOM.render()가 사용되지 않는다.
Warning: ReactDOM.render is no longer supported in React 18. 에러가 나므로 index.js 파일을 수정해줬다.
import React from 'react';
import * as ReactDOM from 'react-dom/client';
import './App.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
reportWebVitals();
App.js의 경우 기능이 직접적으로(?) 적용되지 않을 구조를 위주로 작성했고,
기능이 추가될 todoList 영역을 따로 만들어 import 한다.
import {useState} from 'react';
import TodoList from './component/todoList';
function TodoHead(){
return (
<div className={'headingArea'}>
<h1 className={'heading__h1'}>TodoList</h1>
<p className={'desctext'}>Enter your to-do and Complete!!</p>
</div>
)
}
function TodoContent() {
return (
<div className={'listArea'}>
<div className={'listBox'}>
<h2 className={'heading__note'}>NOTE.</h2>
<TodoList></TodoList>
</div>
</div>
)
}
function App() {
return (
<div className={'todolistWrap'}>
<TodoHead></TodoHead>
<TodoContent></TodoContent>
</div>
)
}
export default App;
todoList.js도 처음에는 아무런 기능 없이 구조만 작성했다.
텍스트 입력 영역을 따로 관리하고 싶어서 todoCreate를 따로 만들어 todoList에서 import했다.
그리고 이후에... 문법에 익숙치 않아서 연결할때 헤멤
import {useState} from 'react';
import TodoCreate from './todoCreate';
function TodoList() {
return (
<div className={'todoListBox'}>
<ul className={'todoList'}>
<li className={'todoItem'}>
<label>
<input type="checkbox"/>
<span className={'checkIcon'}></span>
<span className={'labelText'}>Sample List item</span>
</label>
<button type={'button'} className={'btnDel'}>삭제</button>
</li>
</ul>
<TodoCreate />
</div>
)
}
export default TodoList;
function TodoCreate() {
return(
<form>
<div className={'createArea'}>
<input type='text' name='list' className={'inputText'} placeholder={'Entering to-do list~!'}></input>
<button className={'btnEnter'}><span className={'arrowIcon'}></span>Enter</button>
</div>
</form>
)
}
export default TodoCreate;
/* App.css */
body {margin: 0;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}code {font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;}* {margin: 0;padding: 0;box-sizing:border-box;}ol,ul,li {list-style: none;}.todolistWrap {display: -webkit-flex;display: -ms-flex;display: flex;flex-direction: column;justify-content: center;align-items: center;height: 100vh;background: #eee;}.heading__h1 {position: relative;font-size: 42px;color: #333;}.headingArea {margin-bottom: 17px;width: 430px;}.headingArea .heading__h1 {display: inline-block;min-width: 125px;transform: rotate(-17deg);}.headingArea .heading__h1:before {content: "TodoList";position: absolute;top: -0.15rem;left: 0.15rem;color: transparent;-webkit-text-stroke: 0.1rem #333;}.headingArea .desctext {text-align: center;font-size: 13px;color: #c1c1c1;}.listArea {position: relative;width: 350px;height: 500px;}.listArea:before {content: '';position: absolute;top: 10px;right: -10px;box-sizing: border-box;width: 100%;height: 100%;background: #4fcda4;border-radius: 10px;z-index: 0;}.countBox {display: flex;justify-content: flex-end;margin-bottom: 12px;}.btnCount {display: inline-block;vertical-align: top;position: relative;margin-left: 4px;width: 20px;height: 20px;border: 0;background: #d2ddff;border-radius: 3px;font-size: 9px;color: transparent;overflow: hidden;}.btnCount::before {content: '';position: absolute;top: 50%;left: 50%;border-bottom: 10px solid royalblue;border-left: 6px solid transparent;border-right: 6px solid transparent;transform: translate(-50%, -50%);}.btnCount--down::before {transform: translate(-50%, -50%) rotate(180deg);}.listBox {display: flex;flex-direction: column;position: relative;padding: 20px;height: 100%;border: 2px solid #555;background: #fff;border-radius: 10px;z-index: 1;}.heading__note {margin-bottom: 10px;}.checkIcon {position: relative;display: inline-block;vertical-align: top;width: 14px;height: 14px;border: 1px solid #333;background: #fff;border-radius: 3px;}input:checked ~ .checkIcon {background: #333;}input:checked ~ .checkIcon:before {content: '';position: absolute;top: 50%;left: 50%;margin-top: -4px;margin-left: -4px;width: 6px;height: 3px;border-left: 2px solid #fff;border-bottom: 2px solid #fff;transform: rotate(-45deg);}.todoCount {margin-bottom: 5px;text-align: right;font-size: 12px;}.todoListBox {display: flex;flex-direction: column;flex: 1;min-height: 0;}.todoList {flex: 1;margin-right: -20px;padding-right: 20px;overflow-y: auto;}.todoItem {display: -webkit-flex;display: -ms-flex;display: flex;justify-content: space-between;align-items: center;border-bottom: 1px solid #333;}.todoItem label {display: -webkit-flex;display: -ms-flex;display: flex;align-items: center;min-width: 0;height: 30px;}.todoItem input[type='checkbox'] {position: absolute;top: 0;left: 0;opacity: 0;}.todoItem .checkIcon {flex-shrink: 0;margin-right: 7px;}.todoItem .labelText {overflow: hidden;white-space: nowrap;text-overflow: ellipsis;}.todoItem .btnDel {flex-shrink: 0;position: relative;width: 20px;height: 20px;border: 0;background: none;font-size: 0;color: transparent;}.todoItem .btnDel:before {content: '';position: absolute;top: 50%;left: 50%;margin-left: -6px;width: 12px;height: 1px;background: #333;transform: rotate(45deg);}.todoItem .btnDel:after {content: '';position: absolute;top: 50%;left: 50%;margin-left: -6px;width: 12px;height: 1px;background: #333;transform: rotate(-45deg);}.createArea {position: relative;margin-top: 20px;}.inputText {display: inline-block;padding: 0 37px 0 13px;width: 100%;height: 35px;border: 1px dashed #333;background: none;border-radius: 20px;}.btnEnter {position: absolute;top: 50%;right: 5px;margin-top: -13px;width: 26px;height: 26px;border: 0;background: #333;border-radius: 50%;text-align: center;font-size: 0;color: transparent;overflow: hidden;}.btnEnter:hover .arrowIcon {transform: translateX(20px);animation: moveRight .9s ease infinite;animation-delay: .5s;}.btnEnter .arrowIcon {position: relative;display: inline-block;vertical-align: top;margin-top: -1px;width: 10px;height: 1px;background-color: #fff;transition: all ease .5s;}.btnEnter .arrowIcon:before {content: '';position: absolute;top: -3px;right: 0;width: 6px;height: 6px;border-top: solid 1px #fff;border-right: solid 1px #fff;-webkit-transform: rotate(45deg);transform: rotate(45deg);}@keyframes moveRight {0% {transform: translateX(-20px);}100% {transform: translateX(20px);}}
{listName}
으로 지정한다.onChange
이벤트를 걸어 값이 변경될때마다 실행될 함수 setListName
를 지정한다.event.target.value
로 작성한다.useState
를 사용한다.setListName
을 사용하고,('')
로한다.onSubmit
속성으로 폼이 제출될 때 실행할 함수 handleSubmit
을 지정한다.listName
을 기존 리스트인 list
배열에 추가하기 위해 전개연산자 ...
을 사용해서 새 배열을 만든다. setList
함수를 사용해list(기존 list + 추가한 리스트 listName)를 새 배열로 업데이트한다.('')
로 설정해서 필드가 비워지도록 한다.const [list, setList] = useState([]);
const [listName, setListName] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
if (listName.trim() !== '') {
setList([...list, listName]);
setListName('');
}
};
// todoCreate.js
<form onSubmit={handleSubmit}>
<div className={'createArea'}>
<input type='text' name='list' className={'inputText'} placeholder={'Entering to-do list~!'} value={listName} onChange={(event) =>
setListName(event.target.value)}></input>
<button className={'btnEnter'}><span className={'arrowIcon'}></span>Enter</button>
</div>
</form>
list
배열을 순회하면서 각 리스트 아이템을 li로 반환한다.const [list, setList] = useState([])
의 list를 참조한다. useState([])
는 초기값으로 빈 배열을 설정한 것이고, 즉 list는 이곳에서 만들어진(?) 배열을 참조하는 것이다.item
은 현재 순회중인 요소이고, 두 번째 매개변수 index
는 현재 요소의 인덱스를 의미한다.key
값으로 index를 받는다.// todoList.js
<div className={'todoListBox'}>
<ul className={'todoList'}>
{list.map((item, index) => (
<li key={index} className={'todoItem'}>
<label>
<input type="checkbox"/>
<span className={'checkIcon'}></span>
<span className={'labelText'}>{item}</span>
</label>
<button type={'button'} className={'btnDel'}>삭제</button>
</li>
))}
</ul>
<TodoCreate />
</div>
handleDelete
를 호출시킨다.handleDelete
는 index
를 매개변수로 받는다.splice
메서드가 원본 배열을 직접 수정하기 때문에)splice
메서드의 첫 번째 매개변수로 수정할 요소의 index 값을 받고, 두 번째 매개변수로 삭제할 요소의 개수를 지정한다. 즉 newList배열에서 해당되는 index 위치의 아이템을 1개 삭제한다.list
를 업데이트 한다. 이때 컴포넌트가 재렌더링 되면서 삭제된 아이템이 반영된다.const handleDelete = (index) => {
const newList = [...list];
newList.splice(index, 1);
setList(newList);
};
// todoList.js
<div className={'todoListBox'}>
<ul className={'todoList'}>
{list.map((item, index) => (
<li key={index} className={'todoItem'}>
<label>
<input type="checkbox"/>
<span className={'checkIcon'}></span>
<span className={'labelText'}>{item}</span>
</label>
<button type={'button'} className={'btnDel'} onClick={() => handleDelete(index)}>삭제</button>
</li>
))}
</ul>
<TodoCreate />
</div>
todoList.js와 todoCreate.js를 연결한다. 사실 처음에 연결이 생각처럼 잘 안돼서 한 파일에 작성했다가 나중에 고쳤다.
<TodoCreate />
또한 <TodoCreate listName={listName} setListName={setListName} handleSubmit={handleSubmit} />
로 정의한다.// todoCreate.js
function TodoCreate({listName, setListName, handleSubmit}) {...}
// todoList.js
function TodoList() {
const [listName, setListName] = useState('');
const [list, setList] = useState([]);
const handleSubmit = (event) => {
event.preventDefault();
if (listName.trim() !== '') {
setList([...list, listName]);
setListName('');
}
};
const handleDelete = (index) => {
const newList = [...list];
newList.splice(index, 1);
setList(newList);
};
return (
...
<TodoCreate listName={listName} setListName={setListName} handleSubmit={handleSubmit} />
)
}
🐱: 분명 초간단인데... 초간단하게 하진 않았다.. 찰떡같은 설명을 할 수 있는 그날까지 가보자고
항상 만들다 실패하는 TodoList..
상세한 설명 감사합니다. 저도 다시 도전해봐야겠어요!