Skip to content
Search

Redux

lastmod 2025-06-04created 2017-01-3026 edits

Redux is an open-source JavaScript library, used to manage the state of a website or application using state.

Categories

  • redux-persist
  • redux-logger
  • Redux Toolkit (redux-toolkit)
  • redux-thunk - The middleware most commonly used to handle asynchronous work in Redux. With this middleware, you can dispatch functions instead of action objects.
    • Pros: lets you dispatch several things at once
    • Cons: that's all it does; you have to implement everything else yourself
  • redux-saga - Middleware for handling asynchronous work. Whereas Redux-thunk is middleware that lets you dispatch functions, Saga monitors actions, and when a specific action occurs, a specific task is performed according to predefined logic. Logic can also be handled with pure functions called Sagas, and because they are pure functions, there are fewer side effects and test code is easy to write.

Pros of Redux

  • Makes state predictable. (because it uses pure functions)
  • Maintainability (compared with complex state management)
  • Good for debugging (when logging actions and state) → redux dev tool (Chrome extension)
  • Easy to add tests (because it uses pure functions)

Basic concepts of Redux: three principles

Single source of truth
The same data always comes from the same place.
That is, there is a single data space called the store.
State is read-only
In React, state can only be changed using the setState method.
In Redux too, state can only be changed through objects called actions.
Changes are made with pure functions
Changes can only be made with pure functions.
This concept is related to reducers.
Store – Action – Reducer

Meaning and characteristics of Store, Action, and Reducer

Redux-state-step.png

This is because data must flow in only one direction.

Store

  • The Store is the one and only place where state is managed.
  • There is a space called the store, separate from components, and the state the app needs is kept in that store.
  • Components access the store when they need state information.

Action

  • An Action is the data the app carries to the store. (an order form)
  • An Action takes the form of a JavaScript object.

Reducer

  • An Action is not passed directly to the Store.
  • The Action must be passed to the Reducer.
  • The Reducer looks at the order and updates the state of the Store.
  • To pass an Action to the Reducer, you must use the dispatch() method.

Two ways to implement the above concepts in Redux

  • mapStateToProps()
  • Redux hooks
    • useSelector
    • useDispatch

Installation for use with React

npm install --save redux react-redux @reduxjs/toolkit
npm install --dev @types/react-redux

Provider

import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
import './index.css';

// Redux 관련 불러오기
import { createStore } from 'redux'
import reducers from './reducers';
import { Provider } from 'react-redux';

// 스토어 생성
const store = createStore(reducers);

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

Deprecated Example

WARNING

This is deprecated, so use #@reduxjs/toolkit Example instead

import { createStore } from 'redux'

/**
 * 이것이 (state, action) => state 형태의 순수 함수인 리듀서입니다.
 * 리듀서는 액션이 어떻게 상태를 다음 상태로 변경하는지 서술합니다.
 *
 * 상태의 모양은 당신 마음대로입니다: 기본형(primitive)일수도, 배열일수도, 객체일수도,
 * 심지어 Immutable.js 자료구조일수도 있습니다.  오직 중요한 점은 상태 객체를 변경해서는 안되며,
 * 상태가 바뀐다면 새로운 객체를 반환해야 한다는 것입니다.
 *
 * 이 예제에서 우리는 `switch` 구문과 문자열을 썼지만,
 * 여러분의 프로젝트에 맞게
 * (함수 맵 같은) 다른 컨벤션을 따르셔도 좋습니다.
 */
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 앱의 상태를 보관하는 Redux 저장소를 만듭니다.
// API로는 { subscribe, dispatch, getState }가 있습니다.
let store = createStore(counter)

// subscribe()를 이용해 상태 변화에 따라 UI가 변경되게 할 수 있습니다.
// 보통은 subscribe()를 직접 사용하기보다는 뷰 바인딩 라이브러리(예를 들어 React Redux)를 사용합니다.
// 하지만 현재 상태를 localStorage에 영속적으로 저장할 때도 편리합니다.

store.subscribe(() => console.log(store.getState())))

// 내부 상태를 변경하는 유일한 방법은 액션을 보내는 것뿐입니다.
// 액션은 직렬화할수도, 로깅할수도, 저장할수도 있으며 나중에 재실행할수도 있습니다.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

@reduxjs/toolkit Example

Use it as follows: app/store.js:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

features/counter/counterSlice.js:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { fetchCount } from './counterAPI';

const initialState = {
  value: 0,
  status: 'idle',
};

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state) => state.counter.value;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd = (amount) => (dispatch, getState) => {
  const currentValue = selectCount(getState());
  if (currentValue % 2 === 1) {
    dispatch(incrementByAmount(amount));
  }
};

export default counterSlice.reducer;

features/counter/counterAPI.js:

// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ data: amount }), 500)
  );
}

index.js:

import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './index.css';

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Usage example:

import { useSelector, useDispatch } from 'react-redux';
import {
  decrement,
  increment,
  incrementByAmount,
  incrementAsync,
  incrementIfOdd,
  selectCount,
} from './counterSlice';

export function Counter() {
  const count = useSelector(selectCount);
  const dispatch = useDispatch();

  dispatch(decrement());
  dispatch(increment());
  dispatch(incrementByAmount(2));

  // ...
}

See also

  • React
  • LiveStore - A state management solution using reactive SQLite and a built-in sync engine

Favorite site

Comparison of state management libraries

References


  1. WebFrameworks_-_Making_sense_of_Redux.pdf ↩

author Zer0 sitemap