본문 바로가기

React

Styled Router Redux 구현

LV2

✅1. IP 주소를 입력하세요.
https://react-lv2-router-redux-styled.vercel.app/

✅2. GitHub 주소를 입력하세요.
https://github.com/webcreastory/LV2_RouterReduxStyled.git

 

✅ 3. 페어와 LV1 개인 과제 리뷰한 깃허브 이슈 링크를 입력하세요.(페어 김서연)
https://github.com/webcreastory/16_WEEK_LV1/issues/1#issue-2057050011

 

✅ 4. 상세 페이지 이동을 위해 react-router-dom 라이브러리를 사용하셨을 거예요, CRA 라우팅이란 무엇인가요?
사용자가 URL을 변경할 때마다 다른 React 컴포넌트를 렌더링하여 화면을 업데이트할 수 있습니다.  CRA는 Create React App의 약자로, React 애플리케이션을 쉽게 설정하고 빠르게 시작할 수 있도록 도와주는 도구입니다. CRA는 기본적으로 React 애플리케이션을 위한 구조와 설정을 제공하며, 이는 React 애플리케이션을 만들기 위해 별도의 설정 없이 바로 시작할 수 있도록 도와줍니다.

CRA에서 제공하는 라우팅 시스템은 React 애플리케이션에서 다양한 페이지 간의 이동 및 네비게이션을 관리하기 위한 기능입니다. 이 라우팅 시스템은 react-router-dom 라이브러리를 사용하여 구현됩니다. react-router-dom은 React 애플리케이션에서 라우팅을 처리하기 위한 도구로, <BrowserRouter>, <Route>, <Link> 등의 컴포넌트들을 제공하여 페이지 간의 이동과 URL 관리를 용이하게 해줍니다.

이러한 CRA의 라우팅 시스템을 사용하면 여러 페이지 간의 이동 및 URL 경로 관리, 그리고 사용자의 상호작용에 따른 뷰 전환 등을 쉽게 구현할 수 있습니다. 이를 통해 React 애플리케이션에서 다양한 화면을 효과적으로 관리하고 구현할 수 있게 됩니다.


5. Redux를 사용하여 애플리케이션의 상태 관리를 하셨을 것입니다. 어떤 상태들을 Redux로 관리하셨나요? 그 상태 값을 Redux를 통해 관리함으로 얻은 이점은 무엇이었나요?

애플리케이션의 전역 상태: 사용자 정보, 애플리케이션 설정, 테마, 언어 등과 같이 여러 컴포넌트 간에 공유되는 상태를 Redux 스토어에서 관리합니다.

비동기 작업 상태: 네트워크 요청, 데이터 가져오기, 상태 업데이트 등과 같은 비동기 작업의 상태를 효과적으로 관리합니다. 예를 들어, 로딩 상태, 성공 또는 실패 상태 등을 Redux로 관리하여 UI에 반영할 수 있습니다.

UI 상태: 모달의 열림/닫힘 상태, 사용자 입력 상태 등의 UI 관련 상태를 Redux를 통해 관리하여 여러 컴포넌트에서 이를 일관되게 조작하고 업데이트할 수 있습니다.

Redux를 사용하여 상태를 관리하면 다음과 같은 이점이 있습니다:
단일 소스 오브 트루스(Single Source of Truth): Redux 스토어는 애플리케이션의 전역 상태를 보유하므로, 데이터가 하나의 장소에서 관리되어 일관성을 유지할 수 있습니다.

상태 변화 추적: Redux는 상태의 변화를 예측 가능한 방식으로 추적하고, 이에 따라 상태 변화에 따른 액션과 리듀서를 사용하여 상태를 업데이트할 수 있습니다.

디버깅 용이성: Redux는 브라우저 확장 프로그램인 Redux DevTools와의 통합을 통해 상태 변화를 실시간으로 모니터링하고 디버깅할 수 있는 기능을 제공합니다.

컴포넌트 간 데이터 공유 및 전달: Redux는 여러 컴포넌트 간에 데이터를 쉽게 공유하고 전달할 수 있도록 도와줍니다.

Redux를 사용하면 복잡한 애플리케이션에서 상태를 효과적으로 관리하고, 상태 변화를 예측 가능한 방식으로 추적하여 유지보수성을 높일 수 있습니다.

