Web Dev/ELICE

59 :: Redux, 구조, React와 Redux, 비동기 처리

HJPlumtree 2022. 1. 14. 16:48

엘리스 SW 엔지니어 트랙 59일차

온라인 강의날

 

 

Redux

앱 전체 상태 관리하기 위해 나온 라이브러리

많은 개념들이 Flux 패턴에서 가져왔다

주로 React와 같이 사용

 

 

언제 Redux 사용할까?

  • 앱 전체 상태 관리시
  • 복잡한 비동기 처리시
  • 앱의 상태가 복잡하고 체계적으로 관리하고 싶을 때
  • 상태 관리 패턴 도입해서, 여러 개발자와 협업시
  • logger, devtool 등 활용해서 상태 관리할 때

 

 

핵심 원칙 3가지

1. Single source of truth

Store는 단 하나, 모든 앱의 상태는 여기에 보관

2. Immutability

상태는 오직 읽을 수만 있다. 변경하려면 도든 상태가 변경되어야 한다

3. Pure function(side effect)

상태의 변경은 사이드 이펙트를 만들지 않아야 한다

 

 

Action

상태의 변경을 나타내는 개념

주로 객체로 type과 payload를 가지고 있다

// action
const action = {
  type: 'namespace/getMyData',
  payload: {
    id: 123
  }
}

 

Action Creator

Action 만들어주는 함수

사용하면 재사용성을 높일 수 있다

// action creator
const addObj = (id) => ({
  type: 'namespace/getData',
  payload: {
    id: String(id).slice(1)
  }
})

 

Store

앱 전체의 상태를 보관하는 곳

reducer는 새로운 상태를 만들고, Store는 그 상태를 저장

// store
const store = createStore(reducer, initialState)

 

Reducer

Action을 받아서 새로운 State 만든다

상태 변경 시 다른 곳에 영향을(사이드 이펙트) 안줘야 한다

(console.log, axios 등 액션 보내기전에 이미 확인하고 reducer로 보내줘야 한다)

// reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'namespace/getMyData': 
      const obj = { id: action.payload.id }
      return { ...state, obj }
      
    default:
      return state
  }
}

const store = createStore(reducer, initialState)

 

Dispatch

Action을 store(Redux)로 보내는 함수

dispatch 후에 middleware를 거쳐서 reducer에 도착한다

// dispatch
function MyApp() {
  const dispatch = useDispatch()
  
  return (
    <button
      onClick ={
      () => dispatch(addObj(1234))
      }
    >Submit</button>
  )
}

 

Selector

특정 state 조각을 store로부터 가져오는 함수

state를 raw data로 저장하고,

계산된 값을 selector로 가져오는 패턴이 유용하다

// selector
function MyApp() {
  const obj = useSelector(state => state.obj)
  
  return (
    <div>
      {JSON.stringify(obj)}
    </div>
  )
}

 

 

Redux 구조

action의 여행 to reducer

action은  middleware를 거쳐가면서 reducer에 도착한다

reducer는 새로운 state(상태)를 만든다

이 state를 전부 받거나, selector를 통해서 받는다 

 

자유롭게 확장할 수 있다고 한다

middleware, enhancer 등 이용해서 확장한다

 

라이브러리

redux-thunk: Middleware, action object가 Promise 리턴하는지 쳌

Enhancer: 전체 state 중심으로 redux 동작 확장

redux-logger 같은 녀석도 있다.

 

Redux 구조 by redux.js.org

 

 

redux-toolkit

redux 공식 추천 helper 라이브러리

유용한 라이브러리 포함시켜 redux 코드 쉽게 작성을 위함

기존에 만들어야 되는 많은 보일러 플레이트 제거

 

redux-toolkit API 예시

 

configureStore

createStore 함수 감싼다

named parameter로 쉽게 store 생성

reducer 객체를 받아서 reducer들을 합쳐준다(combineReducers 적용)

// configureStore
const store = configureStore({
  reducer : {
    posts: postsReducer,
    users: usersReducer
  }
})

 

createAction

