본문 바로가기

공부기록/Frontend

thunk 공식문서보고 번역해보기! (엉망번역)

createAsyncThunk

개관

리덕스 액션타입인 스트링과 콜백 함수를 받는 함수는 프로미스를 반환해야 한다.

이것은 우리가 건네준 액션 타입 프리픽스를 기반으로

프로미스 라이프 사이클 액션타입을 생성하고,

썽크 액션 크리에이터 프로미스 콜백을 수행하고,

리턴된 프로미스에 기초하고 있는 라이프 사이클 액션디스패치할 것이다.

이 개관은 비동기 요청 라이프사이클을 다룰때 추천되는 기본 접근이다.

이것은 어느 리듀서 함수도 생성하지 않으며 당신이 가져오는 데이터가 뭔지 모르기 때문에, 어떻게 로딩 상태를 따를 것인지, 혹은 어떻게 반환하는 데이터가 수행될 것인지 모른다.

이러한 액션을 다루는 로직을 리듀서에 적어야 하며, 로딩 상태와 수행 로직이 당신의 앱에 적절한 로직이면 어느것이든지 함께 적어야 한다.

리덕스 툴킷의 RTK Query data fetching API는 리덕스 앱을 위해 데이터 페칭과 캐싱 솔루션을 수행하기 위해 만들어졌다.

그리고 썽크를 쓸 필요성이나 데이터 페칭을 관리하기 위해서 리듀서를 쓸 필요성을 제거할 수 있다. 당신의 앱의 데이터 페칭코드를 단순화하기 위해 도움을 줄 수 있을것이다.

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'

// First, create the thunk
const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId: number, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

interface UsersState {
  entities: []
  loading: 'idle' | 'pending' | 'succeeded' | 'failed'
}

const initialState = {
  entities: [],
  loading: 'idle',
} as UsersState

// Then, handle actions in your reducers:
const usersSlice = createSlice({
  name: 'users',
  initialState,
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: (builder) => {
    // Add reducers for additional action types here, and handle loading state as needed
    builder.addCase(fetchUserById.fulfilled, (state, action) => {
      // Add user to the state array
      state.entities.push(action.payload)
    })
  },
})

// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))

파라미터들

createAsyncThunk는 세개의 매개변수를 받는다.

  1. 액션 타입 값인 스트링
  2. payloadCreator인 콜백,
  3. 옵션 객체

type

추가적 리덕스 액션 타입 상수 를 생성하는데 사용될 스트링

동기 요청의 라이프사이클을 나타냄

예를들어 users/requestStatus 타입 인수는 아래와같은 action 타입을 생성할 것

pending: 'users/requestStatus/pending'
fulfilled: 'users/requestStatus/fulfilled'
rejected: 'users/requestStatus/rejected'

payloadCreator

비동기 로직의 결과를 담고있는 promise를 리턴해야 하는 콜백 함수.

동기적으로도 값을 리턴할 수 있다.

error가 있으면 에러객체를 포함하는 rejected 프로미스를 리턴하든지,

에러메시지를 서술하는것이나 rejectWithValue 인수 와같은 thunkAPI.rejectWithValue함수에 의해 리턴된 프로미스의 결괏값과 같은 일반적인 값을 리턴함

PayloadCreator함수는 적절한 결과를 계산할 수있는 어떤 로직이든 담을 수 있다.

이 함수는 AJAX데이터 페치 표준 요청, 마지막 값으로 결합될 수 있는 결과와 함께 부른 여러개의 AJAX와, 리액트 네이티브 asyncStorage와 상호작용 등등을 포함할 수 있다.

payloadCreator함수는 두가지 인수와 함께 호출 될 수 있다

arg :

dispatch되었을 때 썽크 액션 크리에이터에 추가된 첫번째 매개변수를 포함하고 있는 단일 값

이것은 요청의 일부로서 필요할 수 있는 아이템 아이디와 같은 값을 전달하는 데 유용할 수 있다.

