CS/React

[Redux] 리덕스로 상태 관리하기

뽀글보리 2021. 10. 29. 20:03
반응형

리덕스는 자바스크립트를 위한 상태 관리 프레임워크

  • 컴포넌트와 상태 관리 코드 분리
  • 서버 렌더링 시 데이터 전달이 간편하다
  • 알림창과 같이 전역 컴포넌트의 상탯값을 관리할 때 좋다
  • 페이지가 전환되어도 데이터는 살아 있어야 할 때 좋다

6.1 리덕스 사용 시 따라야 할 세 가지 원칙

  • 하나의 객체에 프로그램의 전체 상탯값을 저장한다
  • 상탯값을 불변 객체로 관리한다
    • 상탯값은 오직 액션 객체에 의해서만 변경되어야 한다.
  • 오직 순수 함수에 의해서만 상탯값을 변경해야 한다
// [홍길동]님 안녕하세요, [지금은 11시 30분]입니다.
sayHello('홍길동')
sayHello('홍길동', '11:30')

6.2 리덕스의 주요 개념 이해하기

  • 리덕스에서 상탯값이 변경되는 과정

6.2.1 액션

  • 액션은 type 속성값을 가진 자바스크립트 객체다.
  • 액션 객체를 dispatch메서드에 넣어서 호출하면 리덕스는 상탯값을 변경하기 위해 위의 과정을 수행한다
const ADD = 'todo/ADD'

store.dispatch({type:ADD, title:'영화보기'});
store.dispatch({type:'todo/REMOVE', id:123});
  • type 속성값은 상수 변수로 만드는게 좋다.

6.2.2 미들웨어

  • 리듀서가 액션을 처리하기 전에 실행되는 함수
const printLog = store => next => action => {
    console.log(`prev state = ${store.getState()}`)
    const result = next(action)
    console.log(`next state = #{store.getState()}`)
    return result
}

6.2.3 리듀서

  • 액션이 발생했을 때 새로운 상탯값을 만드는 함수
function reducer(state = INITIAL_STATE, action) {
    switch (action.type) {
        case REMOVE_ALL:
            return {
                ...state,
                todos: [],
            }
        case: REMOVE:
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.id),
            }
  • 상탯값은 항상 불변 객체로 관리해야 한다.

immer를 이용하여 리듀서 작성하기

import produce from 'immer'

const person = { name: 'mike', age: 22}
const newPerson = produce(person, draft => {
    draft.age = 32
})

리듀서 작성 시 주의할 점: 순수 함수

  • API 호출 등,, → 액션 생성자 함수나 미들웨어에서 하면 된다.
  • createReducers함수를 사용하면 switch문보다 더 간결하게 작성 가능하다.

6.2.4 스토어

  • 스토어는 리덕스의 상탯값을 가지는 객체
  • 스토어의 dispatch 메서드로 액션을 발생할 수 있다.
store.dispatch({ type: 'INCREMENT' })

6.3 데이터 종류별로 상탯값 나누기

6.3.1 상태값 나누기 예제를 위한 사전 작업

6.3.2 리듀서에서 공통 기능 분리하기

6.4 리액트 상탯값을 리덕스로 관리하기

  • 리덕스는 리액트와 궁합이 잘 맞는다.
  • 리액트 상탯값처럼 리덕스 상탯값도 불변 객체다. -> 값의 변경여부를 빠르게 확인할 수 있다.

6.4.1 react-redux 패키지 없이 직접 구현하기

forceUpdate() 동작

forceUpdate()을 호출하면 곧바로 render()가 호출된다. 즉 shouldComponentUpdate()는 건너뛴다. 해당 메소드를 건너 뛰더라도, child componentslife cycle method정상적으로 다 호출된다. (즉 child componentshouldComponentUpdate()는 호출된다.)

FriendMain component 개선하기

6.4.2 react-redux 패키지 사용하기

npm install react-redux
  • Provider를 사용하여 store 관리하기
    • Provider 컴포넌트는 전달받은 스토어의 subscribe 메서드를 호출해서 액션 처리가 끝날 때마다 알림을 받고, 컨텍스트 API를 사용해서 하위 컴포넌트로 리덕스의 상태값을 전달해준다.
  • useSelector와 useDispatch
    • react-redux 7 버전 이전 : connect HOC
    • useSelector : store에 저장된 state를 가져오는 역할
      • 리덕스 상태가 바뀌면 그 state를 사용하년 컴포넌트는 다시 렌더링 된다.

6.5 reselect 패키지로 선택자 함수 만들기

6.5.1 reselect 패키지 없이 구현해 보기

6.5.2 reselect 패키지 사용하기

  • reselect의 memoization 기능
    • data 값이 변하지 않으면 새롭게 데이터값을 연산하는 대신에 이전의 값을 사용하여 불필요한 연산을 막을 수 있다.
  • createSelector 사용하기
  • timeline rerender할 때 다시 계산 X

6.5.3 reselect에서 컴포넌트의 속성값 이용하기

  • 선택자 함수는 상탯값 외에도 속성값을 입력으로 받아 각 컴포넌트에 특화된 값을 반환할 수 있다.
  • 예시코드
  • reselect에서 제공하는 메모이제이션 기능이 제대로 동작하지 않는다.
  • 같은 선택자 함수를 다른 속성값으로 호출하기 때문에, 선택자 함수의 입장에서 연령제한 정보가 변경됨 → 매번 반복해서 연산을 수행한다.

6.5.4 컴포넌트 인스턴스별로 독립된 메모이제이션 적용하기

6.6 리덕스 사가를 이용한 비동기 액션 처리

  • API 호출을 통해서 서버로부터 데이터를 받아오고 상태값을 비동기로 처리하는 경우

6.6.1 리덕스 사가 시작하기

6.6.2 여러 개의 액션이 협업하는 사가 함수

  • 다수의 액션을 하나의 사가 함수에서 사용할 수 있다.
function *loginFLow() {
    while (true) {
        const { id, password } = yield take(types.LOGIN)
        const userInfo = yield call(callApiLogin, id, password)
        yield put(types.SET_USER_INFO, userInfo)
        yield take(types.LOGOUT)
        yield call(callApiLogout, id)
        yield call(types.SET_USER_INFO, null)
    }
}

6.6.3 사가 함수의 예외 처리

6.6.4 리덕스 사가로 디바운스 구현하기

6.6.5 사가 함수 테스트하기

npm install @redux-saga/testing-utils

나의 깨달음 🤯

  • 북마크를 저장하기 위해서 redux를 통한 상태값을 만들었고, 북마크 추가/삭제 와 같은 Action에서 로컬 스토리지 값을 변경하는 부수적인 일을 하게 됨
  • 순수함수가 아니라서 테스트하기 힘들고, 고민중이었다..
  • 로컬스토리지에 리덕스 상태값 저장하기
    이런식으로 부수적인 것은 미들웨어 또는 subscribe 함수를 사용해서 처리해야겠다.
반응형

'CS > React' 카테고리의 다른 글

[React] 클래스형 컴포넌트의 생명 주기 메서드  (0) 2021.10.21