Redux part

Judo·2021년 1월 11일
0
post-custom-banner

What is Redux?


  • 글로버 애플리케이션 상태를 관리하기 위한 라이브러리

  • JS 어플리케이션에서 data-state와 UI-state를 관리해주는 도구

  • Redux는 "action" 이라는 이벤트를 사용하여 애플리케이션 상태를 관리하고 업데이트 하기 위한 패턴 및 라이브러리다.

단점 : 컴포넌트 갯수가 많아지거나 데이터를 교류 할 컴포넌트들이 parent-child 관계가 아니라면 복잡해진다.

MVC 패턴

  • Action 이 입력되면 Controller은 Model이 지니고 있는 데이터를 조회, 업데이트되고 View에 반영
  • View에서도 Model에 접근할 수 있음.

  • 하지만 Model과 View가 많아지면 엄청 복잡해짐

Flux

  • redux의 단점과 MVC 디자인 패턴의 단점을 해결하기 위해 react에서 제안한 디자인 패턴(라이브러리X)

  • Dispatcher가 Action을 통제하여 Store에 있는 데이터를 업데이트한다. 변동된 데이터가 있으면 View에 리렌더링
  • View에서 Dispatcher로 Action을 보낼 수도 있다.
  • Dispatcher는 Action들의 순서를 통제하여 작업이 중첩도지 않게 한다.

redux는 flux 아키텍쳐를 좀 더 편하게 사용할 사용 할 수 있도록 해주는 라이브러리

Redux를 사용해야 하는 이유?


  • Redux에서 제공하는 패턴과 도구를 사용하면 애플리케이션의 상태가 언제, 어디서, 왜, 어떻게 업데이트 되는지, 이러한 변경이 발생할 때 애플리케이션 로직이 어떻게 작동되는지 쉽게 이해할 수 있다.

redux 세가지 원칙

1. Single Source of Truth

  • redux는 단 하나의 store를 사용한다. 따라서 이 원칙은 데이터는 하나의 store에서 나온다 를 의미한다.

2. State is read-only

  • 상태를 변화시키는 유일한 방법은 액션 객체를 전달하는 방법뿐이다.
  • action이 dispatch에 의해 전달되어야 한다.

3. Changes are made with Pure Functions

  • reducer는 dispatch로 전달받은 action객체를 처리하는 함수다. 즉, 받은 action객체를 이용해 state를 어떻게 변경할지를 정의해둔 함수다.
  • reducer는 순수함수로 작성되어야 한다.
    - return값은 오직 파라미터에만 의존
    • 인수는 변경되지 않아야한다.
    • 같은 인수로 실행된 함수는 같은 결과를 리턴한다.

Redux의 데이터 흐름

단방향 데이터 흐름

  • State는 특정 시점의 앱 상태를 설명하고 UI는 해당 상태를 기반으로 렌더링된다.
  • 앱에서 어떤 일이 발생하면
    • UI가 action을 전달
    • 저장소는 reducer를 실행하고 발생한 상황에 따라 상태가 업데이트
    • store는 state가 변경되었음을 UI에 알림
  • 새로운 상태에 따라 UI가 다시 렌더링됩니다.

Terminology


Actions

  • 자바스크립트 객체
  • 애플리케이션에 발생한 일을 설명하는 이벤트 같은 것
const addTodoAction = {
  //type은 string, 첫 번째 부분은 작업이 속한 기능이나 범주, 두 번째 부분은 발생한 일
	type: 'todos/todoAdded',
  //payload는 발생한 일에 대한 추가 정보
	payload: 'Buy milk'  
}

Action creators

  • 액션 객체를 생성, 반환하는 함수
const addTodo = text => {
	return {
    	type: 'todos/todoAdded',
      	payload: text
    }
}

Reducers

  • 현재 state와 action 객체를 받고 필요한 경우 state를 어떻게 업데이트할 지 결정하고 새로운 state를 반환하는 함수다. reducer는 받은 action type에 따라 이벤트를 처리하는 이벤트 리스너와 비슷하다.
  • 즉, 이전 상태 + action 을 기반으로 새로운 상태값을 계산하는 함수
  • (state, action) => newState
  • Reducers 규칙
    - state와 action 계산하여 새 상태값만 계산한다.
    • 기존 state를 변경할 수 없고 state를 복사하고 복사한 state를 변경하여 immutable하게 수정해야한다.
    • 비동기 로직 수행, 임의의 값 계산, side effect를 유발하는 것을 허용하지 않는다.
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, //  value: 0 
          	// and update the copy with the new value
          	value: state.value + 1
        }
    }
    // otherwise return the existing state unchanged
	return state
}

  • action은 dispatch에 의해 전달되고, store에 action객체가 전달되면 reducer를 호출해 이전 state와 action을 이용해 새로운 state를 반환한다.

Store

  • 현재 state는 store라는 객체안에 있다.
  • store는 reducer를 전달하여 생성되며, 현재 state를 반환하는 getState 메소드를 가지고 있다.
  • action이 전달될 때마다 reducer를 실행한다.
