<리액트를 다루는 기술>
React - 6장 : 컴포넌트 반복
React - 7장 : 컴포넌트의 LifeCycle
React - 8장 : React Hooks 총정리
React - 10장 : 빠르게 TODO앱 실습(현재)
누구나 만들어보는 투두앱을 빠르게 만들어서 컴포넌트최적화와 Hooks를 적용해보자.
10-1. 프로젝트 생성
- npx create-react-app todo-app
10-2. UI 구성
- TodoTemplate : 화면을 가운데에 정렬시켜 주며, 앱 타이틀을 보여준다. children으로 내부 JSX를 props로 받아 와서 렌더링해준다.
- TodoInsert : 새로운 항목을 입력하고 추가할 수 있는 컴포넌트. state를 통해 인풋의 상태를 관리한다.
- TodoListItem : 각 할 일 항목에 대한 정보를 보여주는 컴포넌트. todo 객체를 props로 받아 와서 상태에 따라 다른 스타일의 UI를 보여준다.
- TodoList : todos 배열을 props로 받아 온 후, 이를 배열 내장 함수 map을 사용해서 여러 개의 TodoListItem 컴포넌트로 변환하여 보여준다.
10-2.1 TodoTemplate
TodoTemplate.jsx

- 다음으로 이 컴포넌트를 App.js에서 불러와 렌더링한다.
App.js
- sass가 없을 경우 npm i sass 명령어로 설치해준다.
TodoTemplate.scss
- scss인데 네스팅 안써요? : 네스팅하면 스타일링 수정할 때 브라우저에서 ctrl+c -> v 해서 찾는게 느립니다.
- 왜 엔터쳐서 안함? : 개취입니다. 한줄로 쓰는게 구조파악에 1000억배는 유리합니다
참고로 누가 엔터쳐서 작성한 css 한줄로 만드는 one-line-formatter라는 라이브러리 있습니다 컨트롤j만 누르면 알아서 저렇게 해줌
다시 엔터치는 방식으로 돌리려면 뷰티파이같은거 쓰세요
- css파일을 각각 만드나요 ? : 파일을 엄청나게 많이 나누지는 않습니다. 구분단위는 회사 퍼블리셔 마음임.
10-2.2 TodoTemplate
TodoInsert.jsx
- npm i react/icons 로 아이콘을 설치해주자.
TodoInsert.scss
10-2.3 TodoListItem과 TodoList 만들기
TodoListItem.jsx
TodoList.jsx
- 현재는 props 전달 없이 그대로 여러번 보여주나, 기능을 추가할 예정이다.
- 이를 App.js 에 렌더링해준다.
App.js
TodoList.scss
TodoListItem.scss
- 현재까지 스타일링 된 상태
10-3. 기능 구현하기
10-3.1 App에서 todos 상태 사용하기
- 나중에 추가할 일정 항목에 대한 상태들은 모두 App 컴포넌트에서 관리한다.
- App에서 useState를 사용하여 todos라는 상태를 정의하고, todos를 TodoList의 props로 전달해보자.
App.js
import {useState} from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import './App.css';
function App() {
const [todos, setTodos] = useState([
{
id:1,
text: `리액트의 기초 알아보기`,
checked: true,
},
{
id:2,
text: `컴포넌트 스타일링해 보기`,
checked: true,
},
{
id:3,
text: `일정 관리 앱 만들어 보기`,
checked: false,
}
])
return (
<div className="App">
<TodoTemplate>
<TodoInsert/>
<TodoList todos={todos} />
</TodoTemplate>
</div>
);
}
export default App;
- todos 배열 안에 들어 있는 개체에는 각 항목의 고유 id, 내용, 완료 여부를 알려주는 값이 포함되어 있습니다.
- 이 배열 TodoList에 props로 전달되는데요, TodoList에서 이 값을 받아 온 후 TodoItem으로 변환하여 렌더링하도록 설정한다.
TodoList.jsx
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({todos})=>{
return (
<div className="TodoList">
{
todos.map((e,i)=> (
<TodoListItem todo={e} key={e.id}></TodoListItem>
))
}
</div>
)
}
export default TodoList;
TodoListItem.jsx
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;
10-3.2 항목 추가 기능 구현하기
- TodoInsert 컴포넌트에서 인풋 상태를 관리하고 App 컴포넌트에는 todos 배열에 새로운 객체를 추가하는 함수를 만들어 주어야 합니다.
10-3.2.1 TodoInsert value 상태 관리하기
TodoInsert.jsx
import {useState, useCallback} from 'react';
import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';
const TodoInsert = ()=>{
const [value, setValue] = useState('');
const onChange = useCallback(e => {
//컴포넌트가 리렌더링 될 때마다 함수를 새로 만드는 것이 아니라, 한 번 함수를 만들고 재사용할 수 있도록 useCallback Hook을 사용한다.
setValue(e.target.value);
} ,[])
return (
<form className="TodoInsert">
<input type="text" placeholder='할 일을 입력하세용' value={value} onChange={onChange}/>
<button type='submit'>
<MdAdd/>
</button>
</form>
)
}
export default TodoInsert;
- 이제는 input field에 글을 입력할때, 지울때마다 onChange함수가 실행된다 !
10-3.2.3 todos 배열에 새 객체 추가하기
- 이번에는 App 컴포넌트에서 todos 배열에 새 객체를 추가하는 onInsert 함수를 만든다.
- useState가 아닌 useRef로 사용하여 컴포넌트에서 사용 할 변수를 만드는 이유는 ? => id 값은 레더링되는 정보가 아니기 때문이다.
- 또한 onInsert 함수는 컴포넌트의 성능을 아낄 수 이또록 useCallback으로 감싸준다.
- props로 전달해야 할 함수를 만들 때는 useCallbaxk을 사용하여 함수를 감싸는 것을 습관화하면 좋다.
- onInsert 함수를 만든 뒤에는 해당 함수를 TodoInsert 컴포넌트의 props로 설정해 준다.
App.js
import {useState, useRef, useCallback} from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import './App.css';
function App() {
const [todos, setTodos] = useState([
{
id:1,
text: `리액트의 기초 알아보기`,
checked: true,
},
{
id:2,
text: `컴포넌트 스타일링해 보기`,
checked: true,
},
{
id:3,
text: `일정 관리 앱 만들어 보기`,
checked: false,
}
]);
//고윳값으로 사용될 id
//ref를 사용하여 변수 담기
const nextId = useRef(4);
const onInsert = useCallback(
text => {
const todo = {
id: nextId.current,
text,
checked : false,
};
setTodos(todos.concat(todo));
nextId.current += 1; //nextId 1씩 더하기
},
[todos]
)
return (
<div className="App">
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos} />
</TodoTemplate>
</div>
);
}
export default App;
10-3.2.4 TodoInsert에서 onSubmit 이벤트 설정하기
- 이제는 버튼을 클릭하면 발생할 이벤트를 설정해본다.
- App에서 TodoInsert에 넣어준 onInsert 함수에 현재 useState를 통해 관리하고 있는 value 값을 파라미터로 넣어서 호출한다.
TodoInsert.jsx
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 => {
//컴포넌트가 리렌더링 될 때마다 함수를 새로 만드는 것이 아니라, 한 번 함수를 만들고 재사용할 수 있도록 useCallback Hook을 사용한다.
setValue(e.target.value);
} ,[])
const onClick = useCallback(
e => {
onInsert(value);
setValue('') //value 값 초기화
},
[onInsert, value],
)
return (
<div className="TodoInsert">
<input type="text" placeholder='할 일을 입력하세용' value={value} onChange={onChange}/>
<button onClick={onClick}>
<MdAdd/>
</button>
</div>
)
}
export default TodoInsert;
- onClick 함수가 호출되면 props로 받아 온 onInsert 함수에 현재 value 값을 파라미터로 넣어서 호출하고, 현재 value 값을 초기화한다.
- 이제 일정추가기능이 구현되었다!
10-3.3 지우기 기능 구현
10-3.3.1 배열함수 filter
array.filter((e)=>{조건식})
10-3.3.2 todos 배열에서 id로 항목 지우기
- filter함수를 이용하여 onRemove 함수를 만들어보자.
App.js
// ...
function App() {
// ...
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id !==id));
},
[todos]
)
return (
<div className="App">
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos} onRemove={onRemove} />
</TodoTemplate>
</div>
);
}
export default App;
10-3.3.3 TodoListItem에서 삭제 함수 호출하기
- TodoListItem에서 onRemove함수를 사용하려면 props로 TodoList에 우선 내려야한다.
TodoList.jsx
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({todos, onRemove})=>{
return (
<div className="TodoList">
{
todos.map((e,i)=> (
<TodoListItem todo={e} key={e.id} onRemove={onRemove}></TodoListItem>
))
}
</div>
)
}
export default TodoList;
TodoListItem.jsx
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({todo, onRemove})=>{
const {id, text, checked} = todo;
return (
<div className="TodoListItem">
<div className={cn('checkbox', {checked})}>
{checked? <MdCheckBox/> : <MdCheckBoxOutlineBlank/>}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={()=>{onRemove(id)}}>
<MdRemoveCircleOutline/>
</div>
</div>
)
}
export default TodoListItem;
- 삭제 기능 구현 완료!
10-3.4 수정 기능 구현하기
- 수정기능도 비슷하다. onToggle이라는 함수를 App.js에 만들고, props로 TodoList -> TodoListItem까지 전달해준다.
App.js
//...
function App() {
//...
const onToggle = useCallback(
id => {
setTodos(
todos.map((e)=>{
return(
e.id === id ? {...e, checked : !e.checked} : e
)
})
)
},
[todos]
)
return (
<div className="App">
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
</TodoTemplate>
</div>
);
}
export default App;
-TodoList를 통해 TodoListItem에 props로 onToggle함수를 전달하고 check박스 부분에 onClick으로 바인딩해준다.
TodoListItem.jsx
import {
MdCheckBoxOutlineBlank,
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/> : <MdCheckBoxOutlineBlank/>}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={()=>{onRemove(id)}}>
<MdRemoveCircleOutline/>
</div>
</div>
)
}
export default TodoListItem;
- 기능 구현 모두 완료 !
- 다음은 방금 만든 프로젝트의 렌더링 성능 최적화에 대해 포스팅!
'Frontend > ReactJS(완)' 카테고리의 다른 글
React - 12장 : Immer 라이브러리를 사용하여 불변성 유지 (1) | 2023.02.20 |
---|---|
React - 11장 : 컴포넌트 성능 최적화 (0) | 2023.02.18 |
React - 9장 : 컴포넌트의 스타일링 (0) | 2023.02.16 |
React - 8장 : React 자주 쓰이는 Hooks 총 정리 (0) | 2023.02.15 |
React - 7장 : 컴포넌트의 Lifecycle Method (0) | 2023.02.14 |
댓글