redux(state container)

miin·2021년 11월 19일
0

Redux

목록 보기
1/4
post-thumbnail

is

  • 상태관리 라이브러리
  • 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있다.
  • 컴포넌트끼리 상태를 공유하게 될 때 여러 컴포넌트를 거치지 않고도 손쉽게 상태 값을 전달 할 수 있다.
  • 전역과 지역 state를 사용하기 때문에 state를 공유해야하는 상황이 발생하면 그것을 쉽게 해결하기 위해 리덕스를 사용한다.
  • 공유해야하는 Global State를 특정 장소에서 저장해놓고 쉽게 불러 쓴다면 어떨까? 라는 생각에서 시작했다.
  • 하위 컴포넌트에게 state를 전달하기 위해 불필요한 과정을 생략하기 위해 shared state를 container에 묶어놓는 것
  • 기본적으로 하나의 store 멀티 reducer의 형태를 갖는다

when?

  1. 프로젝트의 규모가 큰가?
    Yes: 리덕스
    No: Context API

    2.비동기 작업을 자주 하게 되는가?
    Yes: 리덕스
    No: Context API

    3.리덕스를 배워보니까 사용하는게 편한가?
    Yes: 리덕스
    No: Context API 또는 MobX

need Type

Action

  • 상태에 변화가 필요할 때 발생시킴 (객체하나로 표현)
  • action에는 type이 필수로 필요하다.(공식문서에 나와있는 방법으로 작성, 그 외의 값들은 개발자 마음대로 생성)
{
	type: "TODDLE_VALUE"
}
  • 중앙 저장소에 저장된 state를 변경하기 위해서는 'add, update, delete'와 같은 동작의 type이 필요하고 'add'의 경우에는 어떤것을 추가할지와 파라미터가 필요
{
  type: "ADD_TODO",
  data:{
  	id: 0,
  	text: '리덕스 배우기'
  }
}
  • type이 많아졌을 경우에는 모듈로 저장해서 사용한다
//actionTypes에 type들을 정의해놓고 import 해서 사용 
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

Action Creater

  • 액션을 만드는 함수 (필수는 아님)
  • 단순히 파라미터를 받아와서 액션 객체 형태로 만들어준다
  • 액션 생성 함수를 만들어서 사용하는 이유는 나중에 컴포넌트에서 더욱 쉽게 액션을 발생시키기 위함이다.
  • 보통 함수 앞에 export 키워드를 붙여서 다른 파일에서 불러와 사용한다.
    👇 type이 ADD_TODO이며 data 담고 있는 Action을 리턴해주는 addTodo함수이다.
    이 함수 자체가 Action Creater가 되는 것
export function addTodo(data) {
  return {
    type: 'ADD_TODO',
    data
  }
}
//화살표 함수
export const changeInput = text => ({
	type: 'CHANGE_INPUT',
    text
});

Dispatch

  • 스토어의 내장함수
  • action Creater로 return해준 액션을 발생 시키는 것. 디스패치라는 함수에는 액션을 파라미터로 전달하고, store의 reducer에게 넘겨주는 역할 ex) dispatch(action)
  • 그렇게 호출을 하면, 스토어는 리듀서 함수를 실행시켜서 해당액션을 처리하는 로직이 있다면 액션을 참고하여 새로운 상태를 만들어준다
  • store의 값을 변화 시키기 위해서는 action이 필요한데 그 action을 action creater가 만들어주고 action creater를 담은 dispatch열차가 store의 reducer에게 actiond을 전달해주면 reducer가 action의 type을 보고 그에 맞는 행동을 해주는 것
//addTodo를 담고 reducer로 향하는 dispatch열차 
dispatch(addTodo(text))
  • 매번 action creator를 호출해서 사용하지 않고, 함수를 만들어 사용가능
const boundAddTodo = text => dispatch(addTodo(text))
boundAddTodo(text)