✅ 6. Redux의 reducer 함수는가 애플리케이션 로직과 어떻게 상호작용하는지 설명해 주세요.

Redux의 Reducer 함수는 애플리케이션의 상태 변화를 다루는 중요한 부분입니다. Reducer 함수는 이전 상태와 액션을 받아 새로운 상태를 반환하는 함수입니다. 주로 switch 문을 활용하여 각 액션 유형에 따라 상태를 어떻게 변경할지 결정합니다.

Reducer 함수의 역할:
이전 상태와 액션 처리: Reducer 함수는 현재 상태(state)와 액션(action)을 받아들여 이전 상태를 기반으로 어떻게 상태를 변경할지 정의합니다.
불변성 유지: Redux에서 상태(state)는 불변성을 유지해야 합니다. Reducer 함수는 기존의 상태를 변경하지 않고, 새로운 상태 객체를 생성하여 반환합니다.
애플리케이션 로직 적용: Reducer는 특정 액션에 대한 로직을 구현합니다. 예를 들어, 로그인 액션이 발생하면 이전 상태에 로그인된 사용자 정보를 추가하는 등의 작업을 수행할 수 있습니다.

Reducer 함수(순수 함수여야 함)특성:
부수 효과 없음: 외부 데이터 변경이나 API 호출 등의 부수 효과를 일으키지 않습니다.
동일한 입력에 대해 항상 동일한 출력 반환: 같은 상태와 액션을 주면 항상 동일한 결과를 반환합니다.
Redux에서 Reducer 함수는 여러 개의 작은 Reducer 함수로 분할될 수 있습니다. 이들 작은 Reducer 함수는 각각의 서브 상태에 대해 책임을 지고, 이들을 결합하여 전체 상태를 관리합니다. 이러한 방식으로 Reducer 함수는 애플리케이션의 로직과 상태 변화를 관리하며, Redux 스토어에서 상태를 업데이트하는 핵심 역할을 수행합니다.


✅ 7. CSS in JS 라이브러리 중 하나인 "styled component"를 사용했을 때의 이점은 많습니다. CSS in JS가 제공하는 이점을 두 가지만 설명해 주세요.

 

컴포넌트 기반 스타일링:
CSS-in-JS 라이브러리를 사용하면 스타일을 컴포넌트와 함께 정의할 수 있습니다. 이는 컴포넌트 단위로 스타일을 작성하고 적용할 수 있는 장점을 제공합니다. 각 컴포넌트에 스타일을 지정하여 해당 컴포넌트가 어떤 스타일을 가지는지 명확하게 파악할 수 있습니다. 이는 컴포넌트 기반 개발 방식과 잘 어울리며, 스타일과 컴포넌트가 밀접하게 결합되어 UI의 일관성을 유지하고 유지보수를 용이하게 합니다.

동적 스타일링 및 조건부 스타일 적용:
CSS-in-JS를 사용하면 JavaScript를 사용하여 동적으로 스타일을 변경하거나 조건에 따라 스타일을 적용할 수 있습니다. 이는 props 값을 활용하여 스타일을 동적으로 조작하거나 상태에 따라 스타일을 변경할 수 있는 유연성을 제공합니다. 예를 들어, 조건부로 클래스를 추가하거나 스타일을 조절하는 데 용이합니다.

이러한 이점들은 CSS-in-JS 라이브러리를 사용하여 코드의 가독성을 높이고 유지보수성을 향상시키며, 컴포넌트 기반의 개발 철학을 따르는데 큰 도움이 됩니다. 추가적으로, CSS-in-JS는 스타일 충돌과 같은 일반적인 CSS의 문제를 방지하고, 번들링 시에 필요한 스타일만 포함하여 성능을 최적화하는 등의 장점도 갖고 있습니다.

✅ 8. useEffect hook을 사용한 부분이 있다면, 왜 사용해야 했고, 해당 hook이 언제 실행되는지 설명해주세요.

렌더링 이후 작업 수행: 함수형 컴포넌트는 렌더링될 때마다 함수 전체가 재실행됩니다. useEffect를 사용하면 컴포넌트가 렌더링된 후에 실행되므로, 컴포넌트가 화면에 렌더링된 이후에 처리해야 하는 작업들을 수행할 수 있습니다.

