1)
yarn add styled-components
yarn add react-icons styled-components
컴포넌트 만들거
1. TodoTemplate
레이아웃을 설정하는 컴포넌트 페이지의 중앙에
그림자가 적용된 흰색박스
2. TodoHead
오늘의 날짜와 요일을 나타냄
앞으로 해야 할 일이 몇개 남았는지 보여줌
3. TodoList
할 일에 대한 정보가 들어있는 todos배열을 내장함수 map을
사용하여 여러개의 Todoitem컴포넌트를 렌더링함
4. TodoItem
각 할 일에 대한 정보를 렌더링해주는 컴포넌트
5. TodoCreate
새로운 할 일을 등록하는 컴포넌트
2)
import { useContext } from 'react';
import { TodoStateContext, TodoDispatchContext } from ''
function Sample(){
const state = useContext(TodoStateContext);
const dispatch = useContext(TodoDispatchContext);
return <div>Sample</div>
}
==> 이렇게도 쓴다.
import { useTodoState, useTodoDispatch } from '../TodoContext';
function Sample() {
const state = useTodoState();
const dispatch = useTodoDispatch();
}
3)
import './App.css';
import { createGlobalStyle } from '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: #e9ecef;
}
`
function App() {
return (
<TodoProvider>
<GlobalStyle/>
<TodoTemplate>
<TodoHead/>
<TodoList/>
<TodoCreate/>
</TodoTemplate>
</TodoProvider>
);
}
export default App;
TodoContext.js
import React, { createContext, useReducer, useContext, useRef } from "react";
const initialTodos = [
{
id: 1,
text: '프로젝트 생성하기',
done: true,
},
{
id: 2,
text: '컴포넌트 스타일링하기',
done: true,
},
{
id: 3,
text: 'Context 만들기',
done: false,
},
{
id: 4,
text: '기능 구현하기',
done: false,
},
];
function todoReducer(state, action) {
switch(action.type){
// action타입이 CREATE면 action객체의 todo를
// state배열에 추가하기
case 'CREATE':
return state.concat(action.todo);
// action타입이 TOGGLE이면 action객체의 id를
// 받아와서 state항목의 id와 일치하면
// 일치하는 항목의 done을 반전 true면 false, false면 true
case 'TOGGLE':
return state.map(todo=>
todo.id === action.id ? {...todo, done: !todo.done} : todo);
// action타입이 REMOVE면 action객체의 id를
// state배열 항목의 id와 비교하여
// 일치하지 않는 항목만 새 배열로 반환해줌
case 'REMOVE':
return state.filter(todo=> action.id !== todo.id);
default:
return state;
}
}
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>
);
}
// 커스텀 Hook
export function useTodoState(){
return useContext(TodoStateContext);
}
export function useTodoDispatch(){
return useContext(TodoDispatchContext);
}
export function useTodoNextId(){
return useContext(TodoNextIdContext);
}
TodoCreate.js
import React, { useState } from 'react';
import styled, { css } from 'styled-components';
import { MdAdd } from 'react-icons/md';
import { useTodoDispatch, useTodoNextId } from '../TodoContext';
import { getValue } from '@testing-library/user-event/dist/utils';
const CircleButton = styled.button`
background: #38d9a9;
&:hover {
background: #63e6be;
}
z-index: 5;
cursor: pointer;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
font-size: 60px;
position: absolute;
bottom: 0;
border-radius: 50%;
left: 50%;
transform: translate(-50%,50%);
color: #fff;
border: none;
transition: 0.3s;
${props=>
props.open &&
css`
background: #ff6b6b;
&:hover {
background: #ff8787;
}
transform: translate(-50%,50%) rotate(45deg);
`
}
`;
const InsertForm = styled.form`
background: #f8f9fa;
padding: 32px 32px 72px;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
border-top: 1px solid #e9ecef;
`;
const Input = styled.input`
padding: 14px;
border-radius: 4px;
border: 1px solid #dee2e6;
width: 100%;
outline: none;
font-size: 18px;
box-sizing: border-box;
`;
const TodoCreate = () => {
const [ open, setOpen ] = useState(false);
const [ value, setValue ] = useState('');
const onToggle = () =>setOpen(!open);
const dispatch = useTodoDispatch();
const nextId = useTodoNextId();
const onChange = e => setValue(e.target.value);
const onSubmit = e => {
e.preventDefault(); // 새로고침 방지
dispatch({
type: 'CREATE',
todo: {
id: nextId.current,
text: value,
done: false,
}
});
setValue('');
setOpen('');
nextId.current += 1;
}
return (
<>
{open && (
<InsertForm onSubmit={onSubmit}>
<Input placeholder='할 일을 입력한 후 Enter누르세요'
value={value} onChange={onChange}/>
</InsertForm>
)}
<CircleButton open={open} onClick={onToggle}>
<MdAdd/>
</CircleButton>
</>
);
};
export default TodoCreate;
TodoHead.js
import React from 'react';
import styled from 'styled-components';
import { useTodoState } from '../TodoContext';
const TodoHeadBlcok = styled.div`
padding:48px 32px 24px;
border-bottom: 1px solid #e9ecef;
h1 {
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;
}
`;
const TodoHead = () => {
// context를 사용하여 state값을 반환함
const todos = useTodoState();
// todos배열 항목 중 done값이 false인 항목만 새 배열로 반환해서
// undoneTasks에 담음
const undoneTasks = todos.filter(todo => !todo.done);
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 (
<TodoHeadBlcok>
<h1>{dateString}</h1>
<div className='day'>{dayname}</div>
<div className='tasks-left'>할 일 {undoneTasks.length}개 남음</div>
</TodoHeadBlcok>
);
};
export default TodoHead;
TodoItem.js
import React from 'react';
import styled, { css } from 'styled-components';
import { MdDone, MdDelete } from 'react-icons/md';
import { useTodoDispatch } from '../TodoContext';
const Remove = styled.div`
display: flex;
align-items: center;
justify-content: center;
color: #dee2e6;
font-size: 24px;
cursor: pointer;
&:hover {
color: #ff6b6b;
}
display: none;
`;
const TodoItemBlock = styled.div`
display: flex;
align-items: center;
padding-top: 12px;
padding-bottom: 12px;
&:hover {
${Remove}{
display: initial;
}
}
`;
const CheckCircle = styled.div`
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid #ced4da;
font-size: 24px;
display: flex;
align-items: center;
justify-constent: center;
margin-right: 20px;
cursor: pointer;
${props =>
props.done &&
css`
border: 1px solid #38d9a9;
color: #38d9a9;
`
}
`;
const Text = styled.div`
flex: 1;
font-size: 21px;
color: #495057;
${props =>
props.done &&
css`
color: #ced4da;
`
}
`;
const TodoItem = ({id, done, text}) => {
const dispatch = useTodoDispatch();
// 항목 클릭했을 때 실행
const onToggle = () => dispatch({type:'TOGGLE', id: id});
const onRemove = () => dispatch({type:'REMOVE', id: id});
return (
<TodoItemBlock>
<CheckCircle done={done} onClick={onToggle}>{done && <MdDone/>}</CheckCircle>
<Text done={done}>{text}</Text>
<Remove onClick={onRemove}>
<MdDelete></MdDelete>
</Remove>
</TodoItemBlock>
);
};
export default TodoItem;
TodoList.js
import React from 'react';
import styled from 'styled-components';
import TodoItem from './TodoItem';
import { useTodoState } from '../TodoContext';
const TodoListBlock = styled.div`
padding: 20px 32px;
overflow-y: auto;
flex: 1;
`
const TodoList = () => {
const todos = useTodoState();
return (
<TodoListBlock>
{todos.map(todo=>(
<TodoItem
key={todo.id}
id={todo.id}
text={todo.text}
done={todo.done}
/>
))}
</TodoListBlock>
);
};
export default TodoList;
TodoTemplate.js
import React from 'react';
import styled from 'styled-components';
const TodoTemplateBlock = styled.div`
width: 512px;
height: 768px;
position: relative;
background: #fff;
border-radius: 16px;
box-shadow: 0 0 8px 0 rgba(0,0,0,0.05);
margin: 0 auto;
margin-top: 96px;
margin-bottom: 32px;
display: flex;
flex-direction: column;
`;
const TodoTemplate = ({children}) => {
return (
<TodoTemplateBlock>{children}</TodoTemplateBlock>
);
};
export default TodoTemplate;

'노력이 좋아서' 카테고리의 다른 글
<step69>'react_router' (0) | 2022.06.28 |
---|---|
<step68>'react_mashup-booklist(useReducer + context API)' (0) | 2022.06.27 |
<step68>'react_createContext' (0) | 2022.06.27 |
<step67>'react_useReducer' (0) | 2022.06.24 |
<step66>'react_styledcomponents' (0) | 2022.06.23 |