본문 바로가기

개발개발

Redux Style Guide

이 글은 https://redux.js.org 의 Redux Style Guide 페이지를 번역한 글입니다. 오역에 주의 바랍니다.

소개

이글은 리덕스 코드를 작성하기 위한 공식 스타일 가이드입니다. 리덕스 어플리케이션을 작성하기 위한 접근 방법, 권장 패턴, 모범 사례를 소개합니다.

리덕스 코어 라이브러리나 대부분의 리덕스 문서에는 이런 지침이 없습니다. 리덕스를 사용하는 방법은 많지만, 항상 올바르게 사용하는 방법은 없었습니다.

하지만, 오랜 경험상 확실히 특정 접근법이 더 좋은 일부 주제들이 있다는게 보여졌고, 많은 개발자들이 고민을 줄이기 위해 공식적인 지침을 요구하였습니다.

이를 염두하고, 에러, 시간낭비, 안티패턴을 피하도록 권장사항 목록을 정리하였습니다. 다양한 환경과 프로젝트마다 요구사항이 다르기때 문에 모든 상황에 맞는 스타일 가이드는 있을 수 없습니다. 이러한 권장 사항을 따르는것이 좋지만, 시간을 내어 자신의 상황과 요구에 맞는지 확인해야 합니다.

마지막으로, 이 페이지 작성에 영감을 준 뷰 스타일 가이드 페이지를 작성한 뷰 문서 작성자들에게 감사를 표합니다.

규칙 분류

우리는 규칙을 3가지 범주로 나누었습니다.

중요도 A : 필수

이 규칙은 에러를 방지하므로 모든 수단을 동원해서 익히고 따라야합니다. 예외는 있을 수 있지만, 매우 드믈어야하고, JS와 리덕스에 전문 지식이 있는 사람에 의해서만 허용됩니다.

중요도 B : 강력히 추천

이 규칙은 프로젝트에서 가독성이나 개발자의 경험을 향상 시킬것입니다. 이 규칙을 위반하더라도 코드는 계속 작동하겠지만, 이런 위반들은 드믈어야하고 정당한 이유가 있어야 합니다. 가능하다면 이 규칙을 따라야합니다.

중요도 C : 권장

홀륭한 옵션들이 여러개 있을 경우, 일관성을 유지하기 위해 그 중 하나를 선택을 할 수 있습니다. 이 규칙은 선택 가능한 옵션들을 설명하고, 기본 선택을 제안합니다. 이 말은 코드에서 일관성과 합리적인 이유가 있다면 다른 선택을 해도 된다는 의미입니다. 부디 합리적인 이유여야 합니다.

중요도 A 규칙 : 필수

상태값을 직접 변경하지 마라

상태값을 직접 변경 하는것은 컴포넌트가 제대로 렌더링되지 않는 등, 리덕스 어플리케이션 버그의 가장 일반적인 원인이며, 리덕스 개발툴에서 타임트래블 디버깅 오류를 발생시킵니다. 상태값의 직접 변경은 항상 피해야하며, 리듀서에서나 앱의 다른 코드에서도 해서는 안됩니다.

개발중에 값의 변경을 감지하는 redux-immutable-state-invariant툴이나, 상태값의 직접 변경을 막아주는 Immer 같은 툴을 사용하십시오.

참고: 복제된 값을 변경하는 것은 허용됩니다 - 이 방법은 직접 변경없이 수정하는 일반적인 방법중 하나입니다. Immer 라이브러리를 사용하게되면 값을 "직접 변경"하더라도 실제로 값의 수정을 하지 않기 때문에 괜찮습니다. - Immer는 안전하게 변경사항을 추적하고, 내부적으로 직접 변경없이 수정된 값을 생성합니다.

리듀서는 부수적인 효과가 없어야 한다

리듀서는 상태와 액션의 인자에만 의존해야 하고 이 인자들을 기반으로 새로운 상태값을 계산하고 반환해야 합니다. 비동기 동작(AJAX 호출, 타임아웃, 프라미스)을 실행하거나 임의의 값(Date.now(), Math.random())을 생성하거나, 리듀서 외부의 변수 값을 수정하거나 리듀서 함수의 외부 영역에 영향을 주는 코드를 실행해서는 안됩니다.