부수 효과 관리: useEffect 안에서 비동기 작업, 구독 설정, 외부 데이터 가져오기 등과 같은 부수 효과를 관리할 수 있습니다. 컴포넌트가 마운트되었을 때, 언마운트되었을 때, 특정 상태나 props가 변경될 때 등 실행 시점을 제어할 수 있습니다.

useEffect의 실행 시점은 다음과 같이 결정됩니다:
컴포넌트 마운트 시: useEffect의 첫 번째 매개변수에 전달된 함수는 컴포넌트가 마운트될 때 한 번 실행됩니다. 이 때, 렌더링 후에 해당 함수가 실행됩니다.

의존성 배열에 명시된 값 변경 시: useEffect의 두 번째 매개변수로 전달된 의존성 배열(dependency array)에 명시된 값이 변경될 때마다 해당 함수가 실행됩니다. 의존성 배열이 빈 배열인 경우(의존성이 없는 경우), useEffect는 컴포넌트가 마운트될 때 한 번만 실행되고, 언마운트될 때 정리(clean-up) 함수가 호출됩니다.

useEffect는 컴포넌트의 라이프사이클과 관련하여 동작하며, 부수 효과를 효과적으로 관리하고 원하는 시점에 실행할 수 있도록 도와줍니다.

 

 

 

GitHub - webcreastory/LV2_RouterReduxStyled

Contribute to webcreastory/LV2_RouterReduxStyled development by creating an account on GitHub.

github.com

 

// pages 폴더 > Detail.jsx

import React from "react";
import DetailBox from "../redux/components/DetailBox";

function Detail() {
  return <DetailBox />;
}

export default Detail;
// pages 폴더 > Main.jsx

import React from "react";
import Todo from "../redux/components/Todo";

const Main = () => {
  return <Todo />;
}

export default Main;
// redux 폴더 > components 폴더 > Button.jsx 

import React from 'react';
// 버튼 컴포넌트(함수형 컴포넌트)
// Button이라는 이름의 함수형 컴포넌트 선언
// 이 컴포넌트는 주로 버튼 역할을 하며, 두 개의 속성(props)을 받아들임
// clickAddButtonHandler: 이는 버튼이 클릭되었을 때 실행될 이벤트 핸들러 함수
// children: 이는 버튼 내에 포함될 내용(일반적으로 텍스트 또는 다른 React 엘리먼트)
const Button = ({ clickAddButtonHandler, children }) => {
    // 이 부분은 컴포넌트의 렌더링을 정의
    // <button> HTML 엘리먼트 반환
    // onClick={clickAddButtonHandler}는 버튼이 클릭되었을 때 실행될 이벤트 핸들러를 정의
    // clickAddButtonHandler로 전달된 함수가 클릭 이벤트에 바인딩되어 해당 함수가 실행됨
    // {children}: 이는 버튼 내에 포함될 내용을 나타냄(버튼 내의 텍스트로 표시됨)
    return <button onClick={clickAddButtonHandler}>{children}</button>;
}; // =  Button 컴포넌트를 정의하여, 버튼을 렌더링하고 클릭 이벤트에 대한 핸들러를 받아 처리할 수 있는 컴포넌트

// 정의한 Button 컴포넌트를 외부에서 사용할 수 있도록 내보낸다.
export default Button;
// redux 폴더 > components 폴더 > DetailBox.jsx

import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';
import styled from 'styled-components';

function Detail() {
    const navigate = useNavigate();
    const params = useParams();

    const todos = useSelector((state) => {
        return state.todos;
    });

    const todo = todos.find((item) => {
        return item.id === params.id;
    });

    return (
        <HomeBackground>
            <HomeContainer>
                <DetailBox>
                    <h1>상세 페이지 정보</h1>
                    <h3> 번호 : {todo.id}</h3>
                    <h3> 제목 : {todo.title}</h3>
                    <h3> 내용 : {todo.text}</h3>
                    <BackButton
                        onClick={() => {
                            navigate('/');
                        }}
                    >
                        Home
                    </BackButton>
                </DetailBox>
            </HomeContainer>
        </HomeBackground>
    );
}