Action creator를 만드는 함수

만들어진 action creator에 데이터 넘기면 payload 필드로 들어간다

자신이 생성한 액션의 타입 String을 리턴한다

// createAction
const addPost = createAction('post/addPost')

addPost({ title: 'post 1' })

// 위처럼 해주면 아래 처럼 만들어진다
/*
{
  type: 'post/addpost',
  payload: { title: 'post 1' }
}
*/

 

createReducer

reducer 만들어준다

builder의 addCase 메서드로 action 마다 state 변경 정의

// createReducer
const postsReducer = createReducer(initState, builder => {
  builder.addCase(addPost, (state, action) => {
    state.posts.push(action.payload)
  })
})

 

createSlice

Action creator, reducer 등 하나의 객체에 모은 것

// createSlice
const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    addPost(state, action) {
      state.posts.push(action.payload)
    }
  }
})

const { addPost } = postsSlice.actions
const reducer = postsSlice.reducer

 

createSelector

selector 함수 만들어준다

selector 만들면 좋은 이유

=> 내부적으로 데이터 캐시, 데이터 변동 없으면 캐시된 데이터 리턴

// createSelector
const postsSelector = state => state.posts
const userSelector = state => state.user

const postsByUserIdSelector = createSelector(
  postsSelector,
  userSelector,
  (posts, user) => posts.filter(post => post.username === user.name)
)

 

 

Redux와 React

react-redux 라이브러리 이용

 

react-redux API

 

Provider

Redux store를 React와 연결하기 위해 Provider로 감싸야된다

// Provider
const store = configureStore({ reducer: rootReducer })

function App() {
  return (
    <Provider store={store}>
      <MyPage />
    </Provider>
  )
}

 

useDispatch

redux의 dispatch 함수 가져오기 위한 API

action creator가 만든 action을

dispatch로 redux 내부로 보낸다

// useDispatch
const addPost = createAction('addPost')

function MyPage() {
  const dispatch = useDispatch()
  const handleClick = () => dispatch(addPost())
  
  return (
    <button
      onClick={handleClick}
    >Submit</button>
  )
}

 

useSelector

store로부터 데이터 가져오는 API

데이터 변경하면 안된다

// useSelector
function MyPage() {
  const posts = useSelector(state => state.posts)
  
  return posts.map(
    post => <Post {...post} />
  )
}

 

 

Redux 이용 비동기 처리

비동기를 위해서 middleware 추가해야 된다

redux-thunk를 이용하면 비동기 Action 쉽게 처리 할 수 있다

redux-thunk는 Promise 이용하는 middleware

redux-thunk 외에도 redux-sage, redux-observable 등 있다

 

createAsyncThunk

redux-toolkit은 thunk middleware를 기본값으로 추가한다

또 createAsyncThunk API도 제공한다

fullied, rejected, pending 3가지 상태에 대해 각각 reducer 작성한다

TypeScript 환경에서 reducer 작성 시, builder callback 사용해서 작성해야 정확하게 타이핑 가능

 

action type과 async 함수(payload creator)를 받는다

pending, fulfilled, rejected가 뒤에 붙어 reducer로 들어온다

예시) posts/addPost/pending

// createAsyncThunk
const addPost = createAsyncThunk('posts/addPost', 
  async (title) => {
    const result = await PostAPI.addPost({ title })
    return result.data
  }
)

useEffect(() => {
  dispatch(addPost("post 1"))
}, [])


thunk 함수 dispatch 하면 Promise가 리턴된다

.then() 메서드로 연속적인 비동기 처리 가능

// 연속적인 비동기
dispatch(addPost("post1"))
  .then(() =>
    dispatch(updatePost("post2"))
  )

 

Promise.all 이용해서 비동기 처리 동시에 처리

주의점

thunk의 Promise가 rejected 되어도 .then()으로 들어온다

// Promise.all
Promise.all([
  dispatch(addPost("post1))
  dispatch(updatePost("post2))
])
  .then(() => console.log("DONE))

 

 

참고 링크

redux org

=> https://redux.js.org/