1. 프로젝트 생성 및 필요한 라이브러리 설치
yarn create react-app todo-app
cd todo-app
yarn add styled-components react-icons
2. Prettier 설정
// 프로젝트의 최상위 디렉토리에 .prettierrc 파일 생성
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}
3. index.css, App.js 초기화
/*index.css 초기화*/
body {
margin: 0;
padding: 0;
background-color: rgba(128,128,128,0.3);
}
//App.js 초기화
import React from 'react';
import './App.css';
function App() {
return (
<>
<div>todo start !</div>
</>
);
}
export default App;
4. UI 구성 - 컴포넌트의 종류와 기능
- TodoTemplate : 화면을 가운데로 정렬, 앱 타이틀 출력, children으로 내부 jsx를 props로 받아서 렌더링
- TodoInsert : 새로운 항목 입력 및 추가, state를 통한 인풋의 상태관리
- TodoListItem : list item 정보 출력, todo 객체를 props로 받아서 상태에 따른 (done, delete 등) 다른 스타일의 ui 적용
- TodoList : todos 배열을 props로 받아 map을 이용해 여러개의 TodoListItem 컴포넌트로 변환
5. TodoTemplate UI 구현
// todo-app/src/components/StyledTodoTemplate.js
import React from 'react';
import styled from 'styled-components';
const TodoTemplate = styled.div`
width: 512px;
margin: 6rem auto auto auto;
border-radius: 4px;
overflow: hidden;
.app-title {
background: blueviolet;
color: white;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.content {
background: white;
}
`;
const StyledTodoTemplate = ({ children }) => {
return (
<TodoTemplate>
<div className="app-title">TODO LIST</div>
<div className="content">{children}</div>
</TodoTemplate>
);
};
export default StyledTodoTemplate;
6. TodoInsert UI 구현
// todo-app/src/components/StyledTodoInsert.js
import React from 'react';
import styled from 'styled-components';
import { MdAdd } from 'react-icons/md';
const Form = styled.form`
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: yellow;
}
flex: 1;
}
button {
/*기본 스타일 초기화*/
background: none;
outline: none;
border: none;
background: rgba(128, 128, 128, 0.2);
color: white;
padding: auto 1rem;
font-size: 1.5rem;
display: flex;
align-items: center;
cursor: pointer;
transition: 0.1s background ease-in;
&:hover {
background: rgba(128, 128, 128, 0.8);
}
}
`;
const StyledTodoInsert = () => {
return (
<Form>
<input placeholder="insert job" />
<button type="submit">
<MdAdd />
</button>
</Form>
);
};
export default StyledTodoInsert;
7. TodoListItem UI 구현
// todo-app/src/components/StyledTodoListItem.js
import React from 'react';
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from 'react-icons/md';
import styled from 'styled-components';
const TodoListItem = styled.div`
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;
}
&.checkd {
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;
}
`;
const StyeldTodoListItem = () => {
return (
<TodoListItem>
<div className="checkbox">
<MdCheckBoxOutlineBlank />
<div className="text">job</div>
</div>
<div className="remove">
<MdRemoveCircleOutline />
</div>
</TodoListItem>
);
};
export default StyeldTodoListItem;
8. TodoList UI 구현
// todo-app/src/components/StyledTodoList.js
import React from 'react';
import styled from 'styled-components';
import StyledTodoListItem from './StyledTodoListItem';
const TodoList = styled.div`
min-height: 320px;
max-height: 513px;
overflow-y: auto;
`;
const StyledTodoLIst = () => {
return (
<TodoList>
<StyledTodoListItem />
<StyledTodoListItem />
<StyledTodoListItem />
</TodoList>
);
};
export default StyledTodoLIst;
UI가 구현된 컴포넌트들을 App.js 에서 렌더링
// todo-app/src/App.js
import React from 'react';
import './App.css';
import StyledTodoTemplate from './components/StyledTodoTemplate';
import StyledTodoInsert from './components/StyledTodoInsert';
import StyledTodoList from './components/StyledTodoList';
function App() {
return (
<StyledTodoTemplate>
<StyledTodoInsert />
<StyledTodoList />
</StyledTodoTemplate>
);
}
export default App;
9. 기능구현1 - App에서 todos 상태 사용
// src/App.js
import React, { useState } from 'react';
import './App.css';
import StyledTodoTemplate from './components/StyledTodoTemplate';
import StyledTodoInsert from './components/StyledTodoInsert';
import StyledTodoList from './components/StyledTodoList';
function App() {
const [todos, setTodos] = useState([
{
id: 1,
text: 'job1',
check: true,
},
{
id: 2,
text: 'job 22',
check: false,
},
{
id: 3,
text: 'job 333',
check: true,
},
]);
return (
<StyledTodoTemplate>
<StyledTodoInsert />
<StyledTodoList todos={todos} />
</StyledTodoTemplate>
);
}
export default App;
// src/components/StyledTodoList.js
import React from 'react';
import styled from 'styled-components';
import StyledTodoListItem from './StyledTodoListItem';
const TodoList = styled.div`
min-height: 320px;
max-height: 513px;
overflow-y: auto;
`;
const StyledTodoLIst = ({ todos }) => {
return (
<TodoList>
{todos.map(todo => (
<StyledTodoListItem todo={todo} key={todo.id} />
))}
</TodoList>
);
};
export default StyledTodoLIst;
// src/components/StyledTodoListItem.js
import React from 'react';
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from 'react-icons/md';
import styled, { css } from 'styled-components';
const TodoListItem = styled.div`
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;
${props =>
props.checked &&
css`
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;
}
`;
const StyeldTodoListItem = ({ todo }) => {
const { text, check } = todo;
return (
<TodoListItem checked={check}>
<div className="checkbox">
{check ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove">
<MdRemoveCircleOutline />
</div>
</TodoListItem>
);
};
export default StyeldTodoListItem;
10. 기능구현2 - 항목 추가 기능
// todo-app/src/App.js
import React, { useState, useRef, useCallback } from 'react';
import './App.css';
import StyledTodoTemplate from './components/StyledTodoTemplate';
import StyledTodoInsert from './components/StyledTodoInsert';
import StyledTodoList from './components/StyledTodoList';
function App() {
const [todos, setTodos] = useState([
{
id: 1,
text: 'job1',
check: true,
},
{
id: 2,
text: 'job 22',
check: false,
},
{
id: 3,
text: 'job 333',
check: true,
},
]);
const nextId = useRef(4);
const onInsert = useCallback(
text => {
const todo = {
id: nextId.current,
text,
check: false,
};
setTodos(todos.concat(todo));
nextId.current += 1;
},
[todos],
);
return (
<StyledTodoTemplate>
<StyledTodoInsert onInsert={onInsert} />
<StyledTodoList todos={todos} />
</StyledTodoTemplate>
);
}
export default App;
//todo-app/src/components/StyledTodoInsert.js
import React, { useState, useCallback } from 'react';
import styled from 'styled-components';
import { MdAdd } from 'react-icons/md';
const Form = styled.form`
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: yellow;
}
flex: 1;
}
button {
/*기본 스타일 초기화*/
background: none;
outline: none;
border: none;
background: rgba(128, 128, 128, 0.2);
color: white;
padding: auto 1rem;
font-size: 1.5rem;
display: flex;
align-items: center;
cursor: pointer;
transition: 0.1s background ease-in;
&:hover {
background: rgba(128, 128, 128, 0.8);
}
}
`;
const StyledTodoInsert = ({ onInsert }) => {
const [value, setValue] = useState('');
// useCallback을 이용하여 컴포넌트가 리렌더링 될때마다 함수를 새로만드는것이 아니라 재사용
const onChange = useCallback(e => {
setValue(e.target.value);
}, []);
const onSubmit = useCallback(
e => {
onInsert(value);
setValue('');
e.preventDefault(); // submit 이벤트의 브라우저 새로고침 불허
},
[onInsert, value],
);
return (
<Form onSubmit={onSubmit}>
<input placeholder="insert job" value={value} onChange={onChange} />
<button type="submit">
<MdAdd />
</button>
</Form>
);
};
export default StyledTodoInsert;
11. 기능구현3 - 삭제 기능
// todo-app/src/App.js
//...
function App() {
//...
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id !== id)); //filter 함수 이용
},
[todos],
);
return (
<StyledTodoTemplate>
<StyledTodoInsert onInsert={onInsert} />
<StyledTodoList todos={todos} onRemove={onRemove} />
</StyledTodoTemplate>
);
};
export default App;
//todo-app/src/components/StyledTodoList.js
import React from 'react';
import styled from 'styled-components';
import StyledTodoListItem from './StyledTodoListItem';
const TodoList = styled.div`
min-height: 320px;
max-height: 513px;
overflow-y: auto;
`;
const StyledTodoLIst = ({ todos, onRemove }) => {
return (
<TodoList>
{todos.map(todo => (
<StyledTodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
))}
</TodoList>
);
};
export default StyledTodoLIst;
//todo-app/src/components/StyledTodoListItem.js
//...
const StyeldTodoListItem = ({ todo, onRemove }) => {
const { id, text, check } = todo;
return (
<TodoListItem checked={check}>
<div className="checkbox">
{check ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</TodoListItem>
);
};
export default StyeldTodoListItem;
12. 기능구현4 - 수정기능
// todo-app/src/App.js
//...
function App() {
//...
const onToggle = useCallback(
id => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, check: !todo.check } : todo,
),
);
},
[todos],
);
return (
<StyledTodoTemplate>
<StyledTodoInsert onInsert={onInsert} />
<StyledTodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
</StyledTodoTemplate>
);
}
export default App;
// todo-app/src/components/StyeldTodoList.js
//...
const StyledTodoLIst = ({ todos, onRemove, onToggle }) => {
return (
<TodoList>
{todos.map(todo => (
<StyledTodoListItem
todo={todo}
key={todo.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</TodoList>
);
};
export default StyledTodoLIst;
// todo-app/src/components/StyledTodoListItem.js
//...
const StyeldTodoListItem = ({ todo, onRemove, onToggle }) => {
const { id, text, check } = todo;
return (
<TodoListItem check={check}>
<div className="checkbox" onClick={() => onToggle(id)}>
{check ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</TodoListItem>
);
};
export default StyeldTodoListItem;
'Frontend' 카테고리의 다른 글
10. SPA 와 Routing (0) | 2020.10.25 |
---|---|
9. immer를 사용한 불변성(Immutability) 유지 (0) | 2020.10.25 |
7. Component Styling (0) | 2020.10.25 |
6. React Hooks (0) | 2020.10.25 |
5. React Component Lifecycle (0) | 2020.10.25 |