본문 바로가기

기타/글로컬청년취업사관학교

[글로컬청년취업사관학교] [TIL] 240807

1. Redux 개요 및 개념 

 

- 애플리케이션 상태를 관리하고 업데이트하는 패턴 및 라이브러리 

- 애플리케이션의 "글로벌" 상태를 관리하는데 도움을 준다. 

- 상태가 많이 필요하거나 자주 업데이트되며, 상태를 업데이트하는 로직이 복잡한 경우 유용

 

2. Redux 용어 

 

1) 액션(Actions)

 

- 타입 필드를 가진 일반 JavaScript 객체 

- 타입 문자열을 "도메인/이벤트 이름" 형식으로 작성 

- 액션 객체는 발생한 일에 대한 추가 정보를 포함하는 다른 필드를 가질 수 있고, 그 정보를 payload 필드에 넣는다. 

 

const addTodoAction = {
 type: 'todos/todoAdded',
 payload: 'Buy milk'
}

 

2) 액션 생성자(Action Creators)

 

- 액션 객체를 생성하고 반환하는 함수 

const addTodo = text => {
	return (
    	type: 'todos/todoAdded',
        payload: text
    )
}

 

 

3) 리듀서(Reducers)

 

- 현재 상태와 액션 객체를 받아 상태를 업데이트할지 결정하고 새로운 상태를 반환하는 함수 

- 로직

  • 리듀서가 이 액션을 처리하는지 확인 
  • 그렇다면 상태를 복사하고 새로운 값으로 업데이트한 복사본을 반환
  • 그렇지 않으면 변경되지 않은 기존 상태를 반환  

ex)

const initState = {value:0}

function counterReducer(state=initState, action) {
	//리듀서가 이 액션을 처리하는지 확인
    if(action.type === 'counter/increment') {
    	//그렇다면, 'state'의 복사본을 확인
        return {
            ...state,
            //그리고 새 값으로 복사본을 업데이트
            value: state.value+1
         }
     }
     //그렇지 않으면 변경되지 않은 기존 상태를 반환
     return state
}

 

 

4) 스토어(Store)

 

-  현재 Redux 애플리케이션 상태는 스토어라는 객체에 저장된다. 스토어는 리듀서를 전달받아 생성되며, getState 메서드는 현재 상태 값을 반환

 

import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}

 

 

5) 디스패치(Dispatch)

 

- Redux 스토어는 dispatch라는 메서드를 가지고 있다. 

- 상태를 업데이트하는 유일한 방법은 store.dispatch()를 호출하고 액션 객체를 전달하는 것이다. 

- 스토어는 리듀서 함수를 실행하고 새 상태 값을 내부에 저장한다. 

- getState()를 호출하여 업데이트된 값을 검색할 수 있다. 

 

store.dispatch({type:'counter/increment'})

console.log(store.getState())
//{value:1}

 

 

6) 셀렉터(Selectors)

 

- 스토어 상태값에서 특정 정보를 추출하는 방법을 알고 있는 함수 

const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2

 

 

실행 단계  

 

 

1) 초기 설정 

  1. 루트 리듀서 함수를 사용하여 Redux 스토어가 생성
  2. 스토어는 루트 리듀서를 한 번 호출하고 반환 값을 초기 상태로 저장
  3. UI가 처음 렌더링될 때, UI 컴포넌트는 Redux 스토어의 현재 상태에 접근하고, 데이터를 사용하여 렌더링할 내용을 결정한다. 또한, 상태가 변경되면 이를 알 수 있도록 모든 미래의 스토어 업데이트에 구독한다. 

 

2) 업데이트 

  1. 애플리케이션에서 무언가 발생
  2. 애플리케이션 코드가 Redux 스토어에 액션을 디스패치 
  3. 스토어는 이전 상태와 현재 액션으로 루트 리듀서 함수를 다시 실행하고, 반환 값을 새로운 상태로 저장
  4. 스토어는 업데이트되었음을 모든 구독된 UI 컴포넌트에 알린다. 
  5. 각 UI 컴포넌트는 필요로 하는 상태 부분이 변경되었는지 확인
  6. 데이터가 변경된 것을 확인한 각 컴포넌트는 새 데이터를 사용하여 다시 렌더링 

 

 

