프로젝트 의도 : TodoList를 작성하고 목록에 추가, 제거할 수 있는 웹 구성
💜프로젝트 준비 :
1) 기본적인 프로젝트 생성 ( creat react-app )
2) 사용된 라이브러리, 전처리기 - Sass, classnames, react-icon
3) .prettierrc 생성
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComa": "all",
"printWidth": 80
}
4) index.css 설정
body {
margin: 0;
padding: 0;
background: #e9ecef;
}
5) App 컴포넌트 초기화
💜프로젝트 중요 구성 :
1) App.js
App컴포넌트에서 1개의 useState, 3개의 useCallback을 사용한다.state는 todos라는 값을 배열 형태로 사용자가 입력한 목록을 관리한다.useCallback은 todo를 입력하는 onInsert, todo를 제거하는 onRemove,checked 여부를 확인하여 css class를 변경하는 논리식을 적용하는 onToggle 이렇게 3가지가 있다.
App.js⬇⬇⬇⬇
import { useState, useRef, useCallback } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
const [todos, setTodos] = useState([]);
//고윳값으로 사용될 id
//ref를 사용하여 변수 담기
const nextId = useRef(1);
const onInsert = useCallback(
text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
setTodos(todos.concat(todo));
nextId.current += 1; // nextId 1씩 더하기
},
[todos],
);
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id !== id));
},
[todos],
);
const onToggle = useCallback(
id => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, checked: !todo.checked } : todo,
), // 불병성을 유지하면서 특정 배열 원소를 업데이트 할때 map을 사용해 쉽게 작성이 가능하다.
// todo.id와 파라미터 id 값이 같을 때 규칙대로 새로운 객체를 생성하지만 다를경우 받은 상태 그대로 반환한다.
// 그래서 map을 사용해 만든 배열에서 변화가 필요한 원소만 업데이트 한 후 나머지는 그대로 남는다.
);
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
</TodoTemplate>
);
};
export default App;
2) TodoTemplate.js(.scss)
실제 App.js에서 반환하는 태그들 중 최상위 태그이며 children props를 받아 랜더링 한다.
TodoTemplate.js⬇⬇⬇⬇
import './TodoTemplate.scss';
const TodoTemplate = ({ children }) => {
return (
<div className="TodoTemplate">
<div className="app-title">일정관리</div>
<div className="content">{children}</div>
</div>
);
};
export default TodoTemplate;
TodoTemplate.scss
.TodoTemplate{
width: 512px;
// width가 주어진 상태에서 좌우 중앙 정렬
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
border-radius: 4px;
overflow: hidden;
.app-title{
background: #22b8cf;
color:white;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.content{
background: white;
}
}
3) TodoListItem.js(.scss)
TodoList에 들어가는 목록을 구성하는 코드이며 react-icons 라이브러리를 이용해 아이콘을 불러온다.그리고 classnames 라이브러리를 이용해 props로 전달 받은 todo 배열, onRemove, onToggle을 받고,todo 배열을 해체할당하여 객체 선언해주었다.
상태에 따라 scss 효과를 classnames를 통해 조건부로 설정한다.
또한 checkBox에 onClick 동작에서 onToggle(id)를 적용해서 해당 목록의 css를 변경한다.마지막으로 RemoverCirecle의 onClick 동작에 onRemove(id)를 적용시켜 목록을 제거할 수 있도록 한다.
TodoListItem.js⬇⬇⬇⬇
import {
MdOutlineCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from "react-icons/md";
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({todo, onRemove, onToggle}) => {
const {id, text, checked} = todo;
return (
<div className="TodoListItem">
<div className={cn('checkbox', { checked })} onClick={() => { onToggle(id) }}>
{checked ? <MdCheckBox /> : <MdOutlineCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={()=>onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
TodoListItem.scss⬇⬇⬇⬇
.TodoListItem{
padding: 1rem;
display: flex;
align-items: center;
&:nth-child(even){
background: #f8f9fa;
}
.checkbox{
cursor: pointer;
flex: 1;
display: flex;
align-items: center;
svg{
//아이콘
font-size: 1.5rem;
}
.text{
margin-left: 0.5rem;
flex: 1; //@차지할 수 있는 영역 모두 차지
}
&.checked{
svg{
color: #22b8cf;
}
.text{
color: #adb5bd;
text-decoration: line-through;
}
}
}
.remove{
display: flex;
align-items: center;
font-size: 1.5rem;
color: #ff6b6b;
cursor: pointer;
&:hover{
color: #ff8787;
}
}
// 엘리먼트 사이사이에 테두리를 넣어 줌
& + &{
border-top: 1px solid #dee2e6;
}
}
4) TodoList.js(.scss)
TodoListItem을 받아와서 랜더링 해주나 App.js에서 사용될 때 props로 todos, onRemove, onToggle을
받아 자식 컴포넌트인 TodoListItem에서 사용할 수 있게 해준다.
또한 todos를 전달할 때 고유 key props를 전달해야 하기에 map을 이용해 컴포넌트로 변환해야 한다.
이때의 key값은 todo.id로 했으며 todo, key, onRemove, onToggle 을 map으로 배열화 하여 전달한다.
TodoList.js⬇⬇⬇⬇
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({todos, onRemove, onToggle}) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem
todo={todo}
key={todo.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
};
export default TodoList;
TodoList.scss⬇⬇⬇⬇
.TodoList{
min-height: 320px;
max-height: 513px;
overflow-y: auto;
}
5) TodoInsert.js(.scss)
이 컴포넌트에서 TodoTemplate에서 할 일을 입력하는 input과 입력한 값을 list에 추가하는 button이 있다.
여기에서는 onInsert를 App.js에서 받아오며, 이는 todo 객체의 형식을 지정하고 setTodos를 통해
todos에 추가하며 nextId를 1씩 추가해준다. 기본값은 [todos]로 한다.
TodoInsert에서 input에 입력되는 value를 관리하는 useState 1개, 입력된 input의 value를 setValue를 이용해
state의 value를 관리하고, 관리된 value를 onInsert(value)로 해주고( { id: nextId.current, text, checked: false } )
todos로 전달하고 value값을 초기화 한다. 그리고 submit하면 웹이 리랜더링 되는 것을 방지하기 위해
e.preventDefault() 함수를 호출해서 리랜더링을 방지한다. 기본값으로 [todos, value]로 설정하며 동작을 마친다.
TodoInsert.js⬇⬇⬇⬇
import { useState, useCallback } from 'react';
import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';
const TodoInsert = ({onInsert}) => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
}, []);
const onSubmit = useCallback(
e => {
onInsert(value);
setValue(''); // value 값 초기화
// submit 이벤트는 브라우저에서 새로고침을 발생시킨다
// 이를 방치하기 위해 이 함수를 호출
e.preventDefault();
},
[onInsert, value],
);
return (
<form className="TodoInsert" onSubmit={onSubmit}>
<input
placeholder="힐 일을 입력하세요"
value={value}
onChange={onChange}
/>
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
TodoInsert.scss⬇⬇⬇⬇
.TodoInsert{
display: flex;
background: #495057;
input{
//기본 스타일 초기화
background: none;
outline: none;
border: none;
padding: 0.5rem;
font-size: 1.125rem;
line-height: 1.5;
color: white;
&::placeholder{
color: #dee2e6;
}
//버튼을 제외한 영역을 모두 차지하기
flex: 1;
}
button{
//기본 스타일 초기화
background: none;
outline: none;
border: none;
background: #868e96;
color: white;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.5rem;
display: flex;
align-items: center;
cursor: pointer;
transition: 0.1s background ease-in;
&:hover{
background: #adb5bd;
}
}
}
처음 React 프로젝트를 진행했다....
확실히 javascript, css, html을 사용해 프로젝트를 진행하는 것 보다는 훨씬 편했다.
라이브러리의 중요성을 알았고 Sass의 좋은 점을 알았다(css보다 간단하게 작성이 가능했다)
'React' 카테고리의 다른 글
React) 12장 immer 라이브러리 (0) | 2022.05.06 |
---|---|
React) 11장 컴포넌트 최적화 (0) | 2022.04.19 |
React) 9장 컴포넌트 스타일링 (0) | 2022.04.16 |
React) 8장 Hooks (0) | 2022.04.12 |
React) 6장 컴포넌트 반복 (0) | 2022.04.12 |