const HomeBackground = styled.div`
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: -1;
    background-image: linear-gradient(45deg, rgb(51 43 43 / 75%), rgb(20 19 20 / 61%)),
        url('https://miro.medium.com/v2/resize:fit:1400/1*QDQvlCg420lzRElCK4AYhw.png');
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
`;
const HomeContainer = styled.div`
    width: 1200px;
    margin: 0px auto 0px auto;
`;
const BackButton = styled.button`
    width: 280px;
    height: 65;
    padding: 10px;

    background-color: red;

    border: none;
    border-radius: 5px;

    color: white;
    font-size: 22px;
    font-weight: 800;

    &:hover {
    font-size: 23px;
    font-weight: 900;
    background-color: rgb(71, 163, 182);
    };
`;
const DetailBox = styled.div`
    width: 750px;
    height: 550px;
    border: 5px solid white;
    border-radius: 10px;

    padding: 40px;
    margin-top: 80px;

    color: white;
    font-size: 22px;
    font-weight: 700;

    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
`;

export default Detail;
// redux폴더 > components폴더 > Todo.jsx

 import React, { useState } from 'react'; // React 라이브러리를 사용하기 위해서는 항상 React를 임포트해야 함
import User from './User';
import styled from 'styled-components';
import { useDispatch } from 'react-redux';
import { addTodo } from '../modules/todos';
import { useSelector } from 'react-redux';