참고: 이 규칙을 따르는 라이브러리나 유틸함수를 임포트해서 리듀서 외부에 정의된 함수를 호출하는것은 허용됩니다.

상세 설명

이 규칙의 목적은 리듀서가 예측가능하게 동작하도록 하기 위해서입니다. 예를 들어, 타임트래블 디버깅을 하게되면, 리듀서는 현재 상태 값을 만들기 위해 이전의 여러 액션들을 호출할 것입니다. 만약 리듀서가 부수적인 효과를 가지고 있다면, 디버깅 실행 중에 이러한 부수적인 효과가 원인이되어, 어플리케이션이 예기치 않은 방식으로 동작하게 됩니다.
이 규칙에는 약간 모호한 부분이 있습니다. 엄밀하게 말해, console.log(state) 같은 코드는 부수적인 효과는 맞지만, 실제로 어플리케이션 동작에 아무런 영향을 미치지 않습니다.

직렬화가 되지 않는 값을 상태나 액션에 사용 하지마라

리덕스 스토어 상태값이나 디스패치된 액션에 직렬화 되지 않는 프로미스(Promise), 심볼(Symbols), 맵/셋(Maps/Sets), 함수나 클래스 인스턴스 같은 값들을 사용하는것을 피해야합니다. 이 규칙은 리덕스 개발 툴을 통한 디버깅 같은 기능이과 UI 업데이트 동작을 정상적으로 동작하게 해줍니다.

예외: 액션에 사용된 값이 리듀서에 도착하기 전에 미들웨어에 의해 가로채질 수 있다면 직렬화 되지 않은 값을 사용해도 됩니다. 리덕스-썽크나 리덕스-프로미스같은 미들웨어가 이에 해당합니다.

하나의 앱은 하나의 리덕스 저장소만 사용해야 한다

일반적인 리덕스 어플리케이션은 어플리케이션 전체에서 사용될 단일 리덕스 저장소를 가져야 합니다. 전형적으로 store.js 같은 파일로 분리되어 정의됩니다.

앱이 직접적으로 저장소를 직접 가져오기 보다는, 리엑트 컴포넌트 트리를 통해 전달되거나 성크 같은 미들웨어를 통해 간접적으로 참조하는게 이상적입니다. 드물게 별도의 파일에서 집적 사용하기도할 수 있지만 최후의 방법이어야 합니다.

중요도 B 규칙 : 강력하게 추천

리덕스 코드를 작성할때 리덕스 툴킷을 사용해야 한다

리덕스 툴킷은 리덕스 사용을 위해 추천하는 툴셋입니다. 상태변화를 감지하고, 리덕스 개발도구를 활성화시키도록 저장소를 설정하거나, Immer로 변경 불가능한 수정 코드를 단순화하는 등 우리가 제안한 권장 사례를 구축합니다.

리덕스를 사용하기 위해 리덕스 툴킷은 필수가 아닙니다. 필요하다면 자유롭게 다른 방법을 사용하면 됩니다. 하지만, 리덕스 툴킷을 사용하면 코드가 단순해 지고, 어플리케이션이 좋은 기본 설정을 가지게 됩니다.

변경 불가능한 수정을 위해 Immer를 사용해야 한다

직접 변경없이 수정하는 코드를 작성하는 것은 어렵고 오류를 발생시키기 쉽습니다. Immer를 사용하면 변경 가능한 코드를 사용해서 직접 변경없이 수정하는 로직의 작성을 단순하게 만들어 주고, 개발중인 상태값을 고정시켜 앱의 다른 곳에서 발생하는 상태변화를 감지할 수 있습니다. 리덕스 툴킷의 일부인 Immer를 사용하여 변경 직접 변경없는 수정 코드를 작성하는 것을 추천합니다.

기능이나 Ducks로 파일을 구조화해야 한다

