GitHub - gilbutITbook/080203: 리액트를 다루는 기술 개정판
리액트를 다루는 기술 개정판. Contribute to gilbutITbook/080203 development by creating an account on GitHub.
github.com
(본 포스팅은 위의 리액트를 다루는 기술(개정판)을 기반으로 작성했습니다.)
'프로젝트 준비하기'
① Prettier 설정
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}
② index.css 수정
body {
margin: 0;
padding: 0;
background: #e9ecef;
}
③ App.js 컴포넌트 초기화
import React from 'react';
const App = () => {
return <div>Todo App을 만들자!</div>;
};
export default App;
'UI 구성하기'
TodoTemplate
- 화면을 가운데 정렬한다.
- 앱 타이틀을 보여준다.
- childred 내부 JSX를 props로 받아 와서 렌더링한다.
TodoInsert
- 새로운 항목을 입력하고 추가할 수 있는 컴포넌트
- state로 인풋의 상태를 관리한다.
TodoListItem
- 각 할 일 항목에 대한 정보를 보여주는 컴포넌트
- todos 객체를 props로 받아 와서 상태에 따라 다른 스타일의 UI를 보여준다.
TodoList
- todo 배열을 props로 받아 map으로 여러 개의 TodoListItem 컴포넌트로 변환해서 보여준다.
'TodoTemplate'
TodoTemplate.js
import React from 'react';
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;
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
border-radius: 4px;
overflow: hidden;
.app-title {
background: salmon;
color: white;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.content {
background: white;
}
}
'TodoInsert'
TodoInsert.js
import React from 'react';
import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';
const TodoInsert = () => {
return (
<form className="TodoInsert">
<input placeholder="할 일을 입력하세요" />
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
TodoInsert.scss
.TodoInsert {
display: flex;
background: rgb(179, 179, 179);
input {
background: none;
outline: none;
border: none;
padding: 0.5rem;
font-size: 1.125rem;
line-height: 1.5;
color: white;
&::placeholder {
color: rgb(224, 224, 224);
}
flex: 1;
}
button {
background: none;
outline: none;
border: none;
background: rgb(141, 141, 141);
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: rgb(247, 169, 160);
}
}
}
transition: 0.1s background ease-in;
background(property) : 애니메이션 시킬 속성
0.1s(duration) : 시작해서 끝날 때까지의 시간
ease-in(timingfunction) : 속도 변화
'TodoListItem'
TodoListItem.js
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">할 일</div>
</div>
<div className="remove">
<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: salmon;
}
.text {
color: #adb5bd;
text-decoration: line-through;
}
}
}
.remove {
cursor: pointer;
display: flex;
align-items: center;
font-size: 1.5rem;
color: rgb(141, 141, 141);
&:hover {
color: salmon;
}
}
& + & {
border-top: 1px solid #dee2e6;
}
}
'TodoList'
TodoList.js
import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = () => {
return (
<div className="TodoList">
<TodoListItem />
<TodoListItem />
<TodoListItem />
</div>
);
};
export default TodoList;
TodoList.js
.TodoList {
min-height: 320px;
max-height: 513px;
overflow-y: auto;
}
- overflow-y: auto; : 리스트 안은 스크롤 가능하게 한다.
'기능 구현하기'
① App에서 todos 상태 이용하기
App.js
import React, { useState }from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '리액트의 기초 알아보기',
checked: true,
},
{
id: 2,
text: '컴포넌트 스타일링해 보기',
checked: true,
},
{
id: 3,
text: '일정 관리 앱 만들어 보기',
checked: false,
},
]);
return(
<TodoTemplate>
<TodoInsert />
<TodoList todos={todos}/>
</TodoTemplate>
);
};
export default App;
TodoList.js
import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
const TodoList = ({ todos }) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} />
))}
</div>
);
};
export default TodoList;
- props으로 받아온 값을 TodoListItem으로 변환하여 렌더링하도록 설정해준다.
TodoListItem.js
import React from 'react';
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({ todo }) => {
const { text, checked } = todo;
return (
<div className="TodoListItem">
<div className={cn('checkbox', {checked})}>
{checked ? <MdCheckBox/> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove">
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
- 조건부 스타일링을 위해 classnames 사용
② 항목 추가 기능 구현하기
- TodoInsert 컴포넌트에서 input에 입력하는 값을 관리할 수 있도록 useState 사용해서 value 정의한다.
- input의 이벤트를 핸들링할 onChange 함수도 작성해야 한다. (컴포넌트가 리렌더링될 때마다 함수를 새로 만드는 것이 아니라, 한 번 함수를 만들고 재사용할 수 있도록 Hook 사용)
- Enter 감지를 위해 onClick 대신에 onSubmit을 써야 한다.
TodoInsert.js
import React, { 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('');
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;
- onInsert : id 값은 렌더링되는 정보가 아니기 때문에 useRef를 사용하여 관리한다.
- props로 전달해야 할 함수이므로 useCallback을 사용한다.
App.js
import React, { 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: 1,
text: '리액트의 기초 알아보기',
checked: true,
},
{
id: 2,
text: '컴포넌트 스타일링해 보기',
checked: true,
},
{
id: 3,
text: '일정 관리 앱 만들어 보기',
checked: false,
},
]);
const nextId = useRef(4);
const onInsert = useCallback(
text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
setTodos(todos.concat(todo));
nextId.current +=1;
},
[todos],
);
return(
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos}/>
</TodoTemplate>
);
};
export default App;
③ 지우기 기능 구현하기
- filter 함수로 todos를 걸러주고, App.js에 아래 onRemove 함수를 정의해 주고, TodoList, TodoListItem에 props로 전달한다.
- onRemove를 호출한 id를 가진 todo만 남기고 새로운 todos 배열로 바뀐다.
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id !== id));
},
[todos],
);
④ 체크 기능 구현하기
- filter 함수로 todos를 걸러주고, App.js에 아래 onToggle 함수를 정의해 주고, TodoList, TodoListItem에 props로 전달 전달한다.
- onToggle을 호출한 id를 가진 todo의 checked 값을 반전시키고, 나머지는 그대로 todo로 반환한다.
- 불변성을 유지하면서 특정 배열 원소를 업데이트해야 할 때 map을 사용하면 짧은 코드로 쉽게 작성할 수 있다.
const onToggle = useCallback(
id => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, checked: !todo.checked } : todo,
),
);
},
[todos],
);
'React' 카테고리의 다른 글
9# 컴포넌트 스타일링 (0) | 2023.02.22 |
---|---|
8# Hooks (0) | 2023.02.09 |
7# 컴포넌트의 라이프사이클 메서드 (0) | 2023.02.03 |
6# 컴포넌트 반복 (0) | 2023.01.27 |
5# ref: DOM에 이름 달기 (0) | 2023.01.13 |