// App이라는 함수형 컴포넌트 : React 애플리케이션의 진입점(entry point)
// 이 컴포넌트는 <Todo></Todo>라는 JSX를 반환 (Todo 컴포넌트를 렌더링하는 역할을 함)
function Todo() {
    const dispatch = useDispatch();

    const todos = useSelector((state) =>{
        return state.todos;
    })
    // 초기 상태 설정
    // useState 훅(Hook)을 사용하여 컴포넌트의 상태를 초기화
    // useState는 배열 형태의 반환값을 가지며
    //--- 첫번째 원소 users(상태 변수의 이름)는 상태값 자체, 두번째 원소 setUsers는 해당 상태를 업데이트하는 함수
    const [users, setUsers] = useState([
        // 초기 상태로 지정된 값은 배열
        // 각 원소는 하나의 Todo를 나타내는 객체
        // 객체에는 id(고유 식별자), title, text, isDine 속성
        { id: 1, title: '[과제] React Lv.1', text: 'My Todo List 제출', isDone: false },
        { id: 2, title: '[강의] 리액트 입문주차', text: '2회독_개발 블로그 정리', isDone: true },
    ]); // = 이 코드는 초기 화면에 렌더링될 때 users 상태가 초기화되고 해당 상태는 title과 text라는 2개의 Todo 객체를 가지고 있음
    // 이후에 이 상태를 업데이트하거나 활용하여 React 컴포넌트를 동적으로 변경할 수 있음

    // React 함수형 컴포넌트에서 useState 훅을 사용하여 2개의 상태 변수를 선언
    // 첫번째 상태 변수인 title을 선언하고 초기값을 빈 문자열로 설정(title은 현재 상태의 값, setTitle 함수를 사용해 이 상태를 업데이트)
    // 두번째 상태 변수인 text을 선언하고 초기값을 빈 문자열로 설정(text는 현재 상태의 값, setText 함수를 사용해 이 상태를 업데이트)
    const [title, setTitle] = useState('');
    const [text, setText] = useState('');
    // = 이렇게 선언된 상태 변수들은 주로 사용자의 입력을 받거나, 컴포넌트 내에서의 상태를 관리하는 데 사용

    // 제목 입력값 변경 핸들러
    // React 함수형 컴포넌트에서 사용되는 event 핸들러 함수
    // titleChangeHandler라는 이름의 '함수형 변수'를 선언
    // 주로 입력 필드에서 발생하는 event(일반적으로 입력 내용이 변경될 때 발생하는 'onChange' event)를 처리
    const titleChangeHandler = (event) => {
        // 화살표 함수 문법을 사용하여 이벤트 핸들러 함수 정의(이벤트(event) 객체를 받아들이는 매개변수)
        setTitle(event.target.value); // setTitle 함수 호출(React 컴포넌트에서 title 상태 업데이트) ->
        // event.target.value 값을 setTitle 함수에 전달하여 컴포넌트의 title 상태 업데이트
    }; // event.target은 이벤트가 발생한 HTML 요소(주로 입력 필드)/.value는 해당 요소의 현재 값, 즉 사용자가 입력한 내용을 나타냄
    // = 주로 사용자가 입력 필드에 텍스트를 입력할 때마다 호출되어, 입력된 텍스트를 컴포넌트의 title 상태로 업데이트하는 역할
    // = 입력된 값을 상태로 관리하고, 필요에 따라 화면을 다시 렌더링하여 반영

    // 내용 입력값 변경 핸들러
    //textChangeHandler라는 이름의 함수형 변수를 선언
    // 주로 입력 필드에서 발생하는 이벤트(일반적으로 입력 내용이 변경될 때 발생하는 'onChange' 이벤트) 처리
    const textChangeHandler = (event) => {
        // 화살표 함수 문법을 사용하여 이벤트 핸들러 함수 정의
        // 매개변수 event는 이벤트 객체를 받아들이는 매개변수
        setText(event.target.value); // event.target.value는 이벤트가 발생한 HTML 요소(주로 입력 필드)의 현재 값,
        // 즉 사용자가 입력한 내용을 나타냄(이 값을 setText 함수에 전달하여 컴포넌트의 text 상태를 업데이트)
    }; // 사용자가 입력 필드에 텍스트를 입력할 때마다 호출되어, 입력된 텍스트를 컴포넌트의 text 상태로 업데이트하는 역할

    // <추가> 버튼 클릭시 카드 추가하기
    // clickAddButtonHandler 함수가 호출될 때마다, 새로운 사용자 정보를 만듦
    const clickAddButtonHandler = () => {
        const newUser = {
            //  newUser 객체를 생성하는데, 이 객체에는 다음과 같은 속성 포함(id, title, text, isDone)
            id: users.length + 1, // users 배열의 길이에 1을 더한 값으로 설정
            title: title, // title 변수에서 가져온 값으로 설정
            text: text, // text 변수에서 가져온 값으로 설정
            isDone: false, // 새로운 사용자의 상태를 나타냄(아직 완료되지 않았다는 것을 의미)
        };
        // setUsers 함수를 호출하여 users 배열 업데이트
        // setUsers([...users, newUser]);
        
        // 기존 users 배열과 새로 생성된 newUser 객체를 합친 새로운 배열을 생성하여 업데이트

        // <추가> 버튼 클릭 후 입력값 초기화
        //  입력 필드의 값을 변경한 후에 해당 값을 초기화
        dispatch(addTodo(title, text))
        setTitle('');
        setText('');
    };

    // Working 섹션에서 <삭제> 버튼 클릭 핸들러
    // clickRemoveButtonHandler 함수는 id라는 매개변수를 받음
    // 사용자가 지정한 id를 가진 항목을 삭제하기 위한 것
    const clickRemoveButtonHandler = (id) => {
        // filter 메서드를 사용하여 users 배열에서 조건을 만족하는 요소들만 새로운 배열인 newUsers에 포함
        // filter 함수는 각각의 user에 대해 주어진 조건을 확인하고,
        // user.id가 함수에 전달된 id와 일치하지 않는 요소들만 선택하여 새로운 배열을 생성
        // id와 일치하지 않는 사용자만을 남기고 필터링
        const newUsers = users.filter((user) => user.id !== id);
        // setUsers 함수를 호출하여 상태(state)를 업데이트
        // newUsers 배열은 이전 users 배열에서 특정 id를 가진 사용자를 제외한 새로운 배열
        // 새로운 사용자 목록으로 설정하여 이전 목록에서 해당 ID를 가진 사용자를 삭제
        setUsers(newUsers);
    };
    // <완료> 또는 <취소> 버튼 클릭시 상태 변경
    // toggleStatusFunction 함수는 id라는 매개변수를 받음
    const toggleStatusFunction = (id) => {
        // map 함수를 사용하여 users 배열의 각 요소를 새로운 배열인 updatedUsers로 변환
        // map() 함수는 배열의 각 요소에 대해 주어진 함수를 호출하고, 그 함수가 반환하는 결과를 모아서 새로운 배열을 생성
        // ----- 배열을 순회하면서 각 요소에 동일한 작업을 적용하여 새로운 배열을 만들 때 유용
        const updatedUsers = users.map((user) => {
            // 만약 user.id가 함수에 전달된 id와 일치한다면, 해당 사용자의 isDone 속성을 반전시켜서 변경
            if (user.id === id) {
                // 객체 전개 연산자(...)를 사용하여 객체를 복사하고, 해당 객체의 isDone 속성을 토글(반전)시킴
                // 반전 연산자 !를 사용하여 user.isDone의 값을 뒤집고, 해당 값을 가지고 새로운 객체를 생성하여 반환
                // 그렇지 않은 경우에는 기존의 사용자 정보를 그대로 반환
                return { ...user, isDone: !user.isDone };
            }
            return user;
        }); //setUsers 함수를 호출하여 상태(state)를 업데이트
        setUsers(updatedUsers);
        // updatedUsers 배열은 이전 users 배열을 기반으로
        // --- 특정 id를 가진 사용자의 isDone 값을 토글(반전)시킨 새로운 배열
    };

    // Done 섹션에서 <삭제> 버튼 클릭 핸들러
    // clickRemoveDoneButtonHandler 함수는 id라는 매개변수를 받음
    // 특정 조건을 충족하는 사용자(isDone이 true인 사용자)를 제외하고 새로운 배열을 만듦
    const clickRemoveDoneButtonHandler = (id) => {
        // filter 함수를 사용하여 users 배열에서 조건을 만족하는 요소들만 새로운 배열인 newUsers에 포함시킴
        const newUsers = users.filter((user) => user.id !== id);
        // newUsers 배열은 이전 users 배열에서 특정 id를 가진 사용자를 제외한 새로운 배열
        // = id에 해당하는 사용자를 제거하고, 해당 사용자가 완료된 작업을 나타내는 isDone이 true인 경우에만 동작
        setUsers(newUsers);
    };

    // JSX(JavaScript XML, React에서 UI를 작성하기 위해 사용되는 JS 확장 문법)로 작성된 코드
    // 1. Todo List 구성 요소:
    // clickRemoveDoneButtonHandler, clickAddButtonHandler, titleChangeHandler, textChangeHandler,
    // toggleStatusFunction 함수들과 함께 <Button>과 <User> 컴포넌트를 사용하여 Todo List를 구성

    // 2. 화면 레이아웃 구성:
    // home-background, home-container, header, input-box, Working-body 등의 CSS 클래스를 사용하여 UI 스타일링
    // 헤더에는 My Todo List와 React라는 두 개의 텍스트 구성

    // 3. 두 부분으로 나누어진 Todo 리스트:
    // Working 부분은 isDone이 false인 항목들을 보여주고 있고,
    // Done 부분은 isDone이 true인 항목들을 보여줌
    // 두 부분에서 각각 filter 함수를 사용하여 users 배열을 필터링하고, 해당하는 부분에 맞는 항목들만을 보여줌
    // map 함수를 사용하여 각각의 Todo 항목을 User컴포넌트로 변환하고, 해당 컴포넌트들을 UI에 렌더링 함

    return (
        <HomeBackground>
            <HomeContainer>
                <Header>
                    <span>My Todo List</span>
                    <span>React</span>
                </Header>

                <InputBox>
                    제목 &nbsp;
                    <InputStyle value={title} onChange={titleChangeHandler} />
                    내용 &nbsp;
                    <InputStyle value={text} onChange={textChangeHandler} />
                    <AddButton onClick={clickAddButtonHandler}>추가하기</AddButton>
                </InputBox>

                <TitleH1>🔥 Working</TitleH1>
                <BodyStyle>
                    {todos
                        .filter((item) => !item.isDone)
                        .map(function (item) {
                            return (
                                <User
                                    key={item.id}
                                    item={item}
                                    removefunction={clickRemoveButtonHandler}
                                    toggleStatusFunction={toggleStatusFunction}
                                />
                            );
                        })}
                </BodyStyle>

                <TitleH1>👍 Done</TitleH1>
                <BodyStyle>
                    {todos
                        .filter((item) => item.isDone)
                        .map(function (item) {
                            return (
                                <User
                                    key={item.id}
                                    item={item}
                                    removefunction={clickRemoveDoneButtonHandler}
                                    toggleStatusFunction={toggleStatusFunction}
                                />
                            );
                        })}
                </BodyStyle>
            </HomeContainer>
        </HomeBackground>
    );
}

