1. Slice 의 reducers 안에 들어가는 함수는 반드시 "순수함수" 여야한다.
"순수함수"는 동일한 input 에는 반드시 동일한 output 이 나오는 함수를 말한다.
대표적으로
const xxFn = ()=>{
return 3 + 4
}
"비순수 함수"는 사이드이펙트 가 있는 함수이다. 동일한 input 으로 다른 output 이 나오는 함수.
대표적으로
const xxFn = (xx) =>{
console.log(xx)
}
" xx 의 정의가 바뀌면, 다른 값이 log 로 나온다."
2. 대표적으로 안되는 함수 " fetch"
: http 요청을 위한 fetch 는 비동기 함수이자, 사이드이펙트가 있는 가장 많이 쓰이는 함수이다.
import {createSlice} from "@reduxjs/toolkit";
cosnt xxSlice = createSlice({
name : "",
initialState: "...",
reducers:{
addItmes(state,action){
...
fetch( ... ) // ==>> [ X ] [이런게 안된다!] 비동기 함수는 사용 할 수 없다. ***
}
})
3. 그렇다면, 비동기, 사이드이펙트가 있는 함수는 어떻게 리덕스로 처리할까?
[리듀스 함수안에 일반적인 순수함수 일 경우]
1) 컴포넌트에서 버튼
2) 버튼이 action을 dispatch
3) action은 자신의 논리로 즉각적으로 state 를 변경
[리듀스 함수안에 일반적인 비순수함수 일 경우]
1) 컴포넌트에서 버튼
2) 일반 함수를 호출 ( 일반 함수 안에는 비동기 함수와 그 결과를 가지고, State 를 변경할 dispatch 가 들어가있다 )
3) 일반 함수는 버튼에 의해 실행되고, 그 안에서, 비동기 함수를 실행하고,
그 결과만을 action 논리에 집어넣어 state 를 변경
>> 결과적으로, reducer 함수 안에는, 비동기 함수는 없고, 간단한 State 변경을 하는 정도
** 그 일반함수를 잘 만드는 것이 중요
** 그 일반함수를 어디에 둘지 도 중요
** 그 일반함수를 ( 평소 하듯 ) 그저 컴포넌트 안에서 fetch 할 수도 있고 ( 컴포넌트 Fat )
** 그 일반함수를 리덕스에서 만든다면, Thunk 액션 생성자라 한다. ( Store Fat )
4. 복잡하게 생각 할 것 없다.
: 그냥 리듀서 함수를 통해, fetch 를 하여 State 를 변경하는 것이 아니고
fetch 한 값을 reducer 함수에 넣어 State 를 변경할 뿐이다.
리덕스에서 fetch 사용하여 DB PUT 요청으로 값 넣기
[ 방법1 : 컴포넌트에서 직접 fetch 하기 WITH, useEffect ]
[ 의도 ]
1) Slice 안에는, action 의 논리에 의해, 모든 item이 담겨있는 배열의 state 가 존재한다.
2) 자동으로 혹은, 버튼을 눌렀을때, DB에 item 을 넣고싶다.
[ 프로세스 ]
1) 버튼을 누른다.
2) dispatch 에 의해 state 가 변경된다.
3) state 가 변경되면, useEffect 에 의해, DB 에 fetch 가 되도록, state 를 종속성으로 만든 useEffect 를 사용한다.
** Redux 를 사용할때는 PUT 이 더 좋을 수 있다. (이유는 코드에서 설명)
let initial = true // ** 처음 실행 했을때, 실행이 되지 않기 위한 initial [첫 실행 막기]
const Header = () => {
const itemState = useSelector((state) => state.item.items);
const itemDispatch = useDispatch();
const submitHandler = (e) => {
e.preventDefault()
itemDispatch(itemState.addItems()); // dispatch 에 의해, itemState 가 변경
...
};
useEffect (()=>{
if(initial){ // [첫 실행 막기] initial 이 true 라면, false 로 바꾸고 뒤에 코드는 실행시키지 않도록 한다.
initial = false
return
}
fetch( ... ,{
method: "PUT", // 이때 PUT 을 활용하면 좋다. (PUT은 새 값을 기존의 값에 "오버라이드" 한다. "추가가 아니고 오버라이드")
... // "전체 item 의 info 를 담고있는, state 이기 때문에, POST를 한다? 마치 팩토리얼처럼 더해질 것이다."
}) // [X], [X,X,Y] [X,X,Y,X,X,Y,Z,] ... 처럼 저장될것이다...
},[itemState]) // 종속성에 itemState
Redux 의 장점은 "전역으로 같은 State가 업데이트 된다는 것이다"
이것을 이용해, 굳이 fetch를 해당 컴포넌트 안에서 하지 말고, App.js 같은 곳에서 해도 된다.
import { useSelector } form "react-redux"
let initial = true // ** 처음 실행 했을때, 실행이 되지 않기 위한 initial [첫 실행 막기]
function App() {
const itemState = useSelector((state) => state.item.items); // 1. 다른 컴포넌트에 의해, item이 변경되면
useEffect (()=>{
if(initial){ // [첫 실행 막기] initial 이 true 라면, false 로 바꾸고 뒤에 코드는 실행시키지 않도록 한다.
initial = false
return
}
const sendData = async () =>{ // 3. 평범한 useEffect 에서 비동기 함수 실행시키기 방법
const response = await fetch( ... ,{
method: "PUT",
...
})
...
}
sendData() // 3. 평범한 useEffect 에서 비동기 함수 실행시키기 방법
},[itemState]) // 2. useEffect 의 종속성에 의해 실행된다.
return (
<Fragment>
<Header />
<Auth />
<Counter />
</Fragment>
);
}
export default App;
[첫 실행 막기] 에 대하여.
왜 initial 변수를 썼을까?
1. 컴포넌트에서 useEffect 를 사용한 fetch(비동기 함수)의 문제점
: PUT 을 사용하여, 값을 페칭하면, "초기값이 아무것도 없으므로, 만약 앱을 시작한다면 [기존에 있던] DB 가 비워지는 불상사가 생기고 말 것이다."
==> 앱을 처음 시작해서, "초기값이 초기화가 되지 않았다면, 아직 값을 DB 로 PUT 하지 않게 한다"
[추가. 컴포넌트가 RE-RENDER 될때에는, "컴포넌트 밖에 있는 것이랑은 아무련 관련이 없다는 것을 알자"]
2. 해결 : 초기에는 DB 로 넘기지 않기
: useEffect 는 초기 [앱이 시작될때] 한 번, 그리고, 종속성이 변경될 때마다 실행된다.
: 문제는 초기에 실행되게 하면 [있던 카트 DB 가 초기화 될 수 있다는 위험] 을 가지고 있다.
(예를들면, 장바구니에 담아놓고, 다시 껐다 키면, 장바구니가 초기화되어버리는 상황 심지어 DB까지 지워지니...)
: 그러므로, useEffect 를 사용하지만, "앱이 시작될때는 적용되지 않고", "오로지 종속성에만 영향을 받아야 한다."
<원리>
1. 컴포넌트 함수 밖에 initial 변수를 정의한다.
2. 이 값은, re-render 되도 전혀 영향을 받지 않는다.(초기화가 이루어지지지 않음. 밖 이므로)
3. 이 값을 이용하여, initial = true (앱 초기) 라면 useEffect에서 함수를 실행할때, 되돌려버리고, initial = false 로 변경한다.
4. 이렇게되면, 아무일도 없이 initial = false 만 만들고, 초기 phase 가 끝날 것이다.
5. "초기 phase 는 끝났으니", 이제 useEffect 는 "종속성에 의해서만 함수를 실행할 것이다."
**[ 방법2 : 리덕스를 이용하여 fetch 하기 WITH, useEffect ]
어? 리듀스 함수 안에서는 fetch 같은 비동기 함수를 사용 할 수 없을텐데!
걱정 마시라, 일반함수를 사용할 것이다.
대신 store 파일 (리듀서함수 아님)에 넣어서 사용 할 것이고,
또, dispatch 를 그 일반함수에 넣어, State 변경( 대부분 Modal, Notification 같은 UI)까지 한번에 이룰 것이다.
[ 방법1. 컴포넌트에서 하는 방법 ]
1) 버튼을 누르면, State 가 변경된다.
2) State가 변경되는 의존성에 의해, fetch 가 실행된다.
[ 방법2. 리덕스를 이용하여 fetch 하는 방법 ] : Thunk 액션 생성자를 활용한 방법
1) 버튼을 누르면 State 가 변경된다.
2) State가 변경되는 의존성에 의해, "정의한 일반함수" 가 실행된다.
3) 정의한 일반 함수는 "비동기 함수" 를 반환한다. dispatch 도 가능하다.
4) "비동기 함수는 fetch 를 실행한다."
5) 일반함수는 보통 "Store" 안에 존재한다.
Thunk 는, "액션 생성자"이다.
: 액션생성자라 하면, "컴포넌트에서, dispatch 를 사용할때, 사용하는 정의된 action 을 호출" 하는 것이 [액션생성자의 생성]이라 할 수 있다
import { counterAction } from "../store/index";
const Counter = () => {
const dispatch = useDispatch();
const toggleCounterHandler = () => {
dispatch(counterAction.toggleCounter()); // counterAction.toggleCounter() => 액션 생성자
1. Thunk 액션 생성자의 생성
0. 일반함수를 만든다
1. 일반함수의 return 함수 정의 (dispatch 사용하기 위해 dispatch를 파라미터에 넣음)
2. return 되는 함수 안에서, dispatch 하고~,
fetch 할 비동기 함수 정의 하고~,
fetch 비동기 함수 실행 하고~
그에 따른 UI 조작 위한 dispatch 또 하고~
하는걸 export 한다.
< /store/cart.js >
import { createSlice } from "@reduxjs/toolkit";
import { xxActions } from "../../.."
const initialCounterState = { counter: 0, showCounter: true };
const counterSlice = createSlice({ // 슬라이스(리듀서함수) 시작 [상관 X]
name: "counter",
initialState: initialCounterState,
reducers: {
increment(state) {
state.counter++;
...
}
}
})
// Thunk 액션 생성자 생성
export const sendCartData = ( inputData ) =>{ // 1. Slice(리듀서 함수) 밖에, "일반 자바스크립트 함수" 를 만든다.
return async (dispatch)=>{ // 2. "사용할 함수" 를 반환한다. [비동기함수로], 이유는 이 스코프 내에서 바로실행할거라서.
// disapath(state조작) 을 사용하기위해, 파라미터에는 dispatch
dispatch( xxActions.xxx ()) // 3. dispatch도 가능하다. (알림 같은거 사용하기 위한), 파라미터에 있는 dispatch 덕분에
const sendRequest = async () =>{ // 4. "fetch 를 할 중첩함수"를 정의
const response = await fetch(...)
if(!response.ok){
throw new Error(...)
}
const responseData = await response.json()
...
dispatch( xxActions.xxx ()) // 대충 성공했다는 notification 띄울 action ...
...
}
try{
await sendRequest // 5. "정의한 중첩함수 실행" [ 2.반환하는 함수가 async 인 이유]
dispatch( xxActions.xxx ())
}catch(error){
dispatch( xxActions.xxx ())
}
}
}
** dispatch 는 "파라미터로 쓴 것" 이므로 따로 import 하여 사용하는 것이 아니다.
** 이 함수 내의 dispatch 는 "현 State를 변형하기 위한 것이 아니라(할순 있지만, 해봐야 같은 함수 안에 있어서 최신 적용 안됨)"\
혹시 fetch 로 인해 바뀌는 UI( Modal, Notification 등..)의 State 를 변경하기 위한 dispatch 이다.
2. useEffect 를 통하여, Thunk 액션 생성자를 사용
<App.js>
import { useSelector, useDispatch } from "react-redux"
import { sendCartData } from "../..." // 1. Thunk 를 import
let initial = true // 2. useEffect 사용하므로, 초기에는 역시나 안되도록 설정
function App(){
const cart = useSelector(state => state.cart)
useEffect(()=>{
if(initial){
initial = false // initial 이 true 라면, false 로 바꾸고 뒤에 코드는 실행시키지 않도록 한다.
return
}
dispatch(sendCartData(cartData)) // 3. "dispatch" 를 사용하여, 실행
},[cart, dispatch])
}
** [ 차이점 ]
[방법들의 차이점] : "컴포넌트 lean" VS "Store lean"
1. "App.js" 나, "사용하는 컴포넌트"에서 "직접 fetch" 를 하는 방법.
: 어렵지 않고, 이렇게 해도 된다.
2. fetch 하는 "Thunk(액션생성자)" 를, "store 의 논리가 있는 곳"에 만들어, 컴포넌트에서 import 하여 사용하는 바업
: 조금 복잡하지만, "컴포넌트를 Lean 하게 만든다."
[누굴 더 lean 하게 만드냐의 차이만이 있다.] 예컨대, 아웃소싱 같은것이다.
보통은 Thunk 를 사용하는 편이 컴포넌트를 관리하기에 더 깔끔하지만, 컴포넌트 내부에서 1번을 할 수도 있다.
** 어디든, "Fat" 한 쪽을 조금더 "Lean" 하게 만드는 "코드 관리 차원에서 선택하면 좋겠다."
8편 예고.
Redux-ToolKit [ 비동기, 사이드 이펙트 있는 함수 다루기2 e.g. fetch함수] with useEffect *
리덕스에서 fetch 사용하여 DB 의 값 GET 요청하고 STATE 에 넣기
'Redux, Redux-toolkit' 카테고리의 다른 글
9. State 를 빼왔다면, 따로 변수에 저장해 사용하는 것이 좋아 보인다. (0) | 2022.12.22 |
---|---|
8. [ Redux-toolkit ] 리덕스에서 fetch 사용하여 DB 의 값 GET 요청하고 STATE 에 넣기 (1) | 2022.12.20 |
6. Redux-ToolKit [ State 다루는 몇가지 팁들 ] (0) | 2022.12.17 |
5. Redux-ToolKit [ Slice 분할하기 ] (0) | 2022.12.17 |
4. Redux-ToolKit [다중 슬라이스 사용해보기] (0) | 2022.12.17 |
댓글