2024년 11월 4일
프로젝트 생성
localhost:4000/about : SPA → 싱글 페이지에서 진행
about.html : MPA
폴더 생성 하기
@reduxjs/toolkit
clsx : classname을 만들때 동적으로 만들기 위해 사용
@vanila-extract/css
react-icons
uuid
react-beautiful-dnd : 드래그앤드랍 기능을 위해 사용
설치하기
리덕스
리덕스의 흐름(Flow)
Redux toolkit을 사용해서 Reducer을 생성하려면 Slice가 있어야함
Toolkit → Slice → Reducer → Store → Provider → react component wrapping
Slice 만들기(Sub reducer 만들기)
// types / index.ts
export interface ITask {
taskId: string;
taskName: string;
taskDescription: string;
taskOwner: string;
}
export interface ILogItem {
logId: string;
logAuthor: string;
logMessage: string;
logTimeStamp: string;
}
// store / slices / 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: "Lina"
}
};
const modalSlice = createSlice({
name: 'modal',
initialState,
reducers: {
}
});
export const modalReducer = modalSlice.reducer;
// store / slices / loggerSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import { ILogItem } from '../../types';
type TloggerState = {
logArray : ILogItem[],
}
const initialState : TloggerState = {
logArray: []
}
const loggerSlice = createSlice({
name: 'logger',
initialState,
reducers: {
}
})
export const loggerReducer = loggerSlice.reducer;
// store / slices / boardsSlice.ts
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
modalActive: false,
boardArray: []
};
const boardsSlice = createSlice({
name: 'boards',
initialState,
reducers : {
}
})
export const boardsReducer = boardsSlice.reducer;
ub reducer들을 만들면 합쳐줘야 함 → 하나의 메인 리듀서로 합치기
reducer combine
// 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;
export default vs export
store 생성 → 이제 데이터를 가져올 곳을 마련해야함
// store / index.ts
import { configureStore } from '@reduxjs/toolkit';
import reducer from './reducer/reducer';
const store = configureStore({
reducer // reducer : reducer
})
export default store;
// main.tsx
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { Provider } from 'react-redux'
import store from './store/index.ts'
createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<App />
</Provider>
)
... 생략 ...
Board 데이터 및 타입 생성 (boardSlice 본격 생성)
// store / slice / boardsSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import { 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: "Lina",
},
{
taskId: "task-1",
taskName : "Task 2",
taskDescription : "Description",
taskOwner: "Lina",
},
]
},
{
listId: "list-1",
listName: "List 2",
tasks : [
{
taskId: "task-2",
taskName : "Task 3",
taskDescription : "Description",
taskOwner: "Lina",
}
]
}
]
}
]
}
const boardsSlice = createSlice({
name: 'boards',
initialState,
reducers : {
}
})
export const boardsReducer = boardsSlice.reducer;
// types / index.ts
export interface ITask {
taskId: string;
taskName: string;
taskDescription: string;
taskOwner: string;
}
export interface ILogItem {
logId: string;
logAuthor: string;
logMessage: string;
logTimeStamp: string;
}
export interface IBoard {
boardId: string;
boardName: string;
lists: IList[];
}
export interface IList {
listId: string;
listName: string;
tasks: ITask[];
}
리덕스 스토어 데이터 가져오기 : userSelector()
데이터 업데이트(action dispatch) : useDispatch()
타입스크립트에서 추론을 하지 못하면 개발자가 타입을 지정(annotate)
store에서 데이터를 가지고 옴 → store.getState() 사용 → 타입을 가져올 것임
리턴 유틸리티 타입
// store / index.ts
import { configureStore } from '@reduxjs/toolkit';
import reducer from './reducer/reducer';
const store = configureStore({
reducer
});
// store의 데이터 타입 지정
// 리턴 유틸리티 타입
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch;
// 이제 state의 타입을 지정할 수 있게 되었고, 아래 처럼 사용할 수 있다.
const logger = useSelector((state: RootState) => state.logger);
// const usingDispatch = useDispatch((dispatch : AppDispatch) => ~~)
// 그런데, 매번 useSelector()을 사용해 RootState를 export 해서 가져오긴 힘드니까
// useDispatch도 마찬가지.
// useAppSelector or useTypedSelector / useTypedDispatch
// 처럼 Hooks를 새롭게 만들고 RootState, AppDispatch를 내장해서 만들면
// RootState, AppDispatch를 계속 가져오지 않아도 됨!
// hooks / redux.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { RootState } from '../store';
// const useTypedSelector : RootState = useSelector; // 오류생김
// 아래처럼 바꿔줘야함
const useTypedSelector : TypedUseSelectorHook<RootState> = useSelector;
const logger = useSelector((state : RootState) => state.logger);
// TypedUseSelectorHook을 쓰고 제네릭으로 감싸준 이유는,
// state : RootState를 보았을 때
// RootState는 현재
/*
type RootState = {
logger : TLoggerState;
boards : TBoardsState;
modal : TModalState;
}
*/
// 이다. 그러나 state는
/*
state : {
logger : TLoggerState;
boards : TBoardsState;
modal : TModalState;
}
*/
// 으로 다르다. state : {} 부분이 RootState에는 없음
interface State {
state : {
data: string,
loading: boolean
}
}
const obj:State = {
name : { // name이 없어서 에러 발생
state: {
data: 'abcd',
loading: false
}
}
}
// 이와 비슷한 상황임
// 그렇기 때문에 제네릭을 사용해준다.
interface Obj<T> {
name: T;
}
interface State {
state : {
data: string,
loading: boolean
}
}
const obj:Obj<State> = {
name : {
state: {
data: 'abcd',
loading: false
}
}
}
// hooks / redux.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { RootState, AppDispatch } from '../store';
export const useTypedSelector : TypedUseSelectorHook<RootState> = useSelector;
// dispatch도 바꿔주기
export const useTypedDispatch = () => useDisaptch<AppDispatch>();
// 타입을 넣지 않고 위의 훅을 넣어서 아래처럼 편리하게 사용 가능
const logger = useTypedSelector((state) => state.logger);
// vite.config.ts
// vanilla extract 플러그인 등록
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), vanillaExtractPlugin()],
})
// App.css.ts
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)",
secondaryDarkText : "rgb(94, 108, 132)",
secondaryDarkTextHover : "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"
}
});
// 사용 : vars.color.main
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 { useState } from 'react'
import reactLogo from './assets/react.svg'
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
🍎🍏 오늘의 느낀점 : 드디어 프로젝트에 들어가서 리덕스를 셋팅과 vanilla extract을 사용해봤다. 제네릭 문법을 대충 알고 있었는데 다시 찾아보면서 개념을 익혔다. 타입스크립트에 대해 좀 더 공부가 필요할 것 같다.