Reducer

  • 변화를 일으키는 함수
  • 두가지의 파라미터를 받아온다
  • 현재의 상태와 액션을 참조하여 새로운 상태를 반환
  • action의 type을 확인해서 그에 맞는 동작을 하는 곳(동작이기에 function으로 작성)
  • reducer는 store의 state를 변경시켜야하기 때문에 state를 파라미터로 받고,
    dispatch를 타고온 action을 파라미터로 받아서 action의 type을 switch case문으로 조건을 걸어줌
    👇 todoApp이 reducer가 되는 것이다.
//기본로직
//현재의 상태와, 전달받은 액션을 참고하여 새로운 상태를 만들어 반환한다
//(이 리듀서는 useReducer를 사용할때 작성하는 리듀서와 똑같은 형태를 가지고 있다. 
function reducer(state, action){
  //상태 업데이트 로직
  return alteredState;
}
----------------------------------
// state가 들어오지 않았을때 state에 initialState를 넣어주는 reducer
const initialState = {
  todos: []
}
function todoApp(state, action) {
  if (typeof state === 'undefined') {
    return initialState
  }
  return state
}
//refactoring
function todoApp(state = initialState, action) {
  return state
}
-------------------------
//카운터를 위한 리듀서 작성
function counter(state, action) {
  switch (action.type) {
    case 'INCREASE':
      return state + 1;
    case 'DECREASE':
      return state - 1;
    default:
      return state;
  }
}

👇 type이 여러 케이스 일 때

// todos자체가 reducer가 된다
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

userReducer

  • 일반적으로 default: 부분에 throw new Error('Unhandled Action') 과 같이 에러를 발생시키도록 처리하는게 일반적인 반면 리덕스의 리듀서에서는 기존 state를 그대로 반환하도록 작성해야한다

combineReducers

  • 글로벌 state로 user의 정보도 담아야하고, todo의 정보 등 많은 정보를 담아야 할 때 여러개의 reducer가 필요하다. 이때 사용하는 것이 combineReducers이다
    👇 todo reducer과 user reducer를 하나의 root reducer로 만들어준다.
    (루트리듀서 안의 작은 리듀서들을 서브 리듀서라고 부른다)
    즉 하나의 부분을 하나의 reducer로 만들어주면 된다.
    userReducer에서는 login, logout, changePw와 같은 type을 가지고 있어야 하고,
    todoReducer에서는 AddTodo, ToggleTodo, deleteTodo, CompleteTodo와 같은 동작을 한다
const todoApp = combineReducers({
  user,
  todos
})

Store

  • store는 react component의 최상위에 감싸져야 한다
  • 한 애플리케이션당 하나의 스토어를 만든다.
  • 스토어 안에는 현재의 앱 상태와, 리듀서, 내장함수 포함
  • 모든 컴포넌트에서 사용할 수 있는 global state를 저장해놓는 저장소이다. 하지만 state는 엄격하게 관리해야 하기 때문에 dispatch라는 함수를 통해서만 state에 접근이 가능하다
    👇 reducer를 통해 바꿔줄 store
import { createStore } from 'redux'
import todoApp from './reducers'
const store = createStore(todoApp)
//createStore는 파라미터로 Reducer를 받는다
import {
  addTodo,
  toggleTodo,
  setVisibilityFilter,
  VisibilityFilters
} from './actions'
// Log the initial state
console.log(store.getState())
// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() => console.log(store.getState()))
// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))
// Stop listening to state updates
unsubscribe()

subscribe

  • 스토어의 내장함수
  1. 함수 형태의 값을 파라미터로 받아온다.
  2. subscribe 함수에 특정 함수를 전달
  3. 액션이 디스패치 되었을 때 마다 전달해준 함수가 호출된다
  • 리액트에서 리덕스를 사용하게 될 때 보통 이 함수를 직접 사용하는 일은 별로 없다.
    리액트에서는 connect 함수 또는 useSelector Hook 을 사용

리덕스의 3가지 규칙

  1. 하나의 애플리케이션에 하나의 스토어
  2. 상태는 읽기전용
    리액트의 불변성
  3. 변화를 일으키는 함수, 리듀서는 순수한 함수
    동일한 인풋 => 동일한 아웃풋
    new Date(), 랜덤 숫자 생성, 네트워크에 요청 등은 순수하지 않은 작업(?) -> 리듀서 밖에서 처리

0개의 댓글