프로젝트/Task 어플리케이션

1117 리액트를 이용한 태스크 정리 앱 만들기 - Vite, Redux 셋팅, Redux Hooks 타입 지정, 전역 스타일 생성

thinktank911 2025. 11. 17. 12:28

vite로 리액트 프로젝트 생성

Vite란?

모던 웹 프로젝트 개발 환경에 초점을 맞춰 탄생한 빠르고 간결한 빌드 툴

다시 말해 프론트엔드 개발용 빌드 도구, 개발할 때 빠르게 화면에 반영되게 도와줌

전통적인 방식(예 : Webpack)보다 시작 속도와 변경 반영 속도가 훨씬 빠른 것이 특징이다.

 

사이트 참조 : https://ko.vite.dev/

 

Vite

Vite, 프런트엔드 개발의 새로운 기준

ko.vite.dev

 

깃허브 참조 : https://github.com/vitejs/vite

 

GitHub - vitejs/vite: Next generation frontend tooling. It's fast!

Next generation frontend tooling. It's fast! Contribute to vitejs/vite development by creating an account on GitHub.

github.com

 

React 프로젝트 생성 시 npx create-react-app과 Vite의 차이

 

1️⃣ 목적

항목 create-react-app Vite
개발용 빌드 도구 내장(Webpack 기반) 별도(Vite 자체 빌드)
속도 느림 (특히 시작 시, HMR 느림) 빠름 (Hot Module Replacement 즉시 반영)
최신 문법 지원 오래된 설정 때문에 최신 JS/TS 기능 바로 사용 불편 최신 ES 모듈 기반, 최신 JS/TS 바로 사용 가능

2️⃣ 설치 및 프로젝트 생성 속도

  • CRA (create-react-app)
    • 프로젝트 생성 후 node_modules 설치가 꽤 오래 걸림
    • Webpack과 Babel 등 여러 패키지를 함께 설치
    • 개발 서버 시작까지 시간이 비교적 김
  • Vite
    • 프로젝트 생성이 매우 빠름
    • 불필요한 번들링 없이 ES 모듈 기반 개발 서버 사용 → 변경 즉시 반영

3️⃣ 설정과 커스터마이징

항목CRAVite
Webpack 설정 숨겨져 있음 (react-scripts 내부) 설정 파일 vite.config.js 직접 사용 가능
설정 확장성 ejected 해야 함 → 복잡 플러그인으로 쉽게 확장
Typescript 지원 가능하지만 옵션 선택 필요 지원 내장, 바로 선택 가능

4️⃣ Hot Reload & Dev 서버

  • CRA: 변경 사항 반영 조금 느릴 수 있음, 전체 번들 재생성 발생
  • Vite: ES 모듈 기반이라 변경된 모듈만 즉시 반영, 거의 실시간

5️⃣ 한눈에 보는 비교

특징CRAVite
설치 속도 느림 매우 빠름
개발 서버 느림 매우 빠름
최신 JS/TS 지원 제한적 최신 기능 지원
설정 커스터마이징 어렵거나 ejected 필요 쉽고 유연
추천 시점 레거시 프로젝트나 안정성 우선 새 프로젝트, 빠른 개발 경험 원할 때

 

npm init vite로 리액트 프로젝트를 만들었다 =
Vite를 기반으로, 바로 개발할 수 있는 React 앱 뼈대를 만들어준 것

npm init vite

Project name:
│ ./
│
◇ Select a framework:
│ React
│
◇ Select a variant:
│ TypeScript
│
◇ Use rolldown-vite (Experimental)?:
│ No
│
◇ Install with npm and start now?
│ Yes

 

구조 생성

※ 상태관리 라이브러리 종류
: Redux, Mobx, Zustand, Recoill, React Context

컴포넌트 생성

리액트 컴포넌트 사용 : tsx
그냥 타입스크립트 사용 : ts

※ES7+ React/Redux/React-Native snippets : 익스텐션 다운로드
- 리액트 템플릿 자동완성 스니펫
- 참조 : https://github.com/r5n-labs/vscode-react-javascript-snippets/blob/HEAD/docs/Snippets.md

 

중요한 패키지들 설치하기

