2024.02.07(수)

🧠Redux

A library for managing global application state 🔗

  • 설치: npm install @reduxjs/toolkit react-redux
  • 설정
    • src/store.tsx 생성
      import { configureStore } from "@reduxjs/toolkit";
      
      export default configureStore({
          reducer: {
              
          }
      });
    • src/index.tsx에 store와 Provider import 후 다음과 같이 감싸고 store 넘기기 (생성한 store를 사용할 수 있도록 해줌)
      import store from './store';
      import { Provider } from 'react-redux';
      
      root.render(
        <React.StrictMode>
          <Provider store={store}>
            <BrowserRouter>
              <App />
            </BrowserRouter>
          </Provider>
        </React.StrictMode>
      );
  • Modern Redux
  • TypeScript 사용법
  • Redux Style Guide

♻️one-way data flow

  • 구성 요소
    • state: the source of truth that drives our app
    • view: a declarative description of the UI based on the current state
    • actions: the events that occur in the app based on user input, and trigger updates in the state
  • 간단한 flow
    description
    StateState는 특정 시점의 app의 상태를 설명한다.
    State → ViewUI는 해당 state를 기반으로 render된다.
    Actions → Stateevent가 발생하면 그에 따라 state가 update된다.
    State → ViewUI는 새로운 state를 기반으로 re-render된다.

🔠Redux Terminology

1. Actions

type field가 있는 일반 JavaScript 객체
👉application에서 발생한 일을 설명하는 event

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}
  • type field
    • 해당 action을 설명하는 이름을 제공하는 문자열
    • 일반적으로 "domain/eventName"과 같은 유형의 문자열 작성
      • domain: 해당 action이 속한 feature 또는 category
      • eventName: 발생한 특정한 일
  • payload field
    • 발생한 일에 대해 추가적인 정보를 관습적으로 담는 field

2. Action Creators

Action 객체를 생성하고 반환하는 함수

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

3. Reducers

현재 stateaction 객체를 받아 필요한 경우 state를 update하는 방법을 결정하고 새로운 state를 반환하는 함수: (state, action) => newState
👉받은 action (event) type을 기반으로 event를 처리하는 event listener

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1
    }
  }
  // otherwise return the existing state unchanged
  return state
}
  • Array.reduce() method에 넘겨주는 callback function과 비슷한 개념이라 여기서 이름을 따왔다고 함
  • immutable update를 수행해야 하고 side effects(부수 효과)를 발생시킬 수 있는 비동기 로직이나 난수를 계산하는 등의 작업은 절대 하면 안됨

4. Store

현재 Redux application이 살아있는(저장되어 있는) 객체

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())  // return the current state value
// {value: 0}
  • store는 reducer를 전달해서 생성되고, 현재 state value를 반환하는 getState라는 method가 있음

5. Dispatch

Redux store가 가진 method로 state를 update하기 위한 유일한 방법
👉actions를 dispatching(파견)하는 event trigger

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

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

const increment = () => {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}
  • store.dispatch()에 action 객체를 전달해서 호출하면, store는 reducer 함수를 실행하고 내부에 새 state value를 저장
  • 우리는 getState()를 호출해 update된 value를 찾아볼 수 있음

6. Selectors

store state value에서 특정 정보를 추출하는 방법을 아는 함수

const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
  • application이 커질수록 앱의 여러 부분이 동일한 data를 읽어야 하기 때문에 logic의 반복을 방지하는 데에 도움이 될 수 있음

🌊Redux Application Data Flow

  • Redux의 경우 앞서 살펴본 one-way data flow의 단계들을 좀 더 자세하게 쪼갤 수 있음
  • Initial setup:
    • Redux store가 root reducer 함수를 통해 생성됨
    • store는 root reducer를 한번 호출하고 반환된 값을 initial state로 저장
    • UI가 처음 render될 때, UI component들이 Redux store의 현재 state에 접근하여 해당 data를 기반으로 무엇을 render할지 결정 & state가 변경되었는지 알 수 있도록 향후 store updates를 구독(subscribe)함
  • Updates:
    • 사용자가 버튼을 클릭하는 등 app에서 무슨 일이 발생하면, app은 dispatch({type: 'counter/increment'})와 같이 Redux store에 action을 dispatch함
    • store는 이전 state와 현재 action으로 reducer 함수를 실행시키고 반환 값을 새로운 state로 저장
    • store는 구독 중인 UI의 모든 부분에 store가 업데이트되었음을 알림
    • store에서 data가 필요한 UI component들은 필요한 state가 변경되었는지 확인하고, 변경된 경우 새 data로 강제로 다시 re-render해서 화면에 표시되는 내용을 업데이트함


📜Redux의 세 가지 원칙

🔗Three Principles | Redux

1. Single source of truth

application의 global state는 모두 단일 store 내의 object tree에 저장되어 관리되어야 한다.

  • app을 debug하거나 검사(inspect)하기 쉬워짐
  • 개발이 빨라짐

