여기저기를 뒤져보니 리액트를 기초적으로 배우고 난뒤에는 TODO LIST를 만들어 보는 것이 마치 국룰처럼 자리잡아 있었습니다. 그래서 저도 시작은 TODO 리스트로 결정했습니다.
클론 코딩 대상은 리액트로 유명한 VELOPERT 님의 TODO LIST를 가지고 따라가 보도록 하겠습니다. 기본적으로 큰 틀은 따라가되 일부분에서는 제 개인 성향이 들어간 수정이 있을 예정입니다.
0. 사전 준비
우선 yarn create react-app으로 프로젝트를 위한 공간을 만들어 줍니다. 저는 todo-list라는 이름으로 작업공간을 만들었습니다.
그리고 우리가 사용할 react-icons와 classnames, node-sass를 설치해줍니다. react-icons는 리액트에서 여러 아이콘 디자인들을 사용할 수 있도록 해주는 패키지, classnames는 조건부 스타일링의 편리하게 해주는 도구, node-sass는 css 전처리기로, css를 더욱 편리하고 쉽게 만들어주는 도구입니다.
yarn add node-sass
yarn add classnames
yarn add react-icons
생성된 프로젝트의 index.css의 내용을 모두 지우고 다음과 같이 작성합니다.
body {
margin: 0;
padding: 0;
background: #e9ecef;
}
App.js의 내용을 지워줍니다.
import React from 'react';
const App = () => {
return (
<div>
사전 준비 완료
</div>
);
};
export default App;
todo-list에는 4가지의 컴포넌트가 들어갑니다.
- TodoTemplate: 앱의 내용을 담을 틀입니다. 화면 가운데 정렬, 내용 보여주기 등을 담당합니다.
- TodoInsert: todo 항목을 입력하고 추가하는 컴포넌트입니다. state로 input을 관리합니다.
- TodoListItem: todo 항목들을 보여주는 컴포넌트입니다. 각 todo 상태에 따라 다른 외형을 렌더링합니다.
- TodoList: todo 항목 배열을 props로 받아서 가공해 각각 TodoListItem 컴포넌트로 변환하고 보여줍니다.
각 컴포넌트들을 외형적으로 우선 구현하고, 그 다음에 세부 기능을 구현하도록 하겠습니다.
1. TodoTemplate 컴포넌트
먼저 가장 바깥 틀이 될 TodoTemplate 컴포넌트를 만들어보겠습니다.
import React from 'react';
import './TodoTemplate.scss';
const TodoTemplate = ({children}) => {
return (
<div className={"TodoTemplate"}>
<div className={"app-title"}>TODO LIST</div>
<div className={"content"}>{children}</div>
</div>
);
};
export default TodoTemplate;
div태그와 className을 통해 앱의 큰 틀을 엿볼 수 있습니다. 큰 하나의 틀 안에 제목이 있고 그 아래 콘텐츠들이 올 것으로 예상할 수 있습니다.
children은 이 todo list의 기능들 입니다. TodoInsert와 List가 있겠죠. 이들을 props로 받아와서 렌더링을 하게 됩니다.
App.js에서 TodoTemplate를 렌더링 하도록 변경합니다.
import React from 'react';
import TodoTemplate from './components/TodoTemplate';
const App = () => {
return (
<TodoTemplate>
todo-list 만들기
</TodoTemplate>
);
};
export default App;
렌더링이 되는 것을 확인 했으니 scss를 수정해보겠습니다.
.TodoTemplate {
width: 512px;
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
border-radius: 4px;
overflow: hidden;
.app-title {
background: #eadf6a;
color: #000;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.content {
background: #fff;
}
}
.scss 입니다. css와는 다르게 속성 안에 다른 속성을 표시함으로써 자식 요소 관계를 명확히 볼 수 있다는 편리함이 있습니다.
rem이라는 단위가 존재하는데 rem은 폰트 상대 크기입니다. em도 예전에 css를 다루면서 폰트 상대 크기라고 했었는데요. 두 가지의 차이점은 em은 해당 속성이 속한 요소의 font-size에 따른 상대 크기를 지정하는 것이고, rem은 root-em으로 최상위 요소의 font-size를 기준으로 하는 것 입니다. 따라서 px보단 em을, em보다는 rem을 이용하는 것이 안정적이다 라고 할 수 있습니다.
이렇게 scss까지 적용시킨 후 결과를 보면 다음과 같습니다. 제가 모니터를 세로로 돌려쓰고 있어서 width 521px가 꽉찼기에 화면에 꽉찬 것 처럼 보입니다. 이것은 사용자의 화면마다 다르게 보입니다.
2. TodoInsert 컴포넌트
두 번째로 만들 컴포넌트는 todo를 입력하고 등록할 수 있는 TodoInsert 컴포넌트입니다. 용도에서 혹시 무엇을 써야할지 감이 오시나요? 예상하기에는 form의 input과 button이 올 것 같은데, 만들어보면서 확인해보도록 합시다.
import React from 'react';
import './TodoInsert.scss';
import {MdAdd} from 'react-icons/md';
const TodoInsert = () => {
return (
<form className={"TodoInsert"}>
<input placeholder={"todo 입력"}/>
<button type={"submit"}>
<MdAdd/>
</button>
</form>
);
};
export default TodoInsert;
예상했던 대로 input과 button을 이용했습니다. 그리고 react-icons도 이용했는데요. <MdAdd/>가 아이콘입니다. 아이콘을 사용하고 싶은 부분에 컴포넌트 형식으로만 삽입하면 되는 편리한 도구입니다.
App.js를 수정하고 결과를 확인해보겠습니다. 요소들이 올바르게 배치되고 버튼에 +아이콘이 나타났나요?
import React from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
const App = () => {
return (
<TodoTemplate>
<TodoInsert />
</TodoTemplate>
);
};
export default App;
마찬가지로 TodoInsert 컴포넌트도 스타일링해보겠습니다.
.TodoInsert {
display: flex;
background: #495057;
input {
background: none;
outline: none;
border: none;
padding: 0.5rem;
font-size: 1.125rem;
line-height: 1.5;
color: #000;
&::placeholder {
color: #bbb;
}
flex: 1;
button {
background: none;
outline: none;
border: none;
background: #868e96;
color: #fff;
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;
}
}
}
}
&::라는 것이 등장하는데 이것은 같은 레벨(들여쓰기가 같은)의 요소에 몇가지 부분이 다른 경우에 사용합니다. 위의 코드에서는 input 태그의 placeholder 속성에 다른 스타일을 지정하기 위해서 사용했습니다.
3. TodoListItem과 TodoList 컴포넌트
마지막 구성입니다. TodoListItem은 각 todo를 나타냅니다. 그리고 TodoList는 이들을 한데 묶어 놓은 컴포넌트입니다. 우선 TodoListItem부터 작성해보겠습니다.
import React from 'react';
import {MdCheckBoxOutlineBlank, MdCheckBox, MdRemoveCircleOutline} from 'react-icons/md';
import './TodoListItem.scss';
const TodoListItem = () => {
return (
<div className={"TodoListItem"}>
<div className={"checkbox"}>
<MdCheckBoxOutlineBlank/>
<div className={"text"}>todo</div>
</div>
<div className={"remove"}>
<MdRemoveCircleOutline/>
</div>
</div>
);
};
export default TodoListItem;
todo list의 item에는 체크박스와 할 일 텍스트, remove버튼이 있음을 알 수 있습니다. 그리고 체크박스와 버튼들은 react-icons를 이용해서 꾸며주었습니다.
자 그럼이제 todo-list-item이 들어갈 TodoList 컴포넌트를 작성해보겠습니다.
import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = () => {
return (
<div className={"TodoList"}>
<TodoListItem/>
<TodoListItem/>
</div>
);
};
export default TodoList;
TodoList에서 TodoListItem 컴포넌트를 보여줄 것이므로 여기에서 import를 했습니다.
그리고 App.js를 수정해줍니다.
import React from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
return (
<TodoTemplate>
<TodoInsert/>
<TodoList/>
</TodoTemplate>
);
};
export default App;
이렇게 Template에 우리가 예측하던 대로 제대로된 결과가 나왔습니다. 이제 이것을 좀 더 깔끔하게 스타일링해보겠습니다.
먼저 TodoList부터 스타일링합니다.
.TodoList {
min-height: 320px;
max-height: 513px;
overflow-y: auto;
}
다음은 TodoListItem 컴포넌트를 스타일링합니다. 코드가 길어서 포함관계가 헷갈리실수도 있는데, 그럴땐 컴포넌트 배치를 생각하면 헷갈리지 않을 수 있습니다.
.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;
}
}
코드가 좀 길어서 설명할 부분을 따로 나누어가며 설명드리겠습니다.
&:nth-child(even) {
background: #f8f9fa;
}
nth-child()는 css 가상 클래스의 일종으로 형재 요소 사이에서 ()에 지정된 순서에 따라 요소를 적용합니다. 즉 형제 관계인 todo들 사이에서 ()사이에 준 값에 다른 스타일을 적용하겠다는 것 입니다. 여기에서는 even, 짝수번 요소마다 background 속성 색을 다르게 스타일링 했습니다. 위의 결과 사진을 보면 첫 번째와 두 번째 todo 배경색이 조금 다른 것이 보이시나요?
svg {
font-size: 1.5rem;
}
svg는 react-icons를 지칭하는 태그입니다.
&.checked {
svg {
color: #22b8cf;
}
.text {
color: #adb5bd;
text-decoration: line-through;
}
}
&.checked는 현재 요소가 체크 되었을 때의 스타일 속성을 지정합니다. text-decoration: line-through는 체크되었을 경우에는 todo를 수행한 상태이므로 text에 취소선을 그어줍니다. 이거요.
& + & {
border-top: 1px solid #dee2e6;
}
이 css 구문은 todo 요소 사이에 테두리를 그어줍니다.
이렇게 컴포넌트를 세우고 기초 공사를 했습니다. 하지만 실제로 동작은 하지 않는데요. 다음 포스트에서는 이어서 핵심이라고 할 수 있는 동작을 구현해보겠습니다.
참조
'Project > [클론 코딩] TODO LIST' 카테고리의 다른 글
[클론 코딩] Todo List 컴포넌트 성능 최적화 (0) | 2021.12.23 |
---|---|
[클론 코딩] TODO LIST 기능 구현 (0) | 2021.12.21 |
댓글