리덕스는 어플리케이션 디렉토리와 파일 구조가 어떻게 되든 상관 없습니다. 하지만, 특정 기능에 연관된 코드를 한곳에 작성하면, 유지보수가 쉬워집니다.

이런 이유로 코드의 유형(리듀서, 액션 등등)에 따라 각각의 디렉토리로 분리하기보다는 "기능에 따른 디렉토리" 방법(특정 기능에 대한 모든 파일을 같은 디렉토리에 위치) 이나 "ducks" 패턴(특정 기능에 대한 모든 리덕스 연관 코드를 하나의 파일에 작성)을 사용하는 파일 구조를 추천합니다.

상세 설명

디렉토리 구조의 예는 다음과 같습니다:

  • /src
    • index.tsx
    • /app
      • store.ts
      • rootReducer.ts
      • App.tsx
  • /common
    • hooks, generic components, utils, etc
  • /features
  • /todos
    • todosSlice.ts
    • Todos.tsx

/app 은 다른 디렉토리에서 의존하는 어플리케이션 전체 설정과 레이아웃을 포함합니다.
/common 은 재사용되는 유틸리티와 컴포넌트를 포함합니다.
/features 는 특정 기능에 연관되 있는 모든 기능을 포함합니다. 예를 들어 todosSlice.ts 는 리덕스 툴킷의 createSlice() 함수에 대한 호출을 포함하고 리듀서와 액션 생성자를 제공하는 "duck" 스타일의 파일입니다.

리듀서에 가능한한 많은 로직을 작성해야 한다

새로운 상태값 계산을 코드에서 준비하고 디스패치하기보다는(Click 핸들러 같은) 리듀서에서 가능한한 많이 작성해야합니다. 그래야 실제 앱의 동작을 테스하기가 쉽고, 타임-트래블 디버깅또한 잘 동작며, 상태의 직접 변경이나 버그의 원인이되는 실수를 방지할 수 있습니다.

일부혹은 전체적으로 새로운 상태값이 먼저 계산되어야 하는 경우도 있지만(유일한 ID값을 생성하는것 같은) 최소한으로 유지해야 합니다.

상세 설명

리덕스는 새 상태값이 리듀서안에서 계산되든 액션 생성 코드에서 계산되든 신경쓰지 않습니다. todo앱을 예로 들면 `토글 todo` 액션은 todo의 배열에 직접 변경없는 수정을 필요로 합니다. 이때 리듀서에서 새로운 배열을 계산하고 액션은 todo의 ID만 포함시켜야 합니다.

// Click handler:
const onTodoClicked = (id) => {
     dispatch({type: "todos/toggleTodo", payload: {id}})
 }
 // Reducer:
case "todos/toggleTodo": {
     return state.map(todo => {
         if(todo.id !== action.payload.id) return todo;
         return {...todo, id: action.payload.id};
     })
 }

새 배열을 먼저 계산하고 액션으로 전달할 수도 있습니다.

 // Click handler:
 const onTodoClicked = id => {
   const newTodos = todos.map(todo => {
     if (todo.id !== id) return todo
     return { ...todo, id }
   })
   dispatch({ type: 'todos/toggleTodo', payload: { todos: newTodos } })
 }
 // Reducer:
 case "todos/toggleTodo":
     return action.payload.todos;