import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

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

Dispatch

  • Redux store는 dispatch 메소드를 가지고 있다.
  • state를 업데이트하는 유일한 방법은 store.dispatch()를 호출하고 action 객체를 전달하는 방법이다.
  • store는 reducer 함수를 실행하고 새로운 state를 저장하고 업데이트된 state를 검색하기 위해 getState()를 호출할 수 있다.
  • action을 dispatch하는 것을 "이벤트를 트리거 하는 것" 으로 생각할 수 있다.
store.dispatch({ type: 'counter/increment' }) //dispatch를 호출하여 action 객체 전달 
console.log(store.getState()) // { value: 1 }
const increment = () => {
	return {
    	type: 'counter/increment'
    }
}
store.dispatch(increment())
console.log(store.getState()) // { value: 2 }

Selectors

  • 특정한 store state value 를 추출하는 함수.
const selectCounterValue = state = state.value;

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

Redux hooks


useSelector()

  • 컴포넌트와 state를 연결하는 역할
  • 즉, 컴포넌트에서 useSelector 메소드를 통해 store의 state에 접근할 수 있다.
  • useSelector는 subscribe처럼 state의 변화를 감시하고 있다.
import { useSelector } from 'react-redux'

const state = useSelector(state => state.itemReducer);
// 여기서 itemReducer는 reducer다. reducer에서 action을 전해주지 않으면 switch case문의 default에 걸리면서 기존 state를 리턴한다.
const [checkedItems, setCheckedItems] = state; //destructuring을 이용해 state를 가져올 수 있다.
  • useSelector을 이용해 가져온 state를 확인

useDispatch()

  • Action 객체를 Reducer로 전달해주는 메소드
import { useDispatch } from 'react-redux';

const dispatch = useDispatch();
dispatch(action객체) //dispatch안에 action객체를 담아 reducer로 전달한다.

redux의 state를 immutable한 방식으로 변경해야 하는 이유

  • 초기 state가 있다고 치자. reducer는 전달된 action을 받고 새로운 state를 리턴한다. 이 때, 초기 state를 직접 변경하지 않고 새로운 객체를 만들어 return 한다. 이렇게 하는 이유는 위 그림과 같이 react에서는 real DOM(초기 state), virtual DOM(새로운 state)을 비교하는데 기존 state와 새로운 state의 주소값을 비교하여 다른 경우에만 새로운 state를 virtual DOM에 렌더링하여 real DOM과 비교한 뒤, 차이가 있는 부분만 부분 렌더링을 하기 때문이다.

  • 추가적으로 기존 state를 변경하지 않음으로써 기존 상태에 대한 트랙킹을 할 수 있게 한다. (다크모드, 화이트 모드를 구현할 때)

Presentational , Container Component

Presentational Component

  • "어떻게 보여지는가?"에 초첨을 맞춘 컴포넌트
  • 뷰 담당 로직만 구성되어 있다.

Container Component

  • "어떻게 동작하는가?"에 초점을 맞춘 컴포넌트

궁금했던 내용


  1. 스프린트 코드에서 아래 코드와 같이 함수를 사용한다. button 컴포넌트에 handleClick = {handleClick}처럼 전달해도 될 거 같은데..
    굳이 해석해보자면 Item 컴포넌트에서 받은 handleClick은 () => { handleClick(item) }
    결국 onClick={(e) => handleClick(e, item.id)} 이 코드는 익명함수임 handleClick에 인자를 전달하는 상황이다. 익명함수에 e와 item.id를 전달하고 익명함수안에 있는 handleClickd은 item.id를 지역 변수로 넣고 여기서 item을 가져와 쓰는거로 보면된다.
<Item handleClick = {() => {
	handleClick(item)
}}/>

function Item({ handleClick }) {
	  <button className="item-button" onClick={(e) => handleClick(e, item.id)}>장바구니 담기</button>
}
  1. redux hooks인 useDispatcher을 사용할 때, reducer가 두 개라면 dispatcher는 action객체를 모든 reducer에게 전달을 해주는지, 아니면 action.type을 판별해내어 적합한 reducer을 찾아내는지
  • redux에서 reducer는 combineReducers를 이용해 하나의 root reducer로 합친다.
import { combineReducers } from 'redux'

const rootReducer = combineReducers({
	itemReducer,
  	notificationReducer
})
  • 그리고 action을 생성할 땐 매번 직접 생성을 하는게 아닌 action creator을 이용한다.
  • 액션 이름을 만들 때 const를 사용하여 레퍼런스에 문자열을 담는데, 앞에 도메인을 추가하는 방식으로 짠다.
// actions creator functions
export const ADD_TO_CART = "domain/ADD_TO_CART";


export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId
    }
  }
}
  • 이제 dispatcher가 action을 전달하면 rootReducer로 먼저 들어가고 넘어온 action.type에 따라 적합한 reducer를 찾아간다.
profile
즐거운 코딩
post-custom-banner

0개의 댓글