만약 여러개의 값을 전달할 필요가 있다면 함께 thunk를 dispatch할 때 객체안에 전달하라.

dispatch(fetchUsers({status: 'active', sortBy: 'name'})).

이렇게

thunkAPI:

추가적 옵션 뿐만 아니라 리덕스 썽크 함수에 일반적으로 전달되는 모든 매개변수를 담고있는 객체 :

dispatch : 리덕스 스토어 dispatch 메서드

getState : 리덕스 스토어 getState메서드

extra: 가능하다면 셋업할 때 썽크 미들웨어에 전달된 추가적 매개변수

requestId : 요청 시퀀스를 판별하기 위해 자동적으로 생성된 고유한 ID 스트링 값

signal : 이 요청이 취소되어야 하는지 여부를 확인하는 데 사용할 수 있는 AbortController.signal 객체

rejectWithValue(value, [meta]): rejectWithValue는 액션 크리에이터에서 반환하거나 throw하여 정의된 페이로드와 메타를 사용하여 거부된 응답을 반환하는 유틸리티 함수입니다. 주어진 값을 취하고 거부된 액션의 페이로드에 해당 값을 반환합니다. meta도 전달하면 기존의 rejectedAction.meta와 병합됩니다.

fulfillWithValue(value, meta): fulfillWithValue는 액션 크리에이터에서 반환하여 값으로 채우는 유틸리티 함수로, fulfilledAction.meta에 추가할 수 있는 기능이 있습니다.

payloadCreator 함수의 논리는 필요에 따라 이러한 값을 사용하여 결과를 계산할 수 있습니다.

Options

다음과 같은 선택적 필드를 가진 객체:

  • condition(arg, { getState, extra }): boolean | Promise<boolean>: 원하는 경우 페이로드 크리에이터 및 모든 액션 디스패치 실행을 건너뛰는 데 사용할 수 있는 콜백입니다. 자세한 내용은 실행 전에 취소하기를 참조하십시오.
  • dispatchConditionRejection: condition()이 false를 반환하면 기본 동작은 액션이 전혀 디스패치되지 않습니다. Thunk가 취소되었을 때 여전히 "거부된" 액션이 디스패치되기를 원하는 경우이 플래그를 true로 설정하십시오.
  • idGenerator(arg): string: 요청 시퀀스의 requestId를 생성할 때 사용할 함수입니다. nanoid를 사용하도록 기본값으로 설정되어 있지만, 사용자 고유의 ID 생성 로직을 구현할 수 있습니다.
  • serializeError(error: unknown) => any: 내부 miniSerializeError 메서드를 사용자 지정 직렬화 논리로 대체하는 함수입니다.
  • getPendingMeta({ arg, requestId }, { getState, extra }): any: pendingAction.meta 필드에 병합될 객체를 생성하는 함수입니다.

Return Value

createAsyncThunk는 표준 Redux thunk 액션 크리에이터를 반환합니다. thunk 액션 크리에이터 함수에는 보류 중, 이행 및 거부 사례에 대한 평범한 액션 크리에이터가 중첩된 필드로 첨부됩니다.

위의 fetchUserById 예제를 사용하면 createAsyncThunk가 네 가지 함수를 생성합니다:

  • fetchUserById: 비동기 페이로드 콜백을 시작하는 thunk 액션 크리에이터
  • fetchUserById.pending: 'users/fetchByIdStatus/pending' 액션을 디스패치하는 액션 크리에이터
  • fetchUserById.fulfilled: 'users/fetchByIdStatus/fulfilled' 액션을 디스패치하는 액션 크리에이터
  • fetchUserById.rejected: 'users/fetchByIdStatus/rejected' 액션을 디스패치하는 액션 크리에이터