하지만 다음의 몇가지 이유 때문에 리듀서에서 비지니스 로직을 작성하는것이 좋습니다.

  • 리듀서는 순수 함수이므로 항상 테스트 하기 쉽습니다. const result = reducer(testState, action) 를 호출하고 예상되는 결과 인지 검증하면됩니다. 그러므로 리듀서에 많은 비지니스 로직을 작성할 수록 테스트 하기 쉬워집니다.
  • 리덕스 상태값은 불변성을 유지하면서 변경되어야 합니다. 대부분의 리덕스 사용자들은 리듀서 안에서는 불변성 유지의 룰을 지키지만, 리듀서 외부에서도 그렇게 해야할지는 확신하지 못합니다. 그래서 뮤테이션 같은 실수가 생기기도하고 스토어의 값을 읽어서 바로 액션을 통해 반환하기도 합니다. 리듀서에서 비지니스 작업을 작성하게되면 이런 실수를 피할 수 있습니다.
  • 만약 리덕스 툴킷이나 Immer를 사용한다면 리듀서에서 불변성을 유지하기가 쉽고, Immer는 뮤테이션을 방지 해줄 것입니다.
  • 타임-트래블 디버깅은 발행된 액션을 취소시키고 재실행 시키거나 다른 액션을 실행 시킬수 있도록 합니다. 일반적으로 리듀서의 핫-리로딩은 발행된 액션을 통해 리듀서를 재실행합니다, 잘못된 액션을 수정했지만 리듀서가 오류난채라면 리듀서를 수정하고 핫-로딩을 하면 고쳐진 상태값을 얻게됩니다. 액션에 오류가 있다면 발행된 액션을 순서대로 실행시키면됩니다. 그러므로 리듀서에 많은 로직이 있을수록 디버깅 하기 쉽습니다.
  • 리듀서에 로직을 작성하면 프로그램의 여러곳에 흩어진 코드를 찾지 않고 리듀서에서 해당 로직을 찾을 수 있습니다.

리듀서는 상태값의 타입을 알아야 한다

최상위 리덕스 상태는 단일 리듀서 함수로 이루어져 있습니다. 유지 관리를 위해 리듀서는 키/값의 조각으로 분할됩니다. 각 리듀서 조각은 리듀서 조각의 초기값과 해당 조각 상태에 대한 수정과 계산을 제공해야 합니다.

또한, 리듀서 조각은 계산된 상태의 일부로 반환되는 값들을 제어해야 합니다. return action.payloadreturn {...state, ...action.payload} 같은 "블라인드 스프레드/리턴"의 사용을 최소화 해야합니다. 컨텐츠를 올바르게 형식화하기 위해 액션을 디스패치한 코드에 의존하고 리듀서는 상태 타입을 모르기 때문입니다. 잘못된 액션의 컨텐츠는 버그로 이어질 수 있습니다.

참고: 스프레드 연산자를 사용해서 값을 반환 하는 리듀서는 폼에서 데이터를 수정하는것 같은 경우, 각 개별 필드들에 대해 여러 액션으로 작성하는것은 시간 낭비고 이득이 없으므로 적합한 선택일 수 있다.

상세 설명
 const initialState = {
     firstName: null,
     lastName: null,
     age: null,
 };
 export default usersReducer = (state = initialState, action) {
     switch(action.type) {
         case "users/userLoggedIn": {
             return action.payload;
         }
         default: return state;
     }
 }

이 예에서 리듀서는 `action.payload`가 완전하게 올바른 형식의 객체라고 가정합니다. 하지만, `user` 객체 대신 다른 객체가 액션 내부의 `todo` 객체로 디스패치 되었다고 가정해봅시다.

 dispatch({
   type: 'users/userLoggedIn',
   payload: {
     id: 42,
     text: 'Buy milk'
   }
 })

리듀서는 단순히 전달받은 todo 객체를 반환할 것이고, 리덕스 스토어를 통해 user를 읽으려고 하면 앱이 중단될 수 있습니다.
적어도 리듀서가 action.payload가 올바른 필드를 가지고 있는지를 확인하는 유효성 검사가 있거나 혹은 이름으로 올바른 필드를 읽으려고 했다면가 있었다면 오류를 방지할 수도 있었습니다. 이것은 코드의 추가 작성을 말하고, 코드의 안정성을 위해 실익을 따져 보아야 합니다.
정적 타입을 사용하면 이런 종류의 코드가 더 안전하고 좀더 좋습니다. 만약 리듀서가 액션이 PayloadAction 라는걸 안다면, action.payload를 반환하는것이 안전해질것입니다.

저장된 데이터에 기반하여 상태 조각의 이름을 지어라

`리듀서는 상태의 타입을 알아야 한다` 에서 언급한것 처럼 리듀서 로직을 분리하는 표준적인 접근은 상태의 조각에 기반하는 것입니다. combineReducers는 리듀서 조각을 더 큰 리듀서 함수로 결합하는 표준 함수입니다.

