TIL|#14 React|TodoList#1の作成


2021年-04-05(月)

レスポンスモジュールとインストールモジュールの作成

yarn react create-app todoListnode-sass、classname、react-insのインストールyarn add node-sass classnames react-icons
  • TodoTemplate
    アプリケーション 見出し 見せてあげる. 子供として. 内部 jsx props 受け取る. レンダー
  • TodoInsert
    新しい プロジェクト 入力 および 追加 ≪ステータス|Status|emdw≫ に合格 入力 ≪ステータス|Status|emdw≫ 管理
  • TodoListItem
    プロジェクト内 大寒 情報 見せてあげる. オブジェクトを しちゅう 持ってきてください. ≪ステータス|Status|emdw≫ ついて行く その他 スタイル 見せてあげる.
  • TodoList
    n.ハル さぎょう 展開 しちゅう もつ おんど 後. 地図 利用する. 複数 TodoListItem 構成部品 フォーマットコピー
  • // App.js
    
    import React, { useCallback, useRef, 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,
            },
        ]);
    
        // useRef로 컴포넌트에서 사용할 변수를 만드는 이유는 
        // id값은 렌더링되는 정보가 아니기 때문이다.
        // 즉 화면에 보이지도 않고 이 값이 바뀐다고해서 
        // 컴포넌트가 다시 렌더링 될 이유가 없기 때문이다.
        // 단순히 새로운 항목을 만들때 참조되는 값이다.
        const nextId = useRef(4);
        const onInsert = useCallback(
            (text) => {
                const todo = {
                    id: nextId.current,
                    text,
                    checked: false,
                };
                setTodos(todos.concat(todo));
                nextId.current += 1;
            },
            [todos],
        );
        const onDelete = useCallback(
            (id) => {
                const nextTodos = todos.filter((todo) => todo.id !== id);
                setTodos(nextTodos);
            },
            [todos],
        );
        const onChecked = useCallback(
            (id) => {
                const nextTodos = todos.filter((todo) => {
                    if (todo.id === id) todo.checked = !todo.checked;
                    return todo;
                });
                setTodos(nextTodos);
            },
            [todos],
        );
    
        return (
            <TodoTemplate>
                <TodoInsert onInsert={onInsert} />
                {/* TodoList props로 전달 -> TodoList에서 이 값을 받아온 후
                TodoListItem으로 변환하여 렌더링 하도록 설정한다.*/}
                <TodoList todos={todos} onDelete={onDelete} onChecked={onChecked} />
            </TodoTemplate>
        );
    };
    
    export default App;
    TodoTemplate
    // TodoTemplate.js
    
    import React from 'react';
    import './TodoTemplate.scss';
    
    const TodoTemplate = ({ children }) => {
        return (
            <div className="TodoTemplate">
                <div className="app-title">일정 관리</div>
                <div className="content">{children}</div>
            </div>
        );
    };
    
    export default TodoTemplate;
    
    // TodoTemplate.scss
    
    .TodoTemplate {
        width: 512px;
        margin-left: auto;
        margin-right: auto;
        margin-top: 6rem;
        border-radius: 4px;
        overflow: hidden;
    }
    .app-title {
        background: #22b8cf;
        color: white;
        height: 4rem;
        font-size: 1.5rem;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .content {
        background: white;
    }
    TodoInsert
    // TodoInsert.js
    
    import React, { useCallback, useState } from 'react';
    import { MdAdd } from 'react-icons/md';
    import './TodoInsert.scss';
    const TodoInsert = ({ onInsert }) => {
        const [value, setValue] = useState('');
        const onChange = useCallback((e) => {
            setValue(e.target.value);
        }, []);
    
        const onSubmit = useCallback(
            (e) => {
                onInsert(value); //현재 useState를 통해 관리하고 있는 value값을 인자로 전달
                setValue(''); //value값 초기화
    
                e.preventDefault();
            },
            [onInsert, value],
        );
    
        return (
            <form className="TodoInsert" onSubmit={onSubmit}>
                <input placeholder="할일 입력" value={value} onChange={onChange} />
                <button type="submit">
                    <MdAdd />
                </button>
            </form>
        );
    };
    
    export default TodoInsert;
    .TodoInsert {
        display: flex;
        background: #495057;
        input {
            // 기본 스타일 초기화
            background: none;
            outline: none;
            border: none;
            padding: 0.5rem;
            font-size: 1.2rem;
            line-height: 1.5;
            color: white;
            &::placeholder {
                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: #adb5db;
            }
        }
    }
    TodoList
    // TodoList.js
    
    import React from 'react';
    import TodoListItem from './TodoListItem';
    import './TodoList.scss';
    
    const TodoList = ({ todos, onDelete, onChecked }) => {
        return (
            <div className="TodoList">
                {todos.map((todo) => (
                    <TodoListItem
                        todo={todo}
                        key={todo.id}
                        onDelete={onDelete}
                        onChecked={onChecked}
                    />
                ))}
            </div>
        );
    };
    
    export default TodoList;
    // TodoList.scss
    
    .TodoInsert {
        display: flex;
        background: #495057;
        input {
            // 기본 스타일 초기화
            background: none;
            outline: none;
            border: none;
            padding: 0.5rem;
            font-size: 1.2rem;
            line-height: 1.5;
            color: white;
            &::placeholder {
                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: #adb5db;
            }
        }
    }
    TodoListItem.js
    import React, { useCallback } from 'react';
    import cn from 'classnames';
    import './TodoListItem.scss';
    import {
        MdCheckBoxOutlineBlank,
        MdCheckBox,
        MdRemoveCircleOutline,
    } from 'react-icons/md';
    
    const TodoListItem = ({ todo, onDelete, onChecked }) => {
        const { id, text, checked } = todo;
        const remove = useCallback(
            (e) => {
                onDelete(id);
            },
            [onDelete, id],
        );
        const check = useCallback(
            (e) => {
                onChecked(id);
            },
            [onChecked, checked],
        );
        return (
            <div className="TodoListItem">
                <div className={cn('checkbox', { checked })} onClick={check}>
                    {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
                    <div className="text">{text}</div>
                </div>
                <div className="remove" onClick={remove}>
                    <MdRemoveCircleOutline />
                </div>
            </div>
        );
    };
    
    export default TodoListItem;
    // TodoListItem.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;
        &:hover {
            color: #ff8787;
        }
        & + & {
            border-top: 1px solid #dee2e6;
        }
    }