디스패치되면 해당 thunk는 다음을 수행합니다:

  • 보류 중인 액션을 디스패치합니다.
  • payloadCreator 콜백을 호출하고 반환된 프로미스가 해결되기를 기다립니다.
  • 프로미스가 해결되면:
    • 프로미스가 성공적으로 해결되면 action.payload로 프로미스 값을 사용하여 이행된 액션을 디스패치합니다.
    • 프로미스가 rejectWithValue(value) 반환 값을 사용하여 해결되면 action.payload에 전달된 값과 'Rejected'를 action.error.message로 사용하여 거부된 액션을 디스패치합니다.
    • 프로미스가 rejectWithValue로 처리되지 않고 실패하면 action.error에 오류 값의 직렬화된 버전을 사용하여 거부된 액션을 디스패치합니다.
  • 최종 디스패치된 액션 객체를 포함하는 해결된 프로미스를 반환합니다. (이행 또는 거부된 액션 객체 중 하나)

프로미스 라이프사이클 액션

createAsyncThunk는 createAction을 사용하여 세 가지 Redux 액션 크리에이터를 생성할 것입니다: pending, fulfilled, rejected.

각 라이프사이클 액션 크리에이터는 반환된 thunk 액션 크리에이터에 첨부되어 있어서 리듀서 로직이 액션 타입을 참조하고 액션이 디스패치될 때 해당 액션에 응답할 수 있습니다. 각 액션 객체는 action.meta 하에 현재 고유한 requestId 및 arg 값을 포함할 것입니다.

액션 크리에이터는 다음 시그니처를 가질 것입니다:

interface SerializedError {
  name?: string
  message?: string
  code?: string
  stack?: string
}

interface PendingAction<ThunkArg> {
  type: string
  payload: undefined
  meta: {
    requestId: string
    arg: ThunkArg
  }
}

interface FulfilledAction<ThunkArg, PromiseResult> {
  type: string
  payload: PromiseResult
  meta: {
    requestId: string
    arg: ThunkArg
  }
}

interface RejectedAction<ThunkArg> {
  type: string
  payload: undefined
  error: SerializedError | any
  meta: {
    requestId: string
    arg: ThunkArg
    aborted: boolean
    condition: boolean
  }
}

interface RejectedWithValueAction<ThunkArg, RejectedValue> {
  type: string
  payload: RejectedValue
  error: { message: 'Rejected' }
  meta: {
    requestId: string
    arg: ThunkArg
    aborted: boolean
  }
}

type Pending = <ThunkArg>(
  requestId: string,
  arg: ThunkArg
) => PendingAction<ThunkArg>

type Fulfilled = <ThunkArg, PromiseResult>(
  payload: PromiseResult,
  requestId: string,
  arg: ThunkArg
) => FulfilledAction<ThunkArg, PromiseResult>

type Rejected = <ThunkArg>(
  requestId: string,
  arg: ThunkArg
) => RejectedAction<ThunkArg>

type RejectedWithValue = <ThunkArg, RejectedValue>(
  requestId: string,
  arg: ThunkArg
) => RejectedWithValueAction<ThunkArg, RejectedValue>

이러한 액션을 리듀서에서 처리하려면, "빌더 콜백" 표기법을 사용하여 createReducer 또는 createSlice에서 액션 크리에이터를 참조하세요.

const reducer1 = createReducer(initialState, (builder) => {
  builder.addCase(fetchUserById.fulfilled, (state, action) => {})
})

const reducer2 = createSlice({
  name: 'users',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUserById.fulfilled, (state, action) => {})
  },
})

Thunk 결과 처리

결과 액션 언랩하기

Thunk는 디스패치될 때 값을 반환할 수 있습니다. 일반적인 사용 사례는 thunk에서 프로미스를 반환하고 컴포넌트에서 thunk를 디스패치한 다음 프로미스가 해결될 때까지 기다렸다가 추가 작업을 수행하는 것입니다.

const onClick = () => {
  dispatch(fetchUserById(userId)).then(() => {
    // do additional work
  })
}

createAsyncThunk에서 생성된 썽크는 항상 해결된 프로미스를 반환하며 적절하게 이행된 액션 객체 또는 거부된 액션 객체가 포함되어 있습니다.

