🔍 reducer
💻 action과 reducer
- action은 store로 운반할 데이터를 말한다. 상태 변화를 담은 객체
- 이 액션이 reducer라는 함수로 전달 되고 이 함수가 넘겨 받은 action은 가지고 새로운 state를 만든다.
- action → reducer → store
let user = createSlice({
name: 'userName',
initialState: 'brgndy',
reducers: {
changeName(state) {
return state + 'jeon';
}
}
})
- reducer를 이용해서 changeName이라는 함수를 만들어 주었다.
- 저 매개변수로 들어간 state는 기존에 있던 초기 state 값이다.
💻 reducer로 만든 함수 export하기
export let {changeName} = user.actions
- 디스트럭쳐링 문법을 이용해서 user 안에 action들을 export 해주면 된다.
- 만약 state 변경 함수가 여러개라면 {changeName, …, …} 이런 식으로 작성해주면 된다.
💻 사용할 컴포넌트에서 import 해서 dispatch()사용하기
function Cart() {
let name = useSelector(state => return state.user)
let dispatch = useDispatch();
return(
{name}
<button onClick={() => dispatch(changeName())}>
+
</button>
❓ `dispatch`란?
- 보내다라는 뜻으로 dispatch 안에서 사용할 action을 넣어 이걸 reducer로 보내주는 것
예시)
let user = createSlice({
name: 'username',
initialState: {name: 'brgndy', age: 27},
reducer: {
addAge: {
state.age +=1;
}
}
})
📌 payload
- 사전적 의미로 탑재량인데, dispatch를 통해 함수만 보내는 것이 아니라 payload라는 구체적인 데이터값도 reducer로 보내는 것
let user = createSlice({
name : 'userName',
initialState : {name : 'brgndy', age : 27},
reducers : {
addAge(state, action){
state.age += action.payload;
}
}
})
<button onClick={() => return dispatch(addAge(100))}}>
+
</button>
- 이렇게 되면 버튼을 누를때마다 100씩 늘어난다.
🔍 Redux-saga
- react/redux 애플리케이션의 사이드 이펙트, 데이터 fetching이나 브라우저 캐시에 접근하는 순수하지 않은 비동기 동작들을 쉽고 보기 좋게 만드는 것을 목적으로하는 라이브러리
- redux에서는 action을 발생시키면 reducer를 통해 state를 변화시켜 store를 갱신함
- action → reducer로 state 변화 → store에 갱신
- redux-saga는 action과 reducer 사이에 흐름을 제어하는 미들웨어
- action → middleware → reducer → store 갱신
- redux-saga는 action이 발생하면 reducer가 액션을 처리하기 전에 다양한 작업을 함
- 기존 요청을 취소 처리하거나 불필요한 중복 요청을 방지
- 비동기 작업을 처리하는데 효과적
- 특정 액션이 발생했을 때, 다른 액션을 발생시키거나 리덕스와 관계없는 코드 실행시 사용
- 즉, redux-saga를 이용하면 보다 간편하고 깊게 state의 흐름을 제어할 수 있음
💻 도입하는 이유
- api 호출 로직을 효율적으로 관리할 수 있다.
- 오로지 비동기 작업만을 위해 redux-saga를 쓰는 것은 효율적이지 않다.
- async/await라는 효율적인 기능으로 비동기 동작을 처리하는데 굳이 코드량을 늘려가면서 saga를 쓸 필요가 있을까?
- 하지만, saga는 비동기 동작에 대해 세부적으로 컨트롤할 수 있다.
- 예를 들면, 사용자의 부주의로 동일한 api를 여러번 호출하면, 가장 마지막 호출의 response만 받아오도록 제어할 수 있다.
- 무엇보다 api 호출 로직을 saga에서 관리하면, presentational 컴포넌트와 container 컴포넌트의 명확한 분리가 가능해졌다.
- 같은 api를 다른 페이지에서 호출 시 같은 코드를 두번 적어야 했다면, api 호출 로직을 redux-saga로 관리하면서 데이터를 간편하게 가져올 수 있게 되었다.
- callback 함수를 action payload로 넘길 수 있다.
- 공통으로 사용할 모달창을 구현했다. 이 모달창의 확인 버튼을 누르면 모달창을 호출한 페이지에서 그 사실을 알아야 하는데 이 로직을 구현하는데 많은 고민을 했다
- 모달창을 열 때 페이지의 callback 함수를 함께 넘겨주는 것이 가장 좋은 방법이지만, redux에서는 callback을 state 값으로 저장하는 것을 권장하지 않는다.
- saga를 사용하면 callback 값을 saga에서 처리해주기 때문에 action payload로 callback을 넘길 수 있게 되었다.
- 공통으로 사용할 모달창을 구현했다. 이 모달창의 확인 버튼을 누르면 모달창을 호출한 페이지에서 그 사실을 알아야 하는데 이 로직을 구현하는데 많은 고민을 했다
📌 “콜백 함수(callback function)를 액션의 payload로 넘길 수 있다”
- 액션이 디스패치(redux 스토어에 보내는) 될 때 전달하는payload에 콜백함수를 포함시킨다.
- 액션을 처리하는 사가나 리듀서에 해당 콜백함수를 실행하여 특정 로직을 수행할 수 있다. 이는 비동기 작업 후 특정 동작을 트리거하거나, API 호출 후 후속 처리를 할 때 유용
- 페이로드: 액션 객체에 포함되는 데이터
- 액션과 함께 전달되는 추가 정보, 리듀서나 사가가 액션을 처리할 때 필요한 데이터를 전달하는 역할
💻 동작 원리
- saga는 제너레이터 함수 문법을 기반하여 비동기 작업을 관리한다.
- 우리가 디스패치(발생, 영어 뜻은 ‘보내다’)하는 action을 모니터링해서 그에 따라 필요한 작업을 따로 수행할 수 있다.
📌 디스패치 액션을 redux 스토어에 보내는 행위 디스패치는 그 자체로 로직이나 작업을 실행하지 않는다. 디스패치된 액션은 리듀서에 의해 처리되어 상태를 변경하거나, 다른 미들웨어에 의해 비동기 작업을 트리거함
📌 generator란 무엇일까? generator 안에서 while(true)를 사용하면 무한으로 사용가능한 로직을 만들 수 있다. saga에서는 실제로 while(true)를 사용하여 지속적으로 action을 모니터링하고 action이 발생하면 해당하는 로직을 수행한다.
💻saga의 헬퍼 함수
- delay
- 설정된 시간 이후에 resolve를 하는 Promise 객체를 리턴
- put
- 특정 액션을 dispatch한다. (ex. put({type: ‘INCREMENT’}) )
- 액션을 디스패치하여 상태를 변경하거나, 다른 사가가 이 액션에 반응하도록
- 쉽게 설명하자면, 액션을 redux 스토어에 저장하여 상태를 변경하게 하는 동작
- 비동기 작업이 완료된 후 상태 업데이트, 에러 발생 시 에러 액션 디스패치 등 다양한 상황에서 사용
function* fetchDataSaga() {
try {
const data = yield call(fetchDataFromAPI);
yield put(fetchDataSuccess(data));
// 데이터를 가져오면 성공 액션을 디스패치
} catch (error) {
yield put(fetchDataFailure(error));
// 에러가 발생하면 실패 액션을 디스패치
}
}
- call
- 주어진 함수를 실행한다.(ex. call(delay, 1000) )
- 미들웨어가 Promise의 resolve를 기다리게 하기 때문에 동기 함수를 사용한다.
- blocking effect가 있어서 호출이 종료되기까지 아무것도 못한다.
📌 함수를 실행한다는 것
- 특정 로직을 직접 실행하는 것을 의미
- 비동기 함수라면 완료될 때까지 기다리거나, 동기 함수라면 즉시 결과를 반환
- 함수 실행은 결과적으로 특정 작업을 수행하거나, 그 결과를 사용하거나 반환하는 과정
- take
- 들어오는 특정 액션을 처리한다. 한번 실행되고 이벤트가 삭제된다.
- 특정 액션이 디스패치될 때까지 대기하는 함수
- 이 함수를 호출하면 해당 액션이 발생될 때까지 제너레이터 함수의 실행이 중단된다.
- takeEvery
- 모든 리퀘스트에 대해 task를 실행한다.
- 만약 fetchData task가 시작되었을 때 이미 이전 task가 실행 중이라면, 이전 task는 자동으로 취소된다.
function* watchFetchData() {
yield takeEvery('FETCH_REQUESTED', fetchData)
- fork
- 백그라운드에서 task가 실행된다.
📌 백그라운드 작업이란?
- 특정 작업이 비동기적으로 실행되며, 이를 취소하거나 반복하는 상황
function* loginFlow() {
while(true) {
const {user, password} = yield take('LOGIN_REQUEST')
cosnt token = yield call(authorize, user, password)
if(token) {
yield call(Api.storeItem, {token})
yield take('LOGOUT')
yield call(Api.clearItem, 'token')
}
}
}
- 위와 같은 로그인 로직이 있다.
- 이 로직은 로그인을 하고(’LOGIN_REQUEST’), 사용자 인증을 거치면 ‘LOGOUT’ task를 기다린다.
- 만약 ’LOGIN_REQUEST’가 실행되고, token을 받아오는 중에 사용자가 ‘LOGOUT’ task를 실행한다면 어떻게 될까? ‘LOGOUT’ task는 무시된다. call은 봉쇄(blocking) effect라서 호출이 종료되기 전까지는 아무것도 수행할 수 없다.
function* loginFlow() {
while(true) {
...
try {
//non-bloking call, what's the returned value here?
const ?? = yield fork(authorize, user, password)
...
}
..
}
}
- 이럴때 fork를 사용하면 task는 백그라운드에서 시작되고, 호출자는 fork된 task가 종료될 때까지 기다리지 않고 플로우를 계속해서 진행된다.
- 단, fork는 백그라운드에서 실행되기 때문에 token을 받아올 수 없다. 이럴 때는 token을 authorize 안에서 받아와야 한다.
function* authorize(user, password) {
try{
cosnt token = yield call(Api.authorize, user, password)
...
} catch (error) {
...
}
}
function* loginFlow() {
while(true) {
...
yield fork(authorize, user, password)
...
}
}
- 그러면 위와 같은 로직이다.
- 만약 finally 구간에서 제너레이터가 취소된건지 확인이 필요하다면 yield cancelled()으로 확인 가능하다
- cancel
- fork된 task를 취소시킨다(yield cancel(task))
- 제너레이터를 finally 구간에 가게 된다. 이때 취소된 task 하위에 다른 task가 포함되어 있다면 모두 취소된다.
- all
- 이 함수를 사용해서 제너레이터 함수를 배열의 형태로 넣어주면, 제너레이터 함수들이 병행적으로 동시에 실행되고, 전부 resolve 될때까지 기다린다.(Promise.all과 비슷하다)
'정리 모음 ✨' 카테고리의 다른 글
[자바스크립트] string 메서드 정리 ✨ (0) | 2024.11.12 |
---|---|
[자바스크립트] math 메서드 정리 ✨ (1) | 2024.09.20 |
[자바스크립트] Date 메서드 정리✨ (1) | 2024.09.08 |
[자바스크립트] 배열 메소드 정리 ✨ (1) | 2024.01.16 |