@reduxjs/toolkit
redux 
clsx : className 동적만들기
@vanilla-extract/css : 타입스크립트에서의 css 쓰기
@vanilla-extract/css-utils
@vanilla-extract/vite-plugin
react-icons : 리액트 아이콘
uuid : 유니크 아이디 만들기
react-beautiful-dnd : 드롭다운리스트 라이브러리
➡️ React 19에서 지원하지 않음. @hello-pangea/dnd 로 대체
react-redux

리덕스 사용을 위한 준비

리덕스 개념과 사용법 Slice와 reducer

2025.11.17 - [프레임워크\라이브러리/React] - [라이브러리] 상태관리 라이브러리 Redux

 

[라이브러리] 상태관리 라이브러리 Redux

리덕스란?Redux란 상태 관리 라이브러리(선택사항)로 State, Props 상태를 여러 컴포넌트와 공유할 때 쓴다.앱이 커지면 관리 힘들어지고 소스코드 지저분해지는데 이름 방지하고 효율적인 관리를

thinktank911.tistory.com

 

슬라이스(Slice) 만들기 - modalSlice.ts

modalSlice.ts

  • type만 import 시에는 import type으로 import 해야 한다.
    import { ITask } from "../../types";
    ➡️ import type { ITask } from "../../types";
import { createSlice } from "@reduxjs/toolkit";
import type { ITask } from "../../types";

type TModalState = {
    boardId: string;
    listId: string;
    task: ITask;
}
const initialState : TModalState = {
    boardId : "board-0",
    listId : "list-0",
    task: {
        taskId: "task-0",
        taskName: "task 0",
        taskDescription: "task description",
        taskOwner: "kyj"
    }
}
const modalSlice =  createSlice({
    name : 'modal',
    initialState,
    reducers: {

    }
})

export const ModalReducer = modalSlice.reducer;

 

ITask 타입만들기

  • types/intex.ts
export interface ITask {
    taskId: string;
    taskName: string;
    taskDescription: string;
    taskOwner: string;
}

 

export default : 경로만 맞으면 어떤 이름으로도 가져올 수 있다. {} 안 씀
export 차이 : export한 이름으로만 {} 안으로 가져와야 함

 

Reducer 컨바인 하기

  • 만들어놓은 모든 Slice의 Reducer를 한군데에 모아 reducer로 export하기
    • store/reducer/reducer.ts
import { boardsReducer } from "../slices/boardsSlice";
import { loggerReducer } from "../slices/loggerSlice";
import { ModalReducer } from "../slices/modalSlice";

const reducer = {
    logger: loggerReducer,
    boards: boardsReducer,
    modal: ModalReducer
}

export default reducer;

 

  • Redux 스토어 생성
    • Redux 어플리케이션의 모든 상태(데이터)를 저장하는 단 하나의 중앙저장소
    • configureStore 함수로 Redux 스토어 생성
    • reducer.ts 에서 합친 reducer를 reducer로 받기
    • getState()로 상태를 읽고, dispatch(action)으로 상태를 업데이트하며, subscribe(listener)로 상태 변화 감지
    • store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer/reducer";

const store = configureStore({
    reducer
})

export default store;

 

슬라이스(Slice) 만들기 - boardsSlice.ts

boardsSlice.ts

import { createSlice } from "@reduxjs/toolkit";
import type { IBoard } from "../../types";

type TBoardsState = {
    modalActive: boolean;
    boardArray: IBoard[]
}

const initialState : TBoardsState = {
    modalActive: false,
    boardArray: [
        {
            boardId: 'board-0',
            boardName: "첫 번째 게시물",
            lists: [
                {
                    listId: 'list-0',
                    listName: "List 1",
                    tasks: [
                        {
                            taskId: "task-0",
                            taskName: "Task 1",
                            taskDescription: "Description",
                            taskOwner: "kyj",
                        },
                        {
                            taskId: "task-1",
                            taskName: "Task 2",
                            taskDescription: "Description",
                            taskOwner: "kyj",
                        },
                    ]
                },
                {
                    listId: 'list-1',
                    listName: "List 2",
                    tasks: [
                        {
                            taskId: "task-3",
                            taskName: "Task 3",
                            taskDescription: "Description",
                            taskOwner: "kyj",
                        }
                    ]
                }
            ] 
        }
    ]
}