const HomeBackground = styled.div`
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: -1;
    background-image: linear-gradient(45deg, rgb(51 43 43 / 75%), rgb(20 19 20 / 61%)),
        url('https://miro.medium.com/v2/resize:fit:1400/1*QDQvlCg420lzRElCK4AYhw.png');
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
`;
const HomeContainer = styled.div`
    width: 1200px;
    margin: 0px auto 0px auto;
`;
const Header = styled.div`
    height: 50px;
    width: 1100px;

    padding: 10px;
    margin-bottom: 10px;

    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;

    font-size: 50px;
    font-weight: 700;
    color: rgb(18, 78, 89);
`;
const InputBox = styled.div`
    background-color: rgba(44, 40, 40, 0.921);
    border-radius: 6px;

    height: 100px;
    width: 1100px;

    padding-left: 30px;
    padding-right: 30px;

    color: white;
    font-size: 25px;
    font-weight: 800;

    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
`;
const InputStyle = styled.input`
    height: 40px;
    width: 280px;

    margin-right: auto;
    padding-left: 10px;

    border-radius: 7px;
    border: none;

    font-size: 18px;
    font-weight: 600;
`;
const AddButton = styled.button`
    width: 180px;
    height: 45;
    padding: 10px;

    background-color: rgb(71, 163, 182);

    border: none;
    border-radius: 5px;

    color: white;
    font-size: 20px;
    font-weight: 800;

    &:hover {
    font-size: 22px;
    font-weight: 900;
    background-color: red;
    };
`;
const TitleH1 = styled.h1`
    margin-right: 1000px;
    margin-bottom: 15px;

    font-size: 30px;
    font-weight: 800;
    color: white;
`;
const BodyStyle = styled.div`
    display: flex;
    flex-wrap: wrap;
    gap: 12px;

    padding-left: 30px;
`;
// App 컴포넌트를 모듈의 기본으로 내보냄
export default Todo;
// App 컴포넌트는 Todo 컴포넌트를 렌더링하고,
// 애플리케이션의 최상위 레벨에 위치하여
// 각 컴포넌트들을 조합하고 화면에 보여주는 역할을 함
// redux 폴더 > components폴더 > User.jsx