combineReducers에 전달되는 객체의 키 이름은 결과 상태값의 키 이름을 정의합니다.내부에 저장된 데이터의 이름을 따라 키 이름을 지정하고, "reducer" 같은 단어의 사용은 피해야 합니다. 객체는 `{usersReducer: {}, postsReducer: {}}` 보다는 `{users: {}, posts: {}}` 같아야 합니다.

상세 설명

ES6 객체 리터럴 단축을 사용하면 객체의 키 이름과 값을 한번에 쉽게 정의할 수 있습니다.

const data = 42
const obj = { data }
// {data: data}와 같다

combineReducers는 리듀서 함수인 객체를 받아서, 같은 키 이름을 가지는 상태 객체를 생성하는데 사용합니다. 이것은 함수 객체의 키 이름이 상태 객체의 키 이름을 정의한다는것을 의미합니다.

이로 인해 변수 이름에 "reducer"을 사용한 리듀서를 가져와서 객체 리터럴 단축 기능을 사용하여 combineReducers에 전달하게되는 일반적인 실수가 발생합니다.

import usersReducer from 'features/users/usersSlice'

const rootReducer = combineReducers({  
  usersReducer  
})

이 경우에, 객체 리터럴 단축의 사용은 `{usersReducer: usersReducer}` 같은 객체를 생성한다. 따라서, "reducer"는 이제 상태 키 이서에 있습니다. 이것은 중복되고 쓸모가 없습니다.

대신, 내부 데이터에만 관련되는 키 이름을 정의하십시오. 우리는 명확성을 위해 명시적으로 key:value 문법을 사용하는것을 제안합니다:

import usersReducer from 'features/users/usersSlice'  
import postsReducer from 'features/posts/postsSlice'

const rootReducer = combineReducers({  
  users: usersReducer,  
  posts: postsReducer  
})

더 많이 작성해야 하지만, 가장 이해하기 쉬운 코드와 상태 정의가 생성됩니다.

리듀서를 상태값 기계로 다루어야 한다

많은 리덕스의 리듀서들이 조건없이 작성됩니다. 현재 상태에 대한 논리에 기반하지 않고 발행된 액션만 보고 새로운 상태값을 계산합니다. 앱의 리듀서 외부의 로직에 의존하는 일부 액션들은 특정 순간에 유효하지 않을 수 있는 버그를 발생시킵니다. 예를들어 "요청 성공" 액션은 상태값이 이미 "로딩중"일때만 새로 계산된 상태값을 가져야합니다. 그리고 "항목 수정" 액션은 항목이 "수정중" 일때만 발행되어야 합니다.

이것을 고치기 위해서 리듀서를 액션자체를 조건없이 다루지 말고 현재 상태와 발행된 액션의 조합이 새 상태 값이 실제로 계산되는지를 결정하는 "상태 기계"처럼 다루어야 합니다.

상세 설명

유한상태기계(FSM)은 어느 한 순간에 하나의 상태에만 있어야하는 어떤것을 모델링하기에 유용합니다. 예를 들어 `fetchUserReducer` 리듀서가 가질 수 있는 상태는 다음과 같습니다.

  • "idle" (사용자 값 가져오기를 아직 시작하지 않음)

  • "loading" (지금 사용자의 값을 가져오고 있음)

  • "success" (사용자의 값을 성공적으로 가져옴)

  • "failure" (사용자의 값 가져오기에 실패함)
    상태값의 상태를 명확하게하고 유한 상태값을 가지는 속성을 특정한 값으로 지정하여 불가능한 상태로 만들 수 없게합니다.

const initialUserState = {
  status: 'idle', // 명확한 상태
  user: null,
  error: null
}

TypeScript를 사용하면 `discriminated unions`를 사용하여 각 유한 상태를 쉽게 표현할 수 있습니다. 예를 들어 `state.status === 'success'` 라면 stats.user 가 정의 될것을 기대할 것이고, state.error를 기대하지는 않을것입니다. 타입을 통해 이것을 강제할 수 있습니다.

