Trello와 비슷한 형식의 Task 웹 만들기
- 게시물 마다 할일 생성
- 할일 list 여러개
- 할일 상세정보 확인
- 활동기록 로그 확인
- 할일 자유롭게 이동 가능
환경세팅 vite, 폴더구조
npm npx registry 패키지 안에 node 관련 패키지들이 가져온다
npx : 패키지 가져오는건 동일, nodemodules 에는 저장하지 않고, 사용할 수 있게 한다. > 한번만 사용할때 유용
`npm init vite` React, TypeScript 선택 > `npm i` 로 package 에 있는 필요한 라이브러리 저장하기
`npm run dev`
id root 안에 src 값이 들어가있다. SPA(index.html) > MPA(about.html)
폴더구조
`useSelector`, `useDispatch` 는 타입스크립트에서는 hooks로 만들어서 사용하는게 편리하다.
index.ts에서 Redux store 만들어서 사용한다.
- assets
- components : 컴포넌트
- hooks : 커스텀 hooks
- store : 리액트 부모, 자식 소통하는 props 로 데이터 공유할때 쓰는 상태관리 툴 - Redux 소스코드 넣어준다
- 상태관리 툴 : Redux, Mobx, Zustand, Recoil, React Context
- types : 타입 관리 index.ts
`file-tree-generator`
📦src
┣ 📂assets
┃ ┗ 📜react.svg
┣ 📂components
┃ ┣ 📂ActionBtn
┃ ┃ ┣ 📂DropDownForm
┃ ┃ ┃ ┣ 📜DropDownForm.css.ts
┃ ┃ ┃ ┗ 📜DropDownForm.tsx
┃ ┃ ┣ 📜ActionBtn.css.ts
┃ ┃ ┗ 📜ActionBtn.tsx
┃ ┣ 📂BoardList
┃ ┃ ┣ 📂SideForm
┃ ┃ ┃ ┣ 📜SideForm.css.ts
┃ ┃ ┃ ┗ 📜SideForm.tsx
┃ ┃ ┣ 📜BoardList.css.ts
┃ ┃ ┗ 📜BoardList.tsx
┃ ┣ 📂EditModal
┃ ┃ ┣ 📜EditModal.css.ts
┃ ┃ ┗ 📜EditModal.tsx
┃ ┣ 📂List
┃ ┃ ┣ 📜List.css.ts
┃ ┃ ┗ 📜List.tsx
┃ ┣ 📂ListContainer
┃ ┃ ┣ 📜ListContainer.css.ts
┃ ┃ ┗ 📜ListsContainer.tsx
┃ ┣ 📂LoggerModal
┃ ┃ ┣ 📂LogItem
┃ ┃ ┃ ┣ 📜LogItem.css.ts
┃ ┃ ┃ ┗ 📜LogItem.tsx
┃ ┃ ┣ 📜LoggerModal.css.ts
┃ ┃ ┗ 📜LoggerModal.tsx
┃ ┗ 📂Task
┃ ┃ ┣ 📜Task.css.ts
┃ ┃ ┗ 📜Task.tsx
┣ 📂hooks
┃ ┗ 📜redux.ts
┣ 📂store
┃ ┣ 📂reducer
┃ ┃ ┗ 📜reducer.ts
┃ ┣ 📂slices
┃ ┃ ┣ 📜boardsSlice.ts
┃ ┃ ┣ 📜loggerSlice.ts
┃ ┃ ┗ 📜modalSlice.ts
┃ ┗ 📜index.ts
┣ 📂types
┃ ┗ 📜index.ts
┣ 📜App.css
┣ 📜App.tsx
┣ 📜index.css
┣ 📜main.tsx
┗ 📜vite-env.d.ts
`ES7+ React/Redux/React-Native snippets` 익스텐션 설치 > rafce 단축어로 react 함수 세팅 할 수 있다.
리액트 컴포넌트를 사용하는 경우 `tsx`
패키지
`@reduxjs/toolkit redux clsx` : classname 동적으로 사용하기 위해 사용한다.
<div className={clsx('foo', true && 'bar', 'baz');}>
<div className='foo bar baz'>
`@vanilla-extract/css` : `.css.ts` 에서 타입스크릅트에서의 css 를 작성하기 위한 패키지
`@vanilla-extract/css-utils` `@vanilla-extract/vite-plugin` 두가지도 추가로 설치해주어야 한다.
`react-icons` : 리액트 패키지에서 제공해주는 아이콘 사용
`uuid` : 유니크한 아이콘 사용
`react-beautiful-dnd` : 드래그 앤 드랍 기능 구현할때 사용하는 라이브러리
`react-redux` : Provider 리덕스 사용
`npm i react-redux @reduxjs/toolkit redux clsx @vanilla-extract/css @vanilla-extract/css-utils @vanilla-extract/vite-plugin react-icon uuid react-beautiful-dnd`
리덕스 사용 준비
상태 관리 라이브러리 - 선택사항
State, Props 로 상태를 여러 컴포넌트와 공유하는데
앱이 커지면 관리가 힘들고, 소스코드가 지저분해지기 때문에 Redux를 사용한다
Flow
- Action 객체 Dispatch 함수 : 함수 안에 객체를 인수로 넣어서 전달한다
- `Reducer 함수`로 전달해서, type에 따라 return 값을 업데이트 한다.
- Redux `Store` Stae에 저장한다.
- React Component에 Provider로 감싸줘서 rerendering 된다
Toolkit에서 Reducer를 생성하려면 Slice가 있어야 한다.
Slice 생성
`createSlice` 제공 api
modalSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import { 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: "dah",
},
};
const modalSlice = createSlice({
name: "modal",
initialState,
reducers: {},
});
export const modalReducer = modalSlice.reducer;
//types 폴더 index.ts
export interface ITask {
taskId: string;
taskName: string;
taskDescription: string;
taskOwner: string;
}
loggerSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import { ILogItem } from "../../types";
type loggerState = {
logArray: ILogItem[];
};
const initialState: loggerState = { logArray: [] };
const loggerSlice = createSlice({
name: "logger",
initialState,
reducers: {},
});
export const loggerReducer = loggerSlice.reducer;
boardsSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import { IBoard } from "../../types";
type TBoardsrState = { modalActive: boolean; boardArray: IBoard[] };
const initialState: TBoardsrState = {
modalActive: false,
boardArray: [
{
boardId: "board-0",
boardName: "첫번째 게시물",
lists: [
{
listId: "list-0",
listName: "list 1",
tasks: [
{
taskId: "task-0",
taskName: "Task 1",
taskDescription: "Description",
taskOwner: "dah",
},
{
taskId: "task-1",
taskName: "Task 2",
taskDescription: "Description",
taskOwner: "dah",
},
],
},
{
listId: "list-1",
listName: "list 2",
tasks: [
{
taskId: "task-3",
taskName: "Task 3",
taskDescription: "Description",
taskOwner: "dah",
},
],
},
],
},
],
};
const boardsSlice = createSlice({
name: "boards",
initialState,
reducers: {},
});
export const boardsReducer = boardsSlice.reducer;
//index.ts
export interface IBoard {
boardId: string;
boardName: string;
lists: IList[];
}
export interface IList {
listId: string;
listName: string;
tasks: ITask[];
}
`reducer.ts` 에서 위에서 생성한 sub reducer 모아서 `combine` reducer로 만들어야 한다.
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;
** export 할때 변수로만 추출하면 import 할때 {} 중괄호 필수!!
** default 로 추출하면 경로가 맞다면 '이름'이 달라도 가능하다. 중괄호 없다.
store 생성
slices > index.ts `configureStore` 제공api
import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer/reducer";
const store = configureStore({ reducer });
export default store;
다양한 컴포넌트에서 사용할 수 있게 하는 부분 ? main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { Provider } from "react-redux";
import store from "./store/index.ts";
ReactDOM.createRoot(document.getElementById("root")!).render(
<Provider store={store}>
<App />
</Provider>
);
redux hooks 생성
타입스크립트에서 타입추론이 안된다면(unknown) : 타입 지정하는 annotate 해주어야 한다.
//index.ts
import { TypedUseSelectorHook, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../store";
import { useDispatch } from "react-redux";
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useTypedDispatch = () => useDispatch<AppDispatch>();
//redux.ts
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
전역 스타일 생성하기
App.css.ts 파일에서 @vanilla-extract/css 사용해서 스타일 적용한다.
그 전! vite.config.ts 에서 플러그인 사용 등록해주기
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(),vanillaExtractPlugin()],
})
import { createGlobalTheme, style } from "@vanilla-extract/css";
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)",
secondaryDartText: "rgb(94, 108, 132)",
secondaryDartTextHover: "rgb(218, 219, 226)",
selectedTab: "rgb(137, 176, 174)",
updateButton: "rgba(237, 180, 88)",
deleteButton: "rgba(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: "row",
minHeight: "100vh",
height: "max-content",
width: "100%",
});
export const board = style({
display: "flex",
flexDirection: "row",
height: "100%",
});
export const buttons = style({
marginTop: "auto",
paddingLeft: vars.spacing.big2
});
☑️ 배운 점
타입스크립트로 프로젝트 하는 어려움 + 리덕스 툴킷 어려움 + 초기세팅의 많은 양의 강의
어려웠다. 복습필요*** > WIL 작성