일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- list.map
- 가상회선교환
- rebase
- equalityFn
- 리사이클러뷰
- 공부
- 비동기처리
- pull
- CSS
- redux
- createPortal
- php문법
- Git
- javascript
- 리액트
- typescript
- 리덕스
- hot Reloading
- merge
- react
- 리액트를 다루는 기술
- 자동반영
- 웹
- async
- 컬러구성
- useState
- useCallback
- Kotlin
- list
- await
- Today
- Total
공부블로그
10장. 일정관리 웹 애플리케이션 만들기 본문
일정관리 웹 애플리케이션을 만들어 보자.
10. 1 ) 프로젝트 준비하기
1. 프로젝트 생성 및 필요한 라이브러리 설치
$ yarn create-app todo-app 으로 새로운 프로젝트를 생성하고
$ yarn add node-sass classnames react-icons 로 필요한 라이브러리를 설치한다.
//react-icons는 리액트에서 아이콘을 사용할 수 있는 라이브러리로, SVG형태로 이루어진 아이콘을 리액트 컴포넌트처럼 쉽게 사용할 수 있다.
2. Prettier 설정
2장에서 배웠던 Prettier를 설정하여 코드를 작성할 때 코드 스타일을 깔끔하게 정리해보자.
프로젝트의 최상위 디렉터리에 .prettierrc를 작성
3. index.css 수정
4. App컴포넌트 초기화
( 프로젝트 준비가 완료된 모습 )
10. 2 ) UI 구성하기
src디렉터리에 components라는 디렉터리를 생성하여 총 4개의 컴포넌트를 만들어 폴더에 넣어준다.
- TodoTemplate 컴포넌트 : 화면을 가운데에 정렬, 앱 타이틀( 일정관리 )을 보여준다. children으로 내부 JSX를 props으로 받아 와서 렌더링
- TodoInsert 컴포넌트 : 새로운 항목을 입력하고 추가할 수 있다. state를 통해 인풋의 상태를 관리
- TodoListItem 컴포넌트 : 각 할 일 항목에 대한 정보를 보여준다. todo객체를 props로 받아와서 상태에 따라 다른 스타일의 UI를 보여준다.
- TodoList 컴포넌트 : todos배열을 prop로 받아온 후 map함수를 사용해서 여러개의 TodoListItem 컴포넌트로 변환하여 보여준다.
10. 2. 1 ) TodoTemplate 만들기
TodoTemplate. js
import React from 'react';
import './TodoTemplate.scss';
const TodoTemplate = ({ children }) => { //children props받아와서 사용
return (
<div className="TodoTemplate">
<div className="app-title">일정 관리</div>
<div className="content">{children}</div>
</div>
);
};
export default TodoTemplate;
App. js
import React from 'react';
import TodoTemplate from './components/TodoTemplate';
const App = () => {
return <TodoTemplate>Todo앱을 만들자!</TodoTemplate>;
};
export default App;
닫혀 있는 파일에도 자동 완성이 제대로 작동하려면 프로젝트 최상위 디렉터리에 jsconfig.json파일을 만들어 주어야 한다.
jsconfig.json 파일을 만들고 열어서 ctrl + Space를 눌러주면 자동 완성 박스에 의해 코드가 작성된다.
여기까지 작성하면 아래와 같이 나온다.
이제 꾸며보자! TodoTemplate. scss
.TodoTemplate { /*TodoTemplate클래스에 적용할 스타일*/
width: 512px;
/*넓이가 주어진 상태에서 좌우 중앙 정렬*/
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
border-radius: 4px;
overflow: hidden;
.app-title { /*app-title클래스에 적용할 스타일*/
background: #22b8cf;
color: white;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.content { /*content에 적용할 스타일*/
background: white;
}
}
( 실행 화면 )
10. 2. 2 ) TodoInsert 만들기
components 디렉터리에 TodoInsert.js와 TodoInsert.scss파일을 생성해서 작성해보자.
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;
App. js에서 렌더링 해주면 아래와 같은 화면이 나온다.
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 {
//input의 문구에 적용됨
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;
//버튼에 마우스를 올리면 배경색이 변경됨
}
}
}
( 실행화면 )
10. 2. 3 ) TodoListItem과 TodoList 만들기
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;
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;
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;
( 실행 화면 )
TodoLsit. scss
.TodoList {
/*전체 틀 크기 조절*/
min-height: 320px;
max-height: 513px;
overflow: auto;
}
TodoLsitItem. 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;
}
}
스타일링까지 마치면 아래와 같이 나온다.
10. 3 ) 기능 구현하기
10. 3. 1 ) App에서 todos 상태 사용하기
나중에 추가할 일정 항목에 대한 상태들은 모두 App컴포넌트에서 관리한다.
App에서 useState를 사용하여 todos라는 상태를 정의하고, todos를 TodoList의 props로 전달한다.
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} /> {/* props 넣어주기 */}
</TodoTemplate>
);
};
export default App;
TodoList. js
import React from 'react';
import TodoListItem from './TodoListItem'; //블러오기
import './TodoList.scss';
const TodoList = ({ todos }) => {
//받아온 props
return (
//map함수로 TodoListItem으로 이루어진 배열로 변환
<div className="TodoList">
{todos.map((todo) => (
<TodoListItem todo={todo} key={todo.id} /> //todo데이터는 통째로 props에 전달, key는 id
))}
</div>
);
};
export default TodoList;
TodoListItem. js
import React from 'react';
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from 'react-icons/md'; //아이콘 여러개 사용
import cn from 'classnames'; //조건부 스타일링을 위해 classnames사용
import './TodoListItem.scss'; //스타일 받아오기
const TodoListItem = ({ todo }) => {
const { text, checked } = todo; //객체
return (
<div className="TodoListItem">
<div className={cn('checkbox', { checked })}>
{' '}
{/* checked가 참이어야 checked클래스가 적용된다. */}
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove">
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
TodoList에서 props으로 받아온 값을 TodoListItem으로 변환하여 렌더링하도록 설정해준다.
최종적으로 TodoList컴포넌트는 App에서 전달해 준 todos값에 따라 다른 내용을 보여준다
10. 3. 2 ) 항목 추가 기능 구현하기
TodoInsert컴포넌트에서 인풋상태를 관리하고 App컴포넌트에는 todos배열에 새로운 객체를 추가하는 함수를 만들어야 한다.
1. TodoInsert value 상태 관리하기
TodoInsert 컴포넌트에서 input에 입력하는 값을 관리할 수 있도록 useState를 사용하여 value라는 상태를 정의,
input의 이벤트를 핸들링할 onChange 함수도 작성해야 한다. ( 이때 useCallBack Hook을 사용해 함수를 재사용할 수 있도록 해준다. )
ToInsert. js
import React from 'react';
import { MdAdd } from 'react-icons/md'; // {아이콘 이름}
import { useState } from '../../node_modules/react/cjs/react.development';
import { useCallback } from '../../node_modules/react/index';
import './TodoInsert.scss';
const TodoInsert = () => {
const [value, setValue] = useState(''); //useState로 상태 정의
//onChange함수를 콜백함수로 정의
const onChange = useCallback((e) => {
setValue(e.target.value); //상태를 인풋에 입력받은 값으로 업데이트 해준다.
}, []);
return (
<form className="TodoInsert">
<input
placeholder="할 일을 입력하세요"
value={value} //초기값
onChange={onChange} //onChange함수로 핸들링
/>
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
2. 리액트 개발자 도구
사실 인풋은 value와 onChange를 설정하지 않아도 입력할 수 있다. 그저 추적하지 않을 뿐이다. 이런 경우에 state가 잘 업데이트되고 있는지 확인하려면 리액트 개발자 도구를 사용하면 된다.
크롬 웹 스토어에서 React Developer Tools를 검색하여 설치해보자. ( 설치했는데 나는 "⚛️ Components" and "⚛️ Profiler" 이게 뜬다... 왜이러는지는 모르게따 )
3. todos 배열에 새 객체 추가하기
App컴포넌트에서 todos 배열에 새 객체를 추가하는 onInsert함수를 만들어보자.
새로운 객체를 만들 때마다 id에는 1씩 더해주고 id는 useRef를 통해 관리한다. ( 렌더링해도 바뀌지 않으므로 )
그리고 콜백함수로 onInsert를 감싸서 재사용해준다. ( 콜백함수 사용을 습관화하는 것이 좋다 )
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([
//초기값으로 객체 배열이 들어감, todos는 객체 배열!!
{
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)); //todos배열에 위에서 만들어준 todo객체를 추가해준다
nextId.current += 1;//id+1
},
[todos], //todos가 바뀔때만 렌더링
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert}/>
<TodoList todos={todos} /> {/* props 넣어주기 */}
</TodoTemplate>
);
};
export default App;
4. TodoInsert에서 onSubmit 이벤트 설정하기
버튼을 클릭하면 발생할 이벤트를 설정, App에서 TodoInsert에 넣어준 onInsert함수에 value값을 파라미터로 넣어서 호출한다.
TodoInsert. js
import React from 'react';
import { MdAdd } from 'react-icons/md'; // {아이콘 이름}
import { useState } from '../../node_modules/react/cjs/react.development';
import { useCallback } from '../../node_modules/react/index';
import './TodoInsert.scss';
const TodoInsert = ({ onInsert }) => {
//App.js 에서 <TodoInsert onInsert={onInsert}라고 작성되어 있음>, props로 함수를 받아옴
const [value, setValue] = useState(''); //useState로 상태 정의
//콜백함수로 정의
const onChange = useCallback((e) => {
setValue(e.target.value); //상태를 인풋에 입력받은 값으로 업데이트 해준다.
}, []);
const onSubmit = useCallback(
//onSubmit이벤트를 핸들링할 때 넣어줄 함수
(e) => {
onInsert(value); //업데이트된value값을 onInsert함수에 넣어서 호출해준다. ( value == text )
setValue(''); //value값 초기화
//submit 이벤트는 새로고침을 발생시킨다. 이를 방지하기 위해 아래 함수 호출
e.preventDefault();
},
[onInsert, value],
);
return (
<form className="TodoInsert" onSubmit={onSubmit}>
<input
placeholder="할 일을 입력하세요"
value={value} //초기값
onChange={onChange} //onChange함수로 핸들링
/>
<button
type="submit" //submit타입
>
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
onSubmit 대신에 onClick 이벤트로도 충분히 처리할 수 있지만, onSubmit은 인풋에서 엔터키를 눌러도 발생하기 때문에 더욱 유용하다.
10. 3. 3 ) 지우기 기능 구현하기
1. 배열 내장 함수 filter로 todos배열에서 id로 항목 지우기
App컴포넌트에 id를 파라미터로 받아와서 해당id의 항목을 지우는 함수인 onRemove를 작성해 보자.
App. js
import React, { useState, useRef, useCallback } from 'react';
...
const App = () => {
...
const onRemove = useCallback(
(id) => {
setTodos(todos.filter((todo) => todo.id !== id));
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} /> {/* props 넣어주기 */}
</TodoTemplate>
);
};
export default App;
2. TodoListItem에서 삭제함수 호출하기
TodoList. js에서는 props로 받아온 onRemove 함수를 그대로 props로 TodoListItem으로 전달해준다.
const TodoList = ({ todos, onRemove }) => {
return (
<div className="TodoList">
{todos.map((todo) => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
))}
</div>
);
};
TodoListItem. js 에서는 onClick이벤트를 핸들링 하는 함수로 받아온 onRemove함수를 사용한다.
const TodoListItem = ({ todo, onRemove }) => {
const { id, text, checked } = todo; //객체
return (
. . .
<div
className="remove"onClick={( ) => onRemove( id )} //아이콘을 누르면 onRemove함수 호출>
<MdRemoveCircleOutline />
</div>
</div>
);
};
10. 3. 4 ) 수정 기능
삭제 기능과 비슷하게 onToggle함수를 App에 만들고, 해당 함수를 TodoList 컴포넌트에 props로 넣어준다. 그다음 TodoList를 통해 TodoListItem까지 전달한다.
App. js
import React, { useState, useRef, useCallback } from 'react';
...
const App = () => {
...
const onToggle = useCallback(
(id) => {
setTodos(
todos.map(( //특정 id를 가지고 있는 객체의 checked값을 반전시켜줌
todo,) => (todo.id === id ? { ...todo, checked: !todo.checked } : todo)),
);
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />{' '}
{/* props 넣어주기 */}
</TodoTemplate>
);
};
export default App;
여기에서 map을 쓰는 이유..?
TodoList. js 에서 onToggle을 props로 받아와 그대로 props으로 전달
const TodoList = ({ todos, onRemove, onToggle }) => {
return (
<div className="TodoList">
{todos.map((todo) => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToggle={onToggle}/>
))}
</div>
);
};
TodoListItem. js
const TodoListItem = ({ todo, onRemove, onToggle }) => {
const { id, text, checked } = todo; //객체
return (
<div className="TodoListItem">
<div className={cn('checkbox', { checked })} onClick={() => onToggle(id)}>
{/* checked가 참이어야 checked클래스가 적용된다. */}
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
...
</div>
</div>
);
};
끝!
'리액트 > 리액트를 다루는 기술' 카테고리의 다른 글
13장. 리액트 라우터로 SPA 개발하기 (0) | 2021.02.02 |
---|---|
11장. 컴포넌트 성능 최적화 (0) | 2021.01.23 |
9강 컴포넌트 스타일링 (0) | 2021.01.20 |
리액트 8강. Hooks (0) | 2021.01.14 |
리액트 7장. 컴포넌트의 라이프사이클 메서드 (0) | 2021.01.07 |