const boardsSlice = createSlice({
    name: 'boards',
    initialState,
    reducers: {

    }
})

export const boardsReducer = boardsSlice.reducer;

 

IBoard, IList 타입 만들기

export interface IBoard {
    boardId : string;
    boardName : string;
    lists: IList[];
}

export interface IList {
    listId: string;
    listName: string;
    tasks: ITask[];
}

 


리덕스 Hooks 생성

  • 타입스크립트에서  useSelector와 useDispatch를 쓸 때 state와 action에 대한 타입을 커스텀 훅으로 지정해줘야 한다.
  • store/index.ts
    • RootState와 AppDispatch의 타입 생성
import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer/reducer";

const store = configureStore({
    reducer
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch;

export default store;
  • hooks/redux.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../store";

export const useTypedSelector: TypedUseSelectorHook<RootState>  = useSelector
export const useTypeDispatch = () => useDispatch<AppDispatch>();

 

2025.11.17 - [프레임워크\라이브러리/React] - [Redux] 리덕스 Hooks 생성

 

[Redux] 리덕스 Hooks 생성

1️⃣ 기본 개념: useSelector와 useDispatch useSelector()리덕스 스토어에 있는 상태 데이터 가져올 때 쓰는 훅 예:const count = useSelector(state => state.counter.value);여기서 문제는 TypeScript를 쓰면 state의 타입을

thinktank911.tistory.com

 

 


※ 제너릭 타입 사용법

interface Obj<T>{
    name: T;
}

interface State {
    state: {
        data: string,
        loading: boolean
    }
}

const obj: Obj<State> = {
    name : {
        state: {
            data: 'abcd',
            loading: false
        }
    }
}

전역 스타일 생성

vite.config.ts에 플러그인 셋팅

import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), vanillaExtractPlugin()],
})

 

App.css.ts 생성

  • App.tsx 스타일을 위한 파일 생성
  • createGlobalTheme, style 을 @vanilla-extract/css 에서 호출
import { createGlobalTheme, style } from "@vanilla-extract/css";

export const vars = createGlobalTheme(":root", {
    color: {
        main: "#ffa726",
        mainDarker: "#f57c00",
        mainFaded: "#ffb74d",
        mainFadedBright: "#ffb74da6",
        list: "rgb(235, 236, 240)",
        task: "rgb(255, 255, 255)",
        taskHover: "rgb(245, 245, 245)",
        brightText: "rgb(255, 255, 255)",
        darkText: "rgb(24, 42, 77)",
        seconarDdarkText: "rgb(94, 108, 132)",
        seconarDdarkTextHover: "rgb(218, 219, 226)",
        selectedTab: "rgb(137, 176, 174)",
        updateButton: "rgb(237, 180, 88)",
        deleteButton: "rgb(237, 51, 88)",
    },
    fontSizing: {
        T1: "32px",
        T2: "24px",
        T3: "18px",
        T4: "14px",
        P1: "12px",
    },
    spacing: {
        small: "5px",
        medium: "10px",
        big1: "20px",
        big2: "15px",
        listSpacing: "30px"
    },
    font: {
        body: "arial",
    },
    shadow: {
        basic: "4px 4px 8px 0px rgba(34, 60, 80, 0.2)"
    },
    minWidth: {
        list: '250px'
    }
})

export const appContainer = style({
    display: 'flex',
    flexDirection: 'column',
    minHeight: '100vh',
    height: 'max-content',
    width: '100vw'
})

export const board = style({
    display: 'flex',
    flexDirection: 'row',
    height: '100%',
})

export const buttons = style({
    marginTop: 'auto',
    paddingLeft: vars.spacing.big2
})

 

App.tsx에 스타일 적용

import { appContainer, board, buttons } from './App.css'

function App() {

  return (
    <div className={appContainer}>
      <div className={board}></div>
      <div className={buttons}>
        <button>이 게시판 삭제하기</button>
        <button>활동목록 보이기 </button>
      </div>
    </div>
  )
}

export default App