App.js
import React from 'react';
import { createGlobalStyle } from 'styled-components';
//yarn add styled-components
import TodoTemplate from './components/todoTemplate';
import TodoHead from './components/todohead';
import TodoList from './components/todolist';
import TodoCreate from './components/todoCreate';
import { TodoProvider } from './todoContext';
const GlobalStyle = createGlobalStyle`
body {
background-color: skyblue;
}
`;
function App() {
return (
<>
<TodoProvider>
<GlobalStyle />
<TodoTemplate>
<TodoHead />
<TodoList />
<TodoCreate />
</TodoTemplate>
</TodoProvider>
</>
);
}
export default App;
todoContext.js
import React, { useReducer, createContext, useContext, useRef } from 'react';
const initialTodos = [
{
id: 1,
text: '프론트엔드 프로젝트 만들기',
done: false
},
{
id: 2,
text: '밥 잘 챙겨먹기',
done: true
},
{
id: 3,
text: '운동하기',
done: true
},
{
id: 4,
text: '일기쓰기',
done: false
}
];
function todoReducer(state, action){
switch(action.type){
case 'CREATE':
return state.concat(action.todo);
case 'TOGGLE':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo );
case 'REMOVE':
return state.filter(todo => todo.id !== action.id);
default:
throw new Error(`${action.type}이 잘못 전달됨`);
}
}
const TodoStateContext = createContext();
const TodoDispatchContext = createContext();
const TodoNextIdContext = createContext();
export function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, initialTodos);
const nextId = useRef(5);
return (
<TodoStateContext.Provider value={state}>
<TodoDispatchContext.Provider value={dispatch}>
<TodoNextIdContext.Provider value={nextId}>
{children}
</TodoNextIdContext.Provider>
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
);
}
export function useTodoState(){
const context = useContext(TodoStateContext);
if(!context){
throw new Error('TodoProvider를 찾을 수 없음');
}
return context;
}
export function useTodoDispatch(){
const context = useContext(TodoDispatchContext);
if(!context){
throw new Error('TodoProvider를 찾을 수 없음');
}
return context;
}
export function useTodoNextId(){
const context = useContext(TodoNextIdContext);
if(!context){
throw new Error('TodoProvider를 찾을 수 없음');
}
return context;
}
components>todoCreate.jsx
import React, {useState} from "react";
import styled, {css} from "styled-components";
import {MdAdd} from "react-icons/md";
import {useTodoDispatch, useTodoNextId} from "../todoContext";
const CircleButton = styled.button`
background-color: #38d9a9;
&:hover {
background-color: #63e6be;
}
&:active {
background: #20c997;
}
z-index: 5;
cursor: pointer;
width: 80px;
height: 80px;
position: absolute;
left: 50%;
bottom: 0px;
line-height:10px;
text-align:center;
font-size: 60px;
transform: translate(-50%, 50%);
color: white;
border-radius: 50%;
border: none;
outline: none;
transition: 0.2s all ease-in;
${(props) =>
props.open &&
css`
background-color: #ff6b6b;
&:hover {
background: #ff8787;
}
&:active {
background: #fa5252;
}
transform: translate(-50%, 50%) rotate(45deg);
`}
`;
const InsertFormPositioner = styled.div`
width: 100%;
bottom: 0;
left: 0;
position: absolute;
`;
const InsertForm = styled.form`
background: #f8f9fa;
padding: 32px 32px 72px 32px;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
border-top: 1px solid #e9ecef;
`;
const Input = styled.input`
padding: 12px;
border-radius: 4px;
border: 1px solid #dee2e6;
width: 100%;
outline: none;
font-size: 18px;
box-sizing: border-box;
`;
function TodoCreate() {
const [open, setOpen] = useState(false); // <InsertFormPositioner>에 대한 있다 없다 상태 초기값
const [value, setValue] = useState(""); // <Input> 태그에 들어가는 값을 상태 초기값 설정
//리덕스함수 생성상태, 현재 배열의 마직막, 키값, 연산값
const dispatch = useTodoDispatch();
const nextId = useTodoNextId();
const onToggle = () => setOpen(!open); // <InsertFormPositioner>에 대한 있다 없다 상태 설정 함수
const onchange = (e) => setValue(e.target.value);
const onSubmit = (e) => {
e.preventDefault(); // 서버로 데이터를 전송하는 기능을 막음(새로고침X)
dispatch({
type: "CREATE",
todo: {
id: nextId.current,
text: value,
done: false,
},
});
setValue("");
setOpen(false);
nextId.current += 1;
};
return (
<>
{open && (
<InsertFormPositioner>
<InsertForm onSubmit={onSubmit}>
<Input
autoFocus
placeholder="할 일을 입력 후, Enter를 누르세요"
onChange={onchange}
value={value}
/>
</InsertForm>
</InsertFormPositioner>
)}
<CircleButton onClick={onToggle} open={open}>
<MdAdd />
</CircleButton>
</>
);
}
export default React.memo(TodoCreate);
components>todoHead.jsx
import React from 'react';
import styled from 'styled-components';
import { useTodoState } from '../todoContext';
const TodoHeadBlock = styled.div`
padding: 48px 32px 24px 32px;
border-bottom: 1px solid #e9ecef;
h1 {
margin: 0;
font-size: 36px;
color: #343a40;
}
.day {
margin-top: 4px;
color: #868e96;
font-size: 21px;
}
.tasks-left {
color: #20c997;
font-size: 18px;
margin-top: 40px;
font-weight: bold;
}
`;
function TodoHead(){
const todos = useTodoState();
const undoneTasks = todos.filter((todo) => !todo.done);
//done -> 할일 리스트에서 체크가 되었는지 유무, 선택 안된 개수를 파악
//map() 배열요소 개수만큼 실행한다.
//filter(조건) 조건에 해당되는 값을 되돌려 받는
const today = new Date();
const dateString = today.toLocaleDateString("ko-KR", {
year: "numeric",
month: "long",
day: "numeric",
});
const dayName = today.toLocaleDateString("ko-KR", {weekday: "long"});
return (
<TodoHeadBlock>
<h1>{dateString}</h1>
<div className="day">{dayName}</div>
<div className="tasks-left">할 일 {undoneTasks.length}개 남음</div>
</TodoHeadBlock>
);
}
export default TodoHead;
components>todoItem.jsx
import React from 'react';
import styled, {css} from 'styled-components';
import { MdDone, MdDelete } from 'react-icons/md';
import { useTodoDispatch } from '../todoContext';
//yarn add react-icons
const TodoItemBlock = styled.div`
display: flex;
align-items: center;
padding-top: 12px;
padding-bottom: 12px;
`;
const CheckCircle = styled.div`
width: 32px;
height: 32px;
border-radius: 16px;
border: 1px solid #ced4da;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
cursor: pointer;
${props => props.done && css`
border: 1px solid #38d9a9;
`}
`;
const Text = styled.div`
flex: 1;
font-size: 21px;
color: #495057;
${props => props.done &&
css`
color: #ced4da;
`}
`;
const Remove = styled.div`
display: flex;
align-items: center;
justify-content: center;
color: #dee2e6;
font-size: 24px;
cursor: pointer;
// &:hover {
// color: #ff6b6b;
// }
// display: none;
`;
function TodoItem({ id, done, text }) {
const dispatch = useTodoDispatch();
const onToggle = () => dispatch({ type: 'TOGGLE', id });//체크 상태를 발생
const onRemove = () => dispatch({type: "REMOVE", id});//삭제상태를 발생
return(
<TodoItemBlock>
<CheckCircle done={done} onClick={onToggle}>{done && <MdDone/>}</CheckCircle>
<Text done={done}>{text}</Text>
<Remove onClick={onRemove}>
<MdDelete/>
</Remove>
</TodoItemBlock>
)
}
export default TodoItem;
components>todoList.jsx
import React from 'react';
import styled from 'styled-components';
import TodoItem from './todoItem';
import { useTodoState } from '../todoContext';
const TodoListBlock = styled.div`
flex: 1;
padding: 20px 32px;
padding-bottom: 48px;
overflow-y: auto;
`;
function TodoList() {
const todos = useTodoState();
return (
<TodoListBlock>
{todos.map(todo => (//배열을 반복해서 출력
<TodoItem
key={todo.id}
id={todo.id}
text={todo.text}
done={todo.done}
/>
))}
{/* <TodoItem text="프로젝트 생성하기" done={true} />
<TodoItem text="컴포넌트 스타일링 하기" done={true} />
<TodoItem text="Context 만들기" done={false} />
<TodoItem text="기능 구현하기" done={false} /> */}
</TodoListBlock>
);
}
export default TodoList;
components>todoTemplate.jsx
import React from 'react';
import styled from 'styled-components';
const TodoTemplateBlock = styled.div`
width: 512px;
height: 768px;
position: relative;
background-color: white;
border-radius: 16px;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
margin: 0 auto;
margin-top: 96px;
margin-bottom: 32px;
display: flex;
flex-direction: column;
`;
function TodoTemplate({children}){
return(
<TodoTemplateBlock>
{children}
</TodoTemplateBlock>
)
}
export default TodoTemplate;
'코리아 IT아카데미 > react.js' 카테고리의 다른 글
6일차 | react_Route (0) | 2022.03.16 |
---|---|
5일차 | react4(Icon, styled) (0) | 2022.03.14 |
3일차 | react2 (0) | 2022.03.10 |
2일차 | react2 (0) | 2022.03.07 |
1일차 | react 기본 (0) | 2022.03.03 |