zoaseo
To Infinity And Beyond
zoaseo
전체 방문자
오늘
어제
  • 분류 전체보기 (763)
    • 개발이 좋아서 (381)
      • SAP가 좋아서 (3)
      • Java가 좋아서 (42)
      • Spring이 좋아서 (50)
      • JPA가 좋아서 (0)
      • QueryDSL이 좋아서 (26)
      • Docker가 좋아서 (7)
      • Redis가 좋아서 (7)
      • AWS가 좋아서 (5)
      • CI/CD가 좋아서 (6)
      • Troubleshooting이 좋아서 (4)
      • Kotlin이 좋아서 (7)
      • SQL이 좋아서 (6)
      • HTTP가 좋아서 (21)
      • JavaScript가 좋아서 (30)
      • TypeScript가 좋아서 (6)
      • Vue가 좋아서 (21)
      • Flutter가 좋아서 (61)
      • React가 좋아서 (20)
      • Redux(React)가 좋아서 (2)
      • Angular가 좋아서 (22)
      • HTML이 좋아서 (9)
      • CSS가 좋아서 (15)
      • PHP가 좋아서 (9)
      • Illustrator가 좋아서 (2)
    • 노력이 좋아서 (169)
    • 결과물이 좋아서 (14)
    • 코딩연습이 좋아서 (168)
      • 이론이 좋아서 (62)
      • SQL이 좋아서 (90)
    • 유용한 사이트가 좋아서 (28)
    • Github (2)

인기 글

티스토리

hELLO · Designed By 정상우.
zoaseo
노력이 좋아서

<step68>'react_mashup-todolist(useReducer + context API)'

<step68>'react_mashup-todolist(useReducer + context API)'
노력이 좋아서

<step68>'react_mashup-todolist(useReducer + context API)'

2022. 6. 27. 12:08

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

    티스토리툴바

    개인정보

    • 티스토리 홈
    • 포럼
    • 로그인

    단축키

    내 블로그

    내 블로그 - 관리자 홈 전환
    Q
    Q
    새 글 쓰기
    W
    W

    블로그 게시글

    글 수정 (권한 있는 경우)
    E
    E
    댓글 영역으로 이동
    C
    C

    모든 영역

    이 페이지의 URL 복사
    S
    S
    맨 위로 이동
    T
    T
    티스토리 홈 이동
    H
    H
    단축키 안내
    Shift + /
    ⇧ + /

    * 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.