// React 라이브러리를 현재 파일에서 사용하겠다고 선언
import React from 'react';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { removeTodo, switchTodo } from '../modules/todos';

// User라는 함수형 컴포넌트
// ES6의 비구조화 할당을 사용하여 컴포넌트의 props를 바로 받아옴
const User = ({ item }) => {
    const dispatch = useDispatch();
    return (
        // React에서 리스트를 렌더링할 때 각 요소에 고유한 key를 지정해야 함
        // item.id를 키로 사용
        // item.isDone이 true인 경우에만 done 클래스가 추가됨(완료된 항목은 스타일링을 위해 done 클래스를 받음)
        // item 객체에 있는 title과 text 값을 출력
        // <div key={item.id} className={`todo-container ${item.isDone ? 'done' : ''}`}>
        <TodoContainer key={item.id} className={item.isDone ? 'done' : ''}>
            <StLink to={`/${item.id}`} key={item.id}>
                상세보기
            </StLink>
            <br />
            {item.title}
            <br />
            {item.text}
            <TodoDiv>
                <BtncontainerBtn onClick={() => dispatch(removeTodo(item.id))}>삭제하기</BtncontainerBtn>
                <BtncontainerBtn onClick={() => dispatch(switchTodo(item.id))}>
                    {item.isDone ? '취소' : '완료'}
                </BtncontainerBtn>
            </TodoDiv>
        </TodoContainer>
        // 버튼을 클릭하면 removefunction이 호출되고, 해당 item의 id가 전달 -> 이를 통해 해당 아이템이 삭제되는 함수를 실행
    ); // 버튼 텍스트를 item.isDone의 상태에 따라 다르게 표시
    // item.isDone이 true라면 '취소'라는 텍스트가 보이며, 클릭 시 toggleStatusFunction이 호출
}; // item.isDone이 false라면 '완료'라는 텍스트가 보이고, 클릭 시 toggleStatusFunction이 호출

