1. UI 구성하기
TodoTemplate | -화면을 가운데에 정렬시켜 주며, 엡 타이틀(일정 관리)을 보여줌 - children으로 내부 JSX를 props로 받아와서 렌더링해 줌 |
TodoInsert | - 새로운 항목을 입력하고 추가할 수 있는 컴포넌트 - state를 통해 인풋의 상태를 관리 |
TodoListItem | - 각 할일 항목에 대한 정보를 보여 주는 컴포넌트 - todo 객체를 props로 받아와서 상태에 따라 다른 스타일의 UI를 보여줌 |
TodoList | - todos 배열을 props로 받아 온 후, 이를 배열 내장 함수 map을 사용해서 여러개의 TodoListItem 컴포넌트로 변환하여 보여 줌 |
2.1 TodoTemplate 만들기
TodoTemplate.js
import React from 'react';
import styled from 'styled-components';
const TodoTemplateS = styled.div`
width: 512px;
// width가 주어진 상태에서 좌우 중앙 정렬
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
border-radius: 4px;
overflow: hidden;
`;
const AppTitle = styled.div`
background: #22b8cf;
color: white;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
`;
const Content = styled.div`
background: white;
`;
const TodoTemplate = ({children}) => {
return (
<TodoTemplateS>
<AppTitle>일정 관리</AppTitle>
<Content>{children}</Content>
</TodoTemplateS>
);
};
export default TodoTemplate;
2.2 TodoInsert 만들기
TodoInsert.js
import { FcAcceptDatabase } from "react-icons/fc";
import styled from "styled-components";
const TodoInserts = styled.form`
display: flex;
background: #495057;
`;
const Inputs = styled.input`
background: none;
outline: none;
border: none;
padding: 0.5rem;
font-size: 1.125rem;
line-height: 1.5;
color: white;
flex: 1;
`;
const Buttons = styled.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;
`;
const TodoInsert = () => {
return (
<TodoInserts>
<Inputs placeholder="할 일을 입력하세요" />
<Buttons type="submit">
<FcAcceptDatabase />
</Buttons>
</TodoInserts>
);
};
export default TodoInsert;
2.3 TodoListItem과 TodoList 만들기
TodoListItem.js
import styled from "styled-components";
import { MdOutlineCheckBoxOutlineBlank} from "react-icons/md";
import { CiSquareRemove } from "react-icons/ci";
const TodoListItems = styled.div`
padding: 1rem;
display: flex;
align-items: center; // 세로 중앙 정렬
`;
const Checkbox = styled.div`
cursor: pointer;
flex: 1; // 차지할 수 있는 영역 모두 차지
display: flex;
align-items: center; // 세로 중앙 정렬
`;
const Text = styled.div`
margin-left: 0.5rem;
flex: 1; // 차지할 수 있는 영역 모두 차지
&.checked {
svg {
color: #22b8cf;
}
.text {
color: #adb5bd;
text-decoration: line-through;
}
}
`;
const Remove = styled.div`
display: flex;
align-items: center;
font-size: 1.5rem;
color: #ff6b6b;
cursor: pointer;
&:hover {
color: #ff8787;
}
`;
const TodoListItem = () => {
return (
<>
<TodoListItems>
<Checkbox>
<MdOutlineCheckBoxOutlineBlank />
<Text>할일</Text>
</Checkbox>
</TodoListItems>
<Remove>
<CiSquareRemove color='red' />
</Remove>
</>
)
}
export default TodoListItem;
TodoList.js
import TodoListItem from "./TodoListItem";
import styled from "styled-components";
const TodoLists = styled.div`
min-height: 320px;
max-height: 513px;
overflow-y: auto;
`;
const TodoList = () => {
return (
<TodoLists>
<TodoListItem />
<TodoListItem />
<TodoListItem />
</TodoLists>
)
}
export default TodoList
App.js
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;
3. 기능 구현하기
3.1 App에서 todos 상태 사용하기
- 나중에 추가할 일정 항목에 대한 상태는 모두 App 컴포넌트에서 관리
- App에서 useState를 사용하여 todos라는 상태를 정의하고, todos를 TodoList의 props로 전달
- todos 배열 안에 들어있는 객체는 각 항목의 고유 id, 내용, 완료 여부를 알려주는 값이 포함되어 있음
- 이 배열은 TodoList에 props로 전달
- TodoList에서 이 값을 받아온 후 TodoItem으로 변환하여 렌더링하도록 설정
App.js
import {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;
- props로 받아 온 todos 배열을 배열 내장 함수 map을 통해 TodoListItem으로 이루어진 배열로 변환하여 렌더링해 줌
- todo 데이터는 통째로 props로 전달
import TodoListItem from "./TodoListItem";
import styled from "styled-components";
const TodoLists = styled.div`
min-height: 320px;
max-height: 513px;
overflow-y: auto;
`;
const TodoList = ({todos}) => {
return (
<TodoLists>
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} />
))}
</TodoLists>
)
}
export default TodoList
- TodoListItem 컴포넌트에서 받아 온 todo 값에 따라 UI를 보여줄 수 있도록 수정
TodoListItem.js
import styled from "styled-components";
import { MdOutlineCheckBoxOutlineBlank,MdCheckBox} from "react-icons/md";
import { CiSquareRemove } from "react-icons/ci";
const TodoListItems = styled.div`
padding: 1rem;
display: flex;
align-items: center; // 세로 중앙 정렬
`;
const Checkbox = styled.div`
cursor: pointer;
flex: 1; // 차지할 수 있는 영역 모두 차지
display: flex;
align-items: center; // 세로 중앙 정렬
&.checked {
svg {
color: #22b8cf;
}
.text {
color: #adb5bd;
text-decoration: line-through;
}
}
`;
const Text = styled.div`
margin-left: 0.5rem;
flex: 1; // 차지할 수 있는 영역 모두 차지
`;
const Remove = styled.div`
display: flex;
align-items: center;
font-size: 1.5rem;
color: #ff6b6b;
cursor: pointer;
&:hover {
color: #ff8787;
}
`;
const Wrapper = styled.div`
// 엘리먼트 사이사이에 테두리를 넣어줌
& + & {
border-top: 1px solid #dee2e6;
}
`;
const TodoListItem = ({todo}) => {
const {text, checked} = todo;
return (
<Wrapper>
<TodoListItems>
<Checkbox>
{checked? <MdCheckBox/> :<MdOutlineCheckBoxOutlineBlank /> }
</Checkbox>
<Text>{text}</Text>
</TodoListItems>
<Remove>
<CiSquareRemove color='red' />
</Remove>
</Wrapper>
)
}
export default TodoListItem;
3.2 항목 추가 기능 구현하기
- TodoInsert 컴포넌트에서 인풋 상태를 관리하고 App 컴포넌트에는 todos 배열에 새로운 객체를 추가하는 함수를 만들어 주어야 함
1) TodoInsert value 상태 관리하기
- TodoInsert 컴포넌트에서 인풋에 입력하는 값을 관리할 수 있도록 useState를 사용하여 value라는 상태를 정의
- useCallback 훅을 사용해 인풋에 넣어 줄 onChange 함수도 작성
TodoInsert.js
...
const TodoInsert = () => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
},[]);
return (
<TodoInserts>
<Inputs placeholder="할 일을 입력하세요"
value={value}
onChange={onChange} />
<Buttons type="submit">
<FcAcceptDatabase />
</Buttons>
</TodoInserts>
);
};
...
2) todos 배열에 새 객체 추가하기
- App 컴포넌트에서 todos 배열에 새 객체를 추가하는 onInsert 함수 만들기
- 새로운 객체를 만들 때마다 id값에 1씩 더해줘야 함
- id값은 useRef를 사용하여 관리 (id값은 렌더링되는 정보가 아니고, 단순히 새로운 항목을 만들 때 참조되는 값임)
- onInsert 함수는 컴포넌트의 성능을 아낄 수 있도록 useCallback으로 감싸 줌
- props으로 전달해야 할 함수를 만들 때는 useCallback을 사용하여 함수를 감싸는 것을 습관화
App.js
...
const App = () => {
// eslint-disable-next-line
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 (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos}/>
</TodoTemplate>
);
};
export default App;
3) TodoInsert에서 onSubmit 이벤트 설정하기
- 버튼을 클릭하면 발생할 이벤트 설정
- App에서 TodoInsert에 넣어 준 onInsert 함수에 현재 useState를 통해 관리하고 있는 value 값을 파라미터로 넣어서 호출
- onSubmit 이라는 함수를 만들고, 이를 form의 onSubmit으로 설정
- 이 함수가 호출되면 props로 받아온 onInsert 함수에 현재 value 값을 파라미터로 넣어서 호출하고, 현재 value 값을 초기화
- onSubmit 이벤트는 브라우저를 새로고침시킴
-> 이를 방지하기 위해 e.preventDefault()를 호출
TodoInsert.js
...
const TodoInsert = ({onInsert}) => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
},[]);
const onSubmit = useCallback(
e => {
onInsert(value);
setValue('');//value 값 초기화
//submit 이벤트는 브라우저에서 새로고침을 발생시킴
//이를 방지하기 위해 이 함수를 호출
e.preventDefault();
},
[onInsert, value],
);
return (
<TodoInserts onSubmit={onSubmit}>
<Inputs placeholder="할 일을 입력하세요"
value={value}
onChange={onChange} />
<Buttons type="submit">
<FcAcceptDatabase />
</Buttons>
</TodoInserts>
);
};
export default TodoInsert;
3.3 지우기 기능 구현하기
- 리액트 컴포넌트에서 배열의 불변성을 지키면서 배열 원소를 제거해야 할 경우, 배열 내장 함수인 filter를 사용하면 매우 간편함
1) 배열 내장 함수 filter
- 기존의 배열은 그대로 둔 상태에서 특정 조건을 만족하는 원소들만 따로 추출하여 새로운 배열을 만들어 줌
const array = [1,2,3,4,5,6,7,8,9,10];
const biggerThanFive = array.filter(number=>number>5);
//결과: [6,7,8,9,10]
2) todos 배열에서 id로 항목 지우기
- App 컴포넌트에 id를 파라미터로 받아와서 같은 id를 가진 항목을 tods 배열에서 지우는 함수
- 함수를 만들고 나서 TodoList의 props로 설정
App.js
...
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo=>todo.id!==id));
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove}/>
</TodoTemplate>
);
};
2) TodoListItem에서 삭제 함수 호출하기
- TodoListItem에서 onRemove 함수를 사용하려면 TodoList 컴포넌트를 거쳐야 함
- props로 받아 온 onRemove 함수를 TodoListItem에 그대로 전달
TodoList.js
const TodoList = ({todos, onRemove}) => {
return (
<TodoLists>
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
))}
</TodoLists>
)
}
- 삭제 버튼을 누르면 TodoListItem에서 onRemove 함수에 현재 자신이 가진 id를 넣어서 삭제 함수를 호출하도록 설정
TodoListItem.js
...
const TodoListItem = ({todo, onRemove}) => {
const {id, text, checked} = todo;
return (
<Wrapper>
<TodoListItems>
<Checkbox>
{checked? <MdCheckBox/> :<MdOutlineCheckBoxOutlineBlank /> }
</Checkbox>
<Text>{text}</Text>
</TodoListItems>
<Remove onClick={()=>onRemove(id)} >
<CiSquareRemove color='red' />
</Remove>
</Wrapper>
)
}
export default TodoListItem;
3.4 수정 기능
- onToggle이라는 함수를 App 에 만들고 해당 함수를 TodoList 컴포넌트에 props로 넣어줌
- 그 다음에는 TodoList를 통해 TodoListItem까지 전달해 주면 됨
1) onToggle 구현하기
- 배열 내장 함수 map을 사용하여 특정 id를 가지고 있는 객체의 checked 값을 반전 시켜줌
- 불변성을 유지하면서 특정 배열 원소를 업데이트해야 할 때 이렇게 map을 사용하면 짧은 코드로 쉽게 작성 가능
- todo.id와 현재 파라미터로 사용된 id값이 같을 떄는 새로운 객체를 생성하지만, id 값이 다를 때는 변화를 주지 않고 처음 받아왔던 상태 그대로 반환
- map을 사용하여 만든 배열에서 변화가 필요한 원소만 업데이트되고 나머지는 그대로 남아 있게됨
App.js
...
const onToggle = useCallback(
id=> {
setTodos(
todos.map(todo=>
todo.id === id ? {...todo, checked: !todo.checked} : todo,),
);
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
</TodoTemplate>
);
};
2) TodoListItem에서 토글 함수 호출하기
TodoList.js
const TodoList = ({todos, onRemove, onToggle}) => {
return (
<TodoLists>
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToogle={onToggle} />
))}
</TodoLists>
)
}
TodoListItem.js
const TodoListItem = ({todo, onRemove, onToggle}) => {
const {id, text, checked} = todo;
return (
<Wrapper>
<TodoListItems>
<Checkbox onClick={()=>onToggle(id)}>
{checked? <MdCheckBox/> :<MdOutlineCheckBoxOutlineBlank /> }
</Checkbox>
<Text>{text}</Text>
</TodoListItems>
<Remove onClick={()=>onRemove(id)} >
<CiSquareRemove color='red' />
</Remove>
</Wrapper>
)
}
export default TodoListItem;
'react' 카테고리의 다른 글
인프런 - MERN 스택으로 만드는 지도 서비스 (+TypeScript) (2) (0) | 2023.11.10 |
---|---|
인프런 - MERN 스택으로 만드는 지도 서비스 (+TypeScript) (1) (3) | 2023.11.10 |
[리액트를 다루는 기술] 8장 Hooks (2) | 2023.11.03 |
[리액트를 다루는 기술] 7장 컴포넌트의 라이프사이클 메서드 (0) | 2023.11.03 |
[리액트를 다루는 기술] 6장 컴포넌트 반복 (1) | 2023.11.01 |