3. Redux 앱 구조 

 

 

카운터 예제 앱 

 

 

Redux DevTools : 초기 앱 상태 

 

애플리케이션 구성 요소 

 

 

Redux 스토어 생성 

 

app/store.js

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


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

 

- Redux 스토어는 Redux Toolkit의 configureStore 함수를 사용하여 생성된다. 

- configureStore는 리듀서 인수를 전달받아야 한다. 

- 애플리케이션의 각 기능은 고유한 리듀서 함수를 가질 수 있다. 

- counterSlice.js 파일은 카운터 로직을 위한 리듀서 함수를 내보내는 역할을 한다. 이 함수를 여기에서 가져와 스토어를 생성할 때 포함시킬 수 있다. 

 

 

Redux Slices

 

- "슬라이스"는 일반적으로 하나의 파일에 정의된 단일 기능에 대한 Redux 리듀서 로직 및 액션의 모음이다.

 

ex) 블로그 앱 

- state.users, state.posts, state.comments는 각각 Redux 상태의 별도 "슬라이스"이다. 

import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'
import commentsReducer from '../features/comments/commentsSlice'
export default configureStore({
 reducer: {
 users: usersReducer,
 posts: postsReducer,
 comments: commentsReducer
 }
})

 

 

슬라이스 리듀서 및 액션 생성 

 

counterSlice.js

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

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

export const incrementAsync = createAsyncThunk(
  "counter/fetchCount",
  async (amount) => {
    const response = await fetchCount(amount);

    return response.data;
  }
);

export const counterSlice = createSlice({
  name: "counter",
  initialState,

  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },

    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },

  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;

export const selectCount = (state) => state.counter.value;

export const incrementIfOdd = (amount) => (dispatch, getState) => {
  const currentValue = selectCount(getState());
  if (currentValue % 2 === 1) {
    dispatch(incrementByAmount(amount));
  }
};

export default counterSlice.reducer;

 

- Redux Toolkit에는 createSlice라는 함수가 있어서 액션 타입 문자열, 액션 생성자 함수, 액션 객체를 생성하는 작업을 처리한다. 이 슬라이스에 대한 이름을 정의하고, 일부 리듀서 함수가 포함된 객체를 작성하면, 해당하는 액션 코드를 자동으로 생성한다. 

 

- name 옵션의 문자열은 각 액션 타입의 첫 번째 부분으로 사용되고, 각 리듀서 함수의 키 이름은 두 번째 부분으로 사용된다. 

 

ex) "counter" 이름 + increment"리듀서 함수는 {type: "counter/increment"} 액션 타입을 생성한다. 

 

- createSlice는 리듀서가 호출될 때 처음 상태가 될 초기 상태 값을 전달해야 한다. 이 경우, 초기 값이 0인 value 필드를 가진 객체를 제공한다. 

- createSlice는 작성한 리듀서 함수와 동일한 이름을 가진 액션 생성자를 자동으로 생성한다. 

 

 

리듀서와 불변 업데이트 

 

- Redux에서는 리듀서가 원본/현재 상태 값을 수정하는 것이 허용되지 않는다!

 

state.value = 123 //틀린 사용  

//올바른 사용 
return {
	...state,
    value: 123
}

 

- createSlice는 내부적으로 Immer라는 라이브러리를 사용한다. Immer는 Proxy라는 특수한 JS 도구를 사용하여 데이터를 래핑하고, 해당 래핑된 데이터를 "변경"하는 코드를 작성할 수 있게 한다. Immer는 시도한 모든 변경사항을 추적하고, 그 변경 목록을 사용하여 수동으로 작성한 것 처럼 안전하게 불변 업데이트된 값을 반환한다. 

 

- Redux Toolkit의 createSlice와 createReducer에서만  "변경" 로직을 작성할 수 있다. 

 

reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },

    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },

 

- increment 리듀서는 항상 state.value에 1을 더하고 decrement 리듀서는 1을 뺀다. Immer가 드래프트 상태 객체에 변경 사항을 감지하기 때문에 실제로 반환할 필요는 없다. 

 

- incrementByAmount 리듀서는 카운터 값에 얼마나 더해야 하는지 알아야한다. 따라서 리듀서를 상태와 액션 인수를 모두 가지도록 선언한다.