const TodoContainer = styled.div`
    width: 350px;
    height: 250px;
    border: 5px solid white;
    border-radius: 10px;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    font-size: 20px;
    font-weight: 800;
    color: white;
`;
const BtncontainerBtn = styled.button`
    width: 100px;
    height: 40;
    padding: 10px;
    margin: 10px;
    margin-top: 10px;

    background-color: red;

    border: none;
    border-radius: 5px;

    color: white;
    font-size: 16px;
    font-weight: 900;

    &:hover {
        background-color: black;
        font-size: 16px;
    }
`;
const TodoDiv = styled.div`
    margin-top: 30px;
`;
const StLink = styled(Link)`
    text-decoration: none;
    font-size: 20px;
    font-weight: 900;
    color: red;

    &:hover {
        cursor: pointer;
        color: black;
    }
`;

export default User;
// User 컴포넌트는 각 Todo 항목을 렌더링하고, 해당 항목의 삭제와 상태 변경을 담당하는 버튼을 보여줌
// 설정된 item prop으로부터 해당 항목의 정보를 받아와 UI로 표시하고,
// removefunction과 toggleStatusFunction을 호출하여 해당 항목의 삭제 및 상태 변경을 처리
// config폴더 > configStore.js

import { createStore } from "redux";
import { combineReducers } from "redux";
import todos from "../modules/todos";

// 1. create rootReducer with reducers
const rootReducer = combineReducers({
  todos,
});

// 2. create store
const store = createStore(rootReducer);

// 3. export
export default store;
// modules 폴더 > todos.js

import React from "react";
import { v4 as uuidv4 } from "uuid";

// action items
const ADD_TODO = "ADD_TODO";
const REMOVE_TODO = "REMOVE_TODO";
const SWITCH_TODO = "SWITCH_TODO";

/**
 * 메서드 개요 : todo 객체를 입력받아, 기존 todolist에 더함
 * @param {todo 객체} payload
 * @returns
 */
export const addTodo = (title, text) => {
  // console.log("addTodo", payload);
  return {
    type: ADD_TODO,
    payload: {
      id:uuidv4(),
      title,
      text,
      isDone: false,
    }
  }
};

/**
 * 메서드 개요 : todo의 id를 입력받아, 일치하는 todolist를 삭제
 * @param {todo의 id} payload
 * @returns
 */
export const removeTodo = (id) => {
  return {
    type: REMOVE_TODO,
    payload: id,
  }
};

/**
 * 메서드 개요 : todo의 id를 입력받아, 일치하는 todo 아이템의 isDone을 반대로 변경
 * @param {*} payload
 * @returns
 */
export const switchTodo = (id) => {
  return {
    type: SWITCH_TODO,
    payload: id,
  };
};

// initial states
const initialState = [
  {
    id: uuidv4(),
    title: "리액트 입문-숙련-심화",
    text: "[STUDY] 리액트 다루는 기술",
    isDone: false,
  },
  {
    id: uuidv4(),
    title: "알고리즘 코드카타",
    text: "프로그래밍 익숙해지기",
    isDone: true,
  },
];

// reducers
const todos = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO: // 기존의 배열에 입력받은 객체를 더함
      return [...state, action.payload];
    case REMOVE_TODO: // 기존의 배열에서 입력받은 id의 객체를 제거(filter)
      return state.filter((item) => item.id !== action.payload);
    case SWITCH_TODO: // 기존의 배열에서 입력받은 id에 해당하는 것만 isDone을 반대로 변경(아니면 그대로 반환)
      return state.map((item) => {
        if (item.id === action.payload) {
          return { ...item, isDone: !item.isDone };
        } else {
          return item;
        }
      });
    default:
      return state;
  }
};

// export
export default todos;
// shared 폴더 > Router.js

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Main from "../pages/Main";
import Detail from "../pages/Detail";

const Router = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Main />} />
        <Route path="/:id" element={<Detail />} />
      </Routes>
    </BrowserRouter>
  );
};

export default Router;
// App.jsx

import React from "react";
import Router from "./shared/Router";

const App = () => {
  return <>
  <Router />
  </>;
};

export default App;
// index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import store from "./redux/config/configStore";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

reportWebVitals();

'React' 카테고리의 다른 글

ref : DOM에 이름 달기  (0) 2024.01.05
컴포넌트 분리's 정석  (0) 2024.01.05
React : Button Modal Input Select 기본기능 구현  (1) 2024.01.05
컴포넌트 (Component)  (1) 2024.01.04
Virtual DOM, props, state  (1) 2023.12.31