일반적으로 리듀서 로직은 액션을 먼저 고려해서 작성됩니다. 상태 머신으로 로직을 모델링할때, 상태를 먼저 고려하는것이 중요합니다.각 상태에 대해 "유한 상태 리듀서"를 생성하면 상태별 동작을 캡슐화하는데 도움이됩니다.

   FETCH_USER,
   // ...
 } from './actions'
 const IDLE_STATUS = 'idle';
 const LOADING_STATUS = 'loading';
 const SUCCESS_STATUS = 'success';
 const FAILURE_STATUS = 'failure';
 const fetchIdleUserReducer = (state, action) => {
   // state.status is "idle"
   switch (action.type) {
     case FETCH_USER:
       return {
         ...state,
         status: LOADING_STATUS
       }
     }
     default:
       return state;
   }
 }
 // ... other reducers
 const fetchUserReducer = (state, action) => {
   switch (state.status) {
     case IDLE_STATUS:
       return fetchIdleUserReducer(state, action);
     case LOADING_STATUS:
       return fetchLoadingUserReducer(state, action);
     case SUCCESS_STATUS:
       return fetchSuccessUserReducer(state, action);
     case FAILURE_STATUS:
       return fetchFailureUserReducer(state, action);
     default:
       // this should never be reached
       return state;
   }
 }

이제 액션이 아닌 상태별로 동작을 정의하므로 불가능한 상태 전환을 막을 수 있습니다. 예를 들어 `FETCH_USER` 액션은 `status === LOADING_STATUS` 일때 아무런 효과가 없어야 하며, 엣지 케이스가 발생하지 않도록 강제할 수 있습니다.

Normalize Complex Nested/Relational State

Model Actions as Events, Not Setters

Redux does not care what the contents of the action.type field are - it just has to be defined. It is legal to write action types in present tense ("users/update"), past tense ("users/updated"), described as an event ("upload/progress"), or treated as a "setter" ("users/setUserName"). It is up to you to determine what a given action means in your application, and how you model those actions.

However, we recommend trying to treat actions more as "describing events that occurred", rather than "setters". Treating actions as "events" generally leads to more meaningful action names, fewer total actions being dispatched, and a more meaningful action log history. Writing "setters" often results in too many individual action types, too many dispatches, and an action log that is less meaningful.

Detailed Explanation
Imagine you've got a restaurant app, and someone orders a pizza and a bottle of Coke. You could dispatch an action like:

{ type: "food/orderAdded",  payload: {pizza: 1, coke: 1} }

Or you could dispatch:

{
    type: "orders/setPizzasOrdered",
    payload: {
        amount: getState().orders.pizza + 1,
    }
}
{
    type: "orders/setCokesOrdered",
    payload: {
        amount: getState().orders.coke + 1,
    }
}

The first example would be an "event". "Hey, someone ordered a pizza and a pop, deal with it somehow".
The second example is a "setter". "I know there are fields for 'pizzas ordered' and 'pops ordered', and I am commanding you to set their current values to these numbers".
The "event" approach only really needed a single action to be dispatched, and it's more flexible. It doesn't matter how many pizzas were already ordered. Maybe there's no cooks available, so the order gets ignored.
With the "setter" approach, the client code needed to know more about what the actual structure of the state is, what the "right" values should be, and ended up actually having to dispatch multiple actions to finish the "transaction".

Write Meaningful Action Names

The action.type field serves two main purposes:

  • Reducer logic checks the action type to see if this action should be handled to calculate a new state
  • Action types are shown in the Redux DevTools history log for you to read

Per Model Actions as "Events", the actual contents of the type field do not matter to Redux itself. However, the type value does matter to you, the developer. Actions should be written with meaningful, informative, descriptive type fields. Ideally, you should be able to read through a list of dispatched action types, and have a good understanding of what happened in the application without even looking at the contents of each action. Avoid using very generic action names like "SET_DATA" or "UPDATE_STORE", as they don't provide meaningful information on what happened.

Allow Many Reducers to Respond to the Same Action