호출 로직은 이러한 액션을 원래 프로미스 콘텐츠처럼 처리하고자 할 수 있습니다. 디스패치된 thunk에 의해 반환된 프로미스에는 언랩 속성이 있으며, 호출될 경우 이행된 액션의 페이로드를 추출하거나 거부된 액션에서 rejectWithValue에 의해 생성된 오류 또는 사용 가능한 경우 페이로드를 throw할 수 있습니다.

// in the component

const onClick = () => {
  dispatch(fetchUserById(userId))
    .unwrap()
    .then((originalPromiseResult) => {
      // handle result here
    })
    .catch((rejectedValueOrSerializedError) => {
      // handle error here
    })
}

Or with async/await syntax:

// in the component

const onClick = async () => {
  try {
    const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap()
    // handle result here
  } catch (rejectedValueOrSerializedError) {
    // handle error here
  }
}

Using the attached .unwrap() property is preferred in most cases, however Redux Toolkit also exports an unwrapResult function that can be used for a similar purpose:

import { unwrapResult } from '@reduxjs/toolkit'

// in the component
const onClick = () => {
  dispatch(fetchUserById(userId))
    .then(unwrapResult)
    .then((originalPromiseResult) => {
      // handle result here
    })
    .catch((rejectedValueOrSerializedError) => {
      // handle result here
    })
}

Or with async/await syntax:

import { unwrapResult } from '@reduxjs/toolkit'

// in the component
const onClick = async () => {
  try {
    const resultAction = await dispatch(fetchUserById(userId))
    const originalPromiseResult = unwrapResult(resultAction)
    // handle result here
  } catch (rejectedValueOrSerializedError) {
    // handle error here
  }
}

Checking Errors After Dispatching

 주의: 이는 thunk에서의 실패한 요청 또는 오류가 거부된 프로미스를 반환하지 않을 것이라는 것을 의미합니다.  

우리는 이 시점에서 어떠한 실패도 처리되지 않은 예외보다는 처리된 오류로 간주합니다. 

이는 디스패치의 결과를 사용하지 않는 경우에 대비하여 uncaught promise rejections를 방지하기 위함입니다.  

만약 컴포넌트가 요청이 실패했는지를 알아야 한다면,

 .unwrap이나 unwrapResult를 사용하고 다시 던져진 오류를 적절히 처리하십시오.  

거부된 액션의 내용을 사용자 정의해야 하는 경우, 오류를 스스로 catch하고 

thunkAPI.rejectWithValue 유틸리티를 사용하여 새 값을 반환하십시오.  

return rejectWithValue(errorPayload)을 사용하면 거부된 액션이 해당 값을 action.payload로 사용하게 됩니다.  

rejectWithValue 접근 방식은 API 응답이 "성공"하지만 리듀서가 알아야 하는 추가 오류 세부 정보가 있는 경우에도 

사용되어야 합니다. 이는 특히 API에서 필드 수준의 유효성 검사 오류를 예상할 때 흔히 발생합니다.

const updateUser = createAsyncThunk(
  'users/update',
  async (userData, { rejectWithValue }) => {
    const { id, ...fields } = userData
    try {
      const response = await userAPI.updateById(id, fields)
      return response.data.user
    } catch (err) {
      // Use `err.response.data` as `action.payload` for a `rejected` action,
      // by explicitly returning it using the `rejectWithValue()` utility
      return rejectWithValue(err.response.data)
    }
  }
)

Cancellation

Canceling Before Execution

If you need to cancel a thunk before the payload creator is called, you may provide a condition callback as an option after the payload creator. The callback will receive the thunk argument and an object with {getState, extra} as parameters, and use those to decide whether to continue or not. If the execution should be canceled, the condition callback should return a literal false value or a promise that should resolve to false. If a promise is returned, the thunk waits for it to get fulfilled before dispatching the pending action, otherwise it proceeds with dispatching synchronously.