2024.12.10 - [React] - React(리액트) 티키타카 21 (Redux -1)
이전 글 에서 사용한 redux에는 비동기가 없당. 그러디용
SPA(Single Page Application)에서 비동기 상황을 빼면 아예 할 필요가 없당.
앞 글에서 사용한 형식이 redux의 standard reducer 포맷이당.
useEffect와 함께 잘 쓴다면, 잘 쓸 수 있을거당. 일단 그건 그렇탕수육!
redux에서는 비동기 처리를 조금 더 예쁘고, 일관된 가독성을 부여할 수 있게
createAsyncThunk란 걸 제공한당. 참고로 Thunk는 think의 과거형이당.
나무위키에서 한번 검색해 보길 간절히 바란당. 상식이 증폭된당.
진행을 위해서 이전 소스를 쪼메만 정돈을 해보도록 하장
src/feature/friends 폴더의 friendSlice.js를 아래 girl로 더퍼쓰장.
friendSlice.js
import { createSlice } from "@reduxjs/toolkit";
// 초기값 수정
const initialState = {
friends: [],
isError: false,
isSuccess: false,
isLoading: false,
msg: "",
};
export const friendSlice = createSlice({
name: "friend",
initialState,
reducers: {
reset: (state) => {
console.log("reset 호출");
state = initialState;
return state;
}, // 억지 표현
},
extraReducers: () => {},
});
// action 추출항 수출
export const { reset } = friendSlice.actions;
// reducer 기본 수출
export default friendSlice.reducer;
초기값 friends를 빈 배열로 한당. 왱?
실제 프로그램에선 비동기로 서버에서 데이터를 가져와야 할 지어당.
그라고 비동기는 보통 pending(로딩중) 과 종료시 success(성공)이냐 error(실패)냥
3가지 상태로 나타낼 수 있당. 그걸 isError, isSuccess, isLoading 불린(boolean)으로
상태변수를 추가했당.
(내 맘이당. 당신은 당신 맘대로 변수명을 지정해도 nobody complain이당)
msg는 출력메세지를 담을 그냥 변수당. 그렇단당.
reducers에 reset 액션이 보일거당. 일이 끝나면 상태변수들을 초기화 해줘야 한당.
마치 스프링의 VO같은 느끼미 가슴팍을 효자손으로 긁는듯한 시원한 설명이당.
(state) => initialState만 써도 되는 걸,
vscode에서 변수를 선언만 하고 쓰고 있지 않다고 투덜되는 바람에 eslint.config.js에서
"off"로 막았다/풀었다를 반복하는 고민을 하다가, 막지 않고 위와 같은 억지 표현으로
전혀 예술적이지 못한 궁상 예술 코드를 만들고 말았다.(Waiting Good Idea!!)
이제 src/feature/friends 폴던에 아래처럼 friendService.js를 맹글장.
이 파일의 존재 의미는 실제 비동기 API 요청 코드를 담는 것이다.
B/E를 또 만들려면 번거롭고 뽀인또를 잊을 수 있어서,
그냥 Promise를 이용하여 Faking 함을 양해각서에 사인하는 느끼미로 받아들이자.
요청 받으면 1.5초 뒤에 데이터를 준당.
friendService.js
// B/E 서버 만들기 귀찮을 땐 요따구로
async function getFriends() {
return new Promise((resolve) => {
console.log("디버깅용 왔낭? 체킁");
setTimeout(() => {
resolve([
{ id: 1, name: "경미니", song: "그리 미누", color: "green" },
{ id: 2, name: "로제", song: "ground", color: "black" },
{ id: 3, name: "원영", song: "iam", color: "gold" },
{ id: 4, name: "제니", song: "mantra", color: "pink" },
{ id: 5, name: "카리나", song: "nova", color: "blue" },
]);
}, 1500);
});
}
// friendService란 이름에 getFriends api 함수 담아서 수출
const friendService = { getFriends };
export default friendService;
getFriends 함수는 딱 봐도 비동기 함수당.
호출하면 진행중(pendig) 과 끝난상태(성공 or 실패)를 가지게 된당.
바로 요 상태 처리를 해 줄 수 있게 해주는 거이 createAsyncThunk당.
friendsSlice.js 파일의
export const friendSlice = createSlice({ 라인 위에
아래 코드를 추가 해보장
export const getFriends = createAsyncThunk(
"friends/getFriends", // 이건 그냥 고유값 의미 부여, 관례적으로 디렉토리/ 비동기함수명
async (_, thunkAPI) => {
try {
return await friendService.getFriends();
} catch (error) {
const msg =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(msg);
}
}
);
분명 낯선girl 시로하는 뇌가 으... 이건 또 머얌... 불평과 불만을 토로 할 거시당.
이건 프레임워크의 세팅과 같은 거라 그저 반복될 뿐이라고 위로를 해주장.
try 안에 있는 것만 중요하고, 나머진 별로 중요하당. thunkAPI는 redux가 제공해준당.
(지금은 전혀 안 중요, 그저 편하게 쓰면 그만)
만약 이 코드에서 createAsyncThunk함수가 Proxy 함수를 만들고 있다는 추론을 했다면
당신은 이루 말할 수 없이 훌륭하당.
아프로 무얼하든 IT쪽만 하면 먹고 사는데 지장이 없을꺼같당.
뽀인트는 createAsyncThunk로 만든 slice의 getFriends 함수를 호출 하면 ,
여기서 friendsService.js의 실질 API 호출 함수 getFriends를 호출하여
로딩(pending), 성공(fulfilled), 실패(rejected) 상태를 처리할 거당.
redux에서 비동기 상태를 pending, fulfilled, rejected 로 나누었던 거시였던 거시당.
다시 friendSlice.js 파일로 가서
extraReducers:() =>{}, 라인을 아래 처럼 수정하도록 하장.
extraReducers: (builder) => {
builder
.addCase(getFriends.fulfilled, (state, action) => {
console.log("체킁1:", action);
state.isLoading = false;
state.isSuccess = true;
state.friends = action.payload;
})
.addCase(getFriends.pending, (state) => {
console.log("체킁2:", state);
state.isLoading = true;
})
.addCase(getFriends.rejected, (state, action) => {
console.log("체킁:3", action);
state.isLoading = false;
state.isError = true;
state.msg = action.payload;
});
},
당황스러울 수 있다. 요즘 프레임워크나 라이브러리들은 요따구 방식이다.
샘플이나 템플릿을 1~2개 보여주고서는 요따구로 쓰면 되요당.
형태만 잘 확인하고, 눈에 익히자.. 향후 반복적인 형태로 재활용 될 거시당.
머릿속에는 이렇게 정리하장. 내가 직접 api를 부르지 않고 proxy(브로커)를
이용해서 api를 호출하게 하고, 난 proxy에서 pending(진행)중일 때는 요렇게 하세요
fulfilled(성공) 일때 요렇게, rejected(실패) 일때 요렇게 하세요 라는 지시만 한다공.
당연 분명 설명이 부족함을 느낄거당. 혹 더 파고 싶다고 아래 링크를 누른당.
https://redux-toolkit.js.org/api/createAsyncThunk
욜씨미 읽어 본다면(이런 공부는 시간 낭비가 아닌 이겨내야 할 성장통이다)
분명 득템하는 것이 꽤나 있을거시당.
여기서 수정 작업한 friendSlice.js 내용의 전체 소스를 참고용으로 다시 붙인당.
friendSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import friendService from "./friendService";
// 초기값 수정
const initialState = {
friends: [],
isError: false,
isSuccess: false,
isLoading: false,
msg: "",
};
export const getFriends = createAsyncThunk(
"friends/getFriends", // 이건 그냥 고유 의미 부여, 관례적으로 디렉토리/ 비동기함수명
async (_, thunkAPI) => {
try {
return await friendService.getFriends();
} catch (error) {
const msg =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(msg);
}
}
);
export const friendSlice = createSlice({
name: "friend",
initialState,
reducers: {
reset: (state) => {
console.log("reset 호출");
state = initialState;
return state;
}, // 억지 표현
},
extraReducers: (builder) => {
builder
.addCase(getFriends.fulfilled, (state, action) => {
console.log("체킁1:", action);
state.isLoading = false;
state.isSuccess = true;
state.friends = action.payload;
})
.addCase(getFriends.pending, (state) => {
console.log("체킁2:", state);
state.isLoading = true;
})
.addCase(getFriends.rejected, (state, action) => {
console.log("체킁:3", action);
state.isLoading = false;
state.isError = true;
state.msg = action.payload;
});
},
});
// action 추출항 수출
export const { reset } = friendSlice.actions;
// reducer 기본 수출
export default friendSlice.reducer;
휴~~ 거의 다 왔당. 이제 App.jsx에 수정된 내용을 반영하장.
App.jsx
import { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getFriends, reset } from "./feature/friends/friendSlice";
function App() {
const { friends } = useSelector((state) => state.friend);
const dispatch = useDispatch();
const myForm = useRef();
console.log("디버깅용 rendering 호출", friends);
const addNew = (e) => {
e.preventDefault();
};
// 요거이 있어야 friends 빈 배열이 데이터를 가지게 되지용
useEffect(() => {
console.log("디버깅용 effect 호출");
dispatch(getFriends());
// unmount 될 때 상태 reset, isPending, isSuccess, isError, msg 초기화
return () => dispatch(reset());
}, [dispatch]);
return (
<>
<h1 id="title">리덕스 createAsyncThunk 연습</h1>
<hr />
<form ref={myForm} onSubmit={addNew}>
이름 <input type="text" autoFocus name="fname" required />
<br />
노래 <input type="text" name="fsong" required />
<br />
칼라 <input type="color" name="fcolor" />
<br />
<button type="submit">추강</button>
</form>
<hr />
<div>
{friends.map((friend) => (
<div key={friend.id} className="friend">
<h1>{friend.name}</h1>
<h2 style={{ color: friend.color }}>{friend.song}</h2>
</div>
))}
</div>
</>
);
}
export default App;
이제 실행해 보면 이전 처럼 잘 나올거당.
혹 본질을 잊은 사람이 있을까봐 노파심에 이야기 한다면, 이전 글은 그냥 초기값으로 데이터를
처음부터 주었고, 이번은 초기값 빈 배열에서, 비동기로 데이터를 가져와 뿌렸당!!(오켕?)
페이크로 1.5초를 주어서 데이터 리스트는 정확히 1.5초 뒤에 출현한당.
꼭 하나 짚고 넘어가야 한다면 아래 코드 일거당.
만약 당신이 초보자거나 React 경험치가 아직 풍부하지 못하다면
아래 useEffect가 무한루프를 일으켜야 하는게 아닌가? 생각할 수도 있당.
왜? 무한 루프가 발생하지 않는지?,
그 이유를 어떻게 코드로 증명할 수 있는지? 꼬옥 한번 생각해보장!
[분명 당신의 추론과 증명 코드는 아름답다못해 자체발광 꿈틀 꾸믈꿀꺼당.]
useEffect(() => {
console.log("디버깅용 effect 호출");
dispatch(getFriends());
// unmount 될 때 상태 reset, isPending, isSuccess, isError, msg 초기화
return () => dispatch(reset());
}, [dispatch]);
여기까지 잘 되었다면 이전 글에 있는 addFriend를 createAsyncThunk를 이용하여
바꿔 보는 연습을 직접 해보는 건 말로 형언할 수 없을 만큼 귀중한 시간이 될 거이당.
아! 크롬 웹 스토어에 가서 Redux DevTools를 설치하는 것도 잊지말장.
Redux 사용시 디버깅에 유용하게 쓰라고 제공된다.( 아래 그림을 고르면 된당)
마무리한 나의 결과 화면은 아래와 같당. (추강도 잘 동작한당.)
Redux DevTools 에 레코딩 된 결과는 아래와 같당.
누군가 혹 열심히 따라했는데.. 안되는 사람이 있을까 하는 노파심에 이 페이지 어딘가에
전체 소스 파일을 압축한 파일이 숨어 있을거시당.(찾을 필요가 있다면 그거이 보물이당!)
찾아서 압축을 풀고 npm i 만 하고 실행시켜 본다면 잘 될거시당. 행운이 함께 하길!~~^-^
헌 걸 잊으나, 아이 잘 되려나
새 걸 잊으나, 아이 맘 쓰리나
마음이 잊으나, 나 가벼우리나
두려움 잊으나, 나 가버리나
이즈나 잊으나, 잊으나 이즈나
IS 나?, 나 잊으나 이즈나
금수저 잊으나, 마데카솔 바르나
이즈나 갈레나 잊으나 가려나
이런 내가 시르나 미우나
아프나 약 발라야하나 먹어야 하나
온 나나나나 정신 차려야 하나
이제나 놀라야 하나 온나나나나
https://www.youtube.com/watch?v=d3mqW9wqqx0
숨기나 마나 잊으나 여기 파일 isNa
React(리액트) 티키타카 21 (Redux -1) (0) | 2025.03.04 |
---|---|
도커(Docker) 이미지로 맹글깅 (0) | 2025.02.20 |
React(리액트) 티키타카 20 ( useState 훅 동작 감 잡아보기) (1) | 2024.12.30 |
React(리액트) 티키타카 19 ( FullCalendar) (3) | 2024.12.29 |
React(리액트) 티키타카 18 ( rc-tree 트리 컴포넌트) (6) | 2024.12.25 |