Redux reducer logic is intended to be split into many smaller reducers, each independently updating their own portion of the state tree, and all composed back together to form the root reducer function. When a given action is dispatched, it might be handled by all, some, or none of the reducers.

As part of this, you are encouraged to have many reducer functions all handle the same action separately if possible. In practice, experience has shown that most actions are typically only handled by a single reducer function, which is fine. But, modeling actions as "events" and allowing many reducers to respond to those actions will typically allow your application's codebase to scale better, and minimize the number of times you need to dispatch multiple actions to accomplish one meaningful update.

많은 액션을 연속해서 발행하지 마라

하나의 트랜잭션을 만들기 위해 여러 액션을 연속적으로 발행해서는 안됩니다. 불가능하지는 않지만, 이 작업은 비교적 비싼 비용의 UI 변경을 여러개 발생시킵니다. 그리고 그 연속된 액션의 중간 상태(발행된 연속적인 액션이 일부만 처리된 상태)는 앱의 다른 로직의 일부에서 잠재적으로 유효하지 않을 수 있습니다. 단일 "이벤트" 타입의 액션을 선호해야합니다.

한번에 모든 적절한 상태 변경을 수행하는 단일 "이벤트" 타입 액션을 발행하거나, 마지막에 UI 변경하는 단일 액션으로 여러 액션들을 일괄 처리하는 애드온 액션을 사용하십시오

상세 설명

연속으로 발행하는 액션의 수에는 제한이 없습니다. 하지만 리덕스 스토어에서 구독중인 모든 콜백들을 실행시키고(리덕스에 연결된 UI 컴포넌트 당 하나이상), 그 결과 UI를 변경 시키게됩니다.
리액트 이벤트 핸들러 큐에 대기중인 UI 변경은 단일 리액트 랜더러에 전달되지만 이 이벤트 핸들러 외부에 대기중인 변경은 전달되지 않습니다. 비동기 함수, 타임아웃 콜백, 비-리액트 코드 들은 여기에 포함되지 않습니다. 이런 상황에서 각 액션들이 이전에 발행된 액션들이 끝나기 전에 동기적인 렌더러 함수로 전달되기 때문에 성능이 감소하게 됩니다.
게다가, 거대한 트랜잭션 개념 스타일의 연속적인 복수의 액션 발행은 유효하지 않은 중간 상태가 됩니다. 예를 들어 `UPDATE_A`, `UPDATE_B`, `UPDATE_C` 연속적으로 발행되는 3개의 액션이 있고, 어떤 코드가 a,b,c가 함게 변경될것이 예상될때, 처음 2개의 액션만 처리된 발행된 후에는 1개 혹은 2개의 변경만 되었기 때문에 상태값이 불완전할것입니다.
실제로 복수의 액션을 발행하는것이 필요하다면 어떤 방법으로 그 변경을 일괄 처리하는것을 고려해야합니다. 사례에 따라 리액트 자체의 렌더러를 사용할 수도 있고,(리액트 리덕스의 batch()) 스토어에 알리는 콜백함수를 디바운스(이벤트를 그룹화하여 일정 시간후에 하나의 이벤트로 발생하도록하는것)하거나, 여러개의 액션을 그룹화하여 하나의 구독자 알림만을 생성하는 거대한 던알 액션을 발행해야 합니다. 추가직인 예제와 애드온에 관련된 링크는 "스토어 업데이트 이벤트 줄이기"에 대한 FAQ를 참조하세요.

Evaluate Where Each Piece of State Should Live

Connect More Components to Read Data from the Store

Use the Object Shorthand Form of mapDispatch with connect

Call useSelector Multiple Times in Function Components

Use Static Typing

Use the Redux DevTools Extension for Debugging

Use Plain JavaScript Objects for State

중요도 C 규칙 : 추천

Write Action Types as domain/eventName

Write Actions Using the Flux Standard Action Convention

Use Action Creators

Use Thunks for Async Logic

Move Complex Logic Outside Components

Use Selector Functions to Read from Store State

Avoid Putting Form State In Redux