2. State is read-only

state는 immutable(read-only) data이며, action 객체만이 state를 변경할 수 있다.

  • 모든 변화들이 중앙에 집중되어 있고 엄격한 순서로 하나씩 일어나기 때문에 따로 주의해야할 사항들이 없음
  • action은 단순한 객체이기 때문에 디버깅이나 테스트를 위해 기록(logged)될 수 있고, 직렬화(serialized)될 수 있으며, 저장(stored)하고 나중에 재생(later replayed)될 수 있음

3. Changes are made with pure functions

state tree가 actions에 의해 어떻게 변화되었는지 명시하려면 순수 함수인 reducer를 작성해라.

😎Modern Redux

configureStore API

  • src/app/store.js old redux
    import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
    import thunk from 'redux-thunk'
    
    import postsReducer from '../reducers/postsReducer'
    import usersReducer from '../reducers/usersReducer'
    
    const rootReducer = combineReducers({
      posts: postsReducer,
      users: usersReducer
    })
    
    // middleware: action이 dispatch된 후 reducer에서 해당 action을 받아와서 업데이트하기 전에 추가적인 작업을 할 수 있음
    const middlewareEnhancer = applyMiddleware(thunk)
    
    const composeWithDevTools =
      window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
    
    const composedEnhancers = composeWithDevTools(middlewareEnhancer)
    
    const store = createStore(rootReducer, composedEnhancers)
  • src/app/store.js modern redux
    import { configureStore } from '@reduxjs/toolkit'
    
    import postsReducer from '../reducers/postsReducer'
    import usersReducer from '../reducers/usersReducer'
    
    // Automatically adds the thunk middleware and the Redux DevTools extension
    const store = configureStore({
      // Automatically calls `combineReducers`
      reducer: {
        posts: postsReducer,
        users: usersReducer
      }
    })
  • configureStore = createStore wrapper
  • 수동으로 했던 store setup들을 다 자동으로 설정해줌

createSlice API

  • src/constants/todos.js old redux
    export const ADD_TODO = 'ADD_TODO'
    export const TOGGLE_TODO = 'TOGGLE_TODO'
  • src/actions/todos.js old redux
    import { ADD_TODO, TOGGLE_TODO } from '../constants/todos'
    
    export const addTodo = (id, text) => ({
      type: ADD_TODO,
      text,
      id
    })
    
    export const toggleTodo = id => ({
      type: TOGGLE_TODO,
      id
    })
  • src/reducers/todos.js old redux
    import { ADD_TODO, TOGGLE_TODO } from '../constants/todos'
    
    const initialState = []
    
    export default function todosReducer(state = initialState, action) {
      switch (action.type) {
        case ADD_TODO: {
          return state.concat({
            id: action.id,
            text: action.text,
            completed: false
          })
        }
        case TOGGLE_TODO: {
          return state.map(todo => {
            if (todo.id !== action.id) {
              return todo
            }
    
            return {
              ...todo,
              completed: !todo.completed
            }
          })
        }
        default:
          return state
      }
    }
  • src/features/todos/todosSlice.js modern redux
    import { createSlice } from '@reduxjs/toolkit'
    
    const initialState = []
    
    const todosSlice = createSlice({
      name: 'todos',
      initialState,
      reducers: {
        todoAdded(state, action) {
          const { id, text } = action.payload
          // "Mutating" update syntax thanks to Immer, and no `return` needed
    			// 자동으로 exsiting state를 반환해줌
          state.todos.push({
            id,
            text,
            completed: false
          })
        },
        todoToggled(state, action) {
          const matchingTodo = state.todos.find(todo => todo.id === action.payload)
    
          if (matchingTodo) {
            // Can directly "mutate" the nested object
            matchingTodo.completed = !matchingTodo.completed
          }
        }
      }
    })
    
    // `createSlice` automatically generated action creators with these names.
    // export them as named exports from this "slice" file
    export const { todoAdded, todoToggled } = todosSlice.actions
    
    // Export the slice reducer as the default export
    export default todosSlice.reducer
without createSlicewith createSlice
action creators를 직접 정의우리가 작성한 reducer 함수를 기반으로 action creators를 자동으로 만들어줌
action type을 기반으로 switch/case문으로 reducer logic 직접 작성한 객체 안에 case에 따른 reducer 함수들을 정의 가능
immutable update logic 사용Immer라는 불변성 관리 라이브러리를 사용해서 불변성을 신경쓰지 않으면서 보기 쉽고 더 짧은 코드로 state update 가능
(복사본을 만들어서 immutable update를 하는 방식도 사용 가능)
파일 분리 ⭕파일 분리 ❌
type of code로 폴더 구성features 별로 폴더 구성
profile
이것저것 관심 많은 개발자👩‍💻

0개의 댓글