redux-rtk
Redux Toolkit(RTK) 상태 관리 패턴. createSlice, configureStore, RTK Query(createApi), slices 디렉토리 구조.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Redux Toolkit(RTK) 상태 관리 패턴. createSlice, configureStore, RTK Query(createApi), slices 디렉토리 구조.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
세션 핸드오프. 컨텍스트가 차기 전에 중요 맥락을 progress.md에 문서화하고 새 세션 킥오프 프롬프트를 클립보드에 복사한다. "핸드오프", "세션 정리하고 넘기자", "컨텍스트 정리", "이어갈 준비" 등의 요청에 사용.
MD 파일 또는 텍스트로 작업을 정의하면, 분석 → 디자인 확인 → 구현 → 검증 → 커밋 → PR까지 전체 플로우를 수행한다. "이거 만들어줘", "기능 구현해줘", "작업 시작하자", "이 티켓 진행해줘" 등 코드 작업 착수 발화에 사용.
Claude + OpenAI Codex 협업 스킬. MCP 서버 설정 시에만 사용 가능.
code-forge 상태 대시보드. REFLECT flag, quality 이벤트, notepad/decisions, usage 집계를 한 번에. forge-glow 같은 외부 도구는 --json으로 파싱.
교차 모델 토론. Agent Teams / Codex CLI / self-debate 모드 선택. 설계 결정, 아키텍처 선택 시 활용. "어느 쪽이 나을까", "설계 비교해줘", "토론시켜줘", "교차 검증해줘" 등의 요청에 사용.
화면 단위 E2E 테스트 자동화. Figma/코드 기반 테스트 케이스 도출 → Playwright 코드 생성 → Forge Loop(에스컬레이션 기반 자율 실행). "E2E 돌려줘", "화면 테스트 만들어줘", "이 페이지 자동 테스트" 등의 요청에 사용.
| name | redux-rtk |
| description | Redux Toolkit(RTK) 상태 관리 패턴. createSlice, configureStore, RTK Query(createApi), slices 디렉토리 구조. |
store/
├── index.ts # configureStore, RootState, AppDispatch
├── slices/
│ ├── authSlice.ts # 인증 상태
│ ├── uiSlice.ts # UI 전역 상태
│ └── filterSlice.ts # 필터 상태
└── api/
├── orderApi.ts # RTK Query API 슬라이스
└── userApi.ts
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import authReducer from './slices/authSlice';
import uiReducer from './slices/uiSlice';
import { orderApi } from './api/orderApi';
export const store = configureStore({
reducer: {
auth: authReducer,
ui: uiReducer,
[orderApi.reducerPath]: orderApi.reducer, // RTK Query
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(orderApi.middleware), // RTK Query 미들웨어
});
// TypeScript 타입 추출
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// 타입이 적용된 훅 (컴포넌트에서 사용)
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// store/slices/authSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
token: string | null;
}
const initialState: AuthState = {
user: null,
isAuthenticated: false,
token: null,
};
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
login: (state, action: PayloadAction<{ user: User; token: string }>) => {
state.user = action.payload.user;
state.token = action.payload.token;
state.isAuthenticated = true;
},
logout: (state) => {
state.user = null;
state.token = null;
state.isAuthenticated = false;
},
updateUser: (state, action: PayloadAction<Partial<User>>) => {
if (state.user) {
state.user = { ...state.user, ...action.payload };
}
},
},
});
export const { login, logout, updateUser } = authSlice.actions;
export default authSlice.reducer;
// Selectors
export const selectUser = (state: RootState) => state.auth.user;
export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated;
// store/slices/uiSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UIState {
isSidebarOpen: boolean;
activeTab: string;
theme: 'light' | 'dark';
}
const initialState: UIState = {
isSidebarOpen: true,
activeTab: 'overview',
theme: 'light',
};
export const uiSlice = createSlice({
name: 'ui',
initialState,
reducers: {
toggleSidebar: (state) => {
state.isSidebarOpen = !state.isSidebarOpen;
},
setActiveTab: (state, action: PayloadAction<string>) => {
state.activeTab = action.payload;
},
setTheme: (state, action: PayloadAction<'light' | 'dark'>) => {
state.theme = action.payload;
},
},
});
export const { toggleSidebar, setActiveTab, setTheme } = uiSlice.actions;
export default uiSlice.reducer;
// store/api/orderApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { RootState } from '../index';
interface Order {
id: string;
name: string;
status: string;
}
interface OrderFilters {
status?: string;
page?: number;
}
export const orderApi = createApi({
reducerPath: 'orderApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
// 인증 토큰 자동 첨부
const token = (getState() as RootState).auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
}),
tagTypes: ['Order'], // 캐시 무효화 태그
endpoints: (builder) => ({
getOrders: builder.query<{ orders: Order[]; total: number }, OrderFilters>({
query: (filters) => ({
url: '/orders',
params: filters,
}),
providesTags: ['Order'],
}),
getOrder: builder.query<Order, string>({
query: (id) => `/orders/${id}`,
providesTags: (result, error, id) => [{ type: 'Order', id }],
}),
createOrder: builder.mutation<Order, Omit<Order, 'id'>>({
query: (body) => ({
url: '/orders',
method: 'POST',
body,
}),
invalidatesTags: ['Order'], // 생성 후 목록 캐시 무효화
}),
updateOrder: builder.mutation<Order, Partial<Order> & { id: string }>({
query: ({ id, ...body }) => ({
url: `/orders/${id}`,
method: 'PATCH',
body,
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Order', id }],
}),
deleteOrder: builder.mutation<void, string>({
query: (id) => ({
url: `/orders/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['Order'],
}),
}),
});
// 자동 생성된 훅 export
export const {
useGetOrdersQuery,
useGetOrderQuery,
useCreateOrderMutation,
useUpdateOrderMutation,
useDeleteOrderMutation,
} = orderApi;
// ✅ 좋은 예: 타입 적용된 훅 사용
import { useAppDispatch, useAppSelector } from '@/store';
import { login, logout, selectUser } from '@/store/slices/authSlice';
import { useGetOrdersQuery, useCreateOrderMutation } from '@/store/api/orderApi';
function OrderListPage() {
const user = useAppSelector(selectUser);
const dispatch = useAppDispatch();
const { data, isLoading, error } = useGetOrdersQuery({ status: 'active' });
const [createOrder, { isLoading: isCreating }] = useCreateOrderMutation();
const handleCreate = async (orderData: NewOrder) => {
try {
await createOrder(orderData).unwrap(); // unwrap으로 에러 처리
} catch (err) {
console.error('생성 실패:', err);
}
};
return (
<div>
<p>사용자: {user?.name}</p>
{isLoading && <div>로딩 중...</div>}
{error && <div>오류 발생</div>}
{data?.orders.map((order) => (
<OrderCard key={order.id} order={order} />
))}
</div>
);
}
// src/main.tsx
import { Provider } from 'react-redux';
import { store } from '@/store';
function Root() {
return (
<Provider store={store}>
<App />
</Provider>
);
}
useAppDispatch, useAppSelector 사용 (기본 훅 대신)?createSlice로 reducer + actions 함께 정의?tagTypes 설정?invalidatesTags로 캐시 무효화?configureStore에 추가?