React | 상태관리 : Redux

Jihun Kim·2021년 8월 13일
0

리액트

목록 보기
3/3

Redux의 핵심 원칙

  • Single source of truth : store는 단 하나이며, 모든 앱의 상태는 이곳에 보관된다.
  • Immutability : 상태는 오로지 읽을 수만 있으며 변경하려면 '모든 상태'가 변경되어야 한다. 재생성시에는 새로운 객체 혹은 새로운 어레이가 생성되어야 한다.
  • Pure function : 상태의 변경은 어떠한 사이드 이펙트도 만들지 않아야 한다.

👉 Reducer에서는 상태를 변경하면 안되며, '새로운 상태'를 리턴해야 한다.
👉 변경을 만들기 위해서는 dispatch를 이용해야 한다.

1. Action

  • 상태의 변경을 나타내는 개념
  • 어떤 형태든지 상관 없지만 주로 type, payload를 포함하는 js 객체
const action1 = {
  type: "namespace/getMyData",
  payload : {
  	id: 123
  }
};

👉 잘개 쪼개서 작은 상태의 변경을 나타내어 여러 action을 합쳐 복합 형태를 나타내기에 유용함.

2. Action Creator

  • Action을 생성하는 함수
  • 직접 action을 생성하는 것보다 action creator를 활용하면 재사용성이 좋고, 하나의 레이어를 추가할 수 있다.
    👉 id를 받아서 action object를 만들어줌
    👉 또 다른 형태의 layer를 두게 됨(id : String(id).slice(1) 부분)
    👉 addObj(id)를 넘기면 액션이 생성됨
const addObj = (id) => {
  type: 'namespace/getMyData',
  payload: {
    id: String(id).slice(1)
  }
}

3. Store

  • 앱 전체의 상태를 보관하는 곳
  • action에 따라 reducer에는 새로운 상태를 만들어 내며, store에는 그 상태를 저장한다.
  • 상태 불변, 매 액션이 발생할 때마다 새로운 객체가 만들어짐
const store = createStore(reducer, initialState);

4. Reducer

  • action을 받아서 새로운 state를 만든다.
    👉 console.log, axios.get() 등이 들어가지 않아야 한다.
const Reducer = (state, action) => {
  switch (action.type) {
    case "namespcae/getMyData":
      const obj = {id: action.payload.id}
      return {
        ...state, obj
      };
    default:
      return state;
  }
};

5. Dispatch

  • action을 redux로 보내는 함수
  • dispatch 후에 action은 middleware를 거쳐서 reducer에 도달한다.
    👉 dispatch(action object) -> middleware -> reducer
  • dispatch() 안에는 action object를 생성해서 해당 action을 넘겨야 한다.
function MyApp() {
  const dispatch = useDispatch()
  
  return (
    <button onClick={
      () => dispatch(addObj(1234))
    }>Submit</button>
  )
}

6. Selector

  • 특정 state 조각을 store로부터 가져오는 함수
  • store의 state는 raw data를 저장하고, 계산된 값을 selector로 가져올 수 있다.
function MyApp() {
  const dispatch = useDispatch()
  const obj = useSelector(state => state.obj)
  
  return (
    <button onClick={
      () => dispatch(addObj(1234))
    }>Submit</button>
  )
}

flux : action -> dispatch -> reducer -> store -> selector
👉 action이 들어오면 dispatch는 reducer로 이를 전달하며, reducer는 action type에 따라 새로운 state를 리턴한다. 그러면 이는 store에 저장되며 여기에 저장된 state를 사용하기 위해 selector를 사용한다.

Redux의 구조

  • Middleware : action object가 reducer에 진입하기 전에 동작한다.
  • Enhancer : 전체 state의 상태를 중심으로 redux의 동작을 확장한다.
    👉 예시로는 Middleware가 있다.
    👉 Middleware는 action object가 promise를 리턴하는지 체크한다.

Redux-toolkit 활용

  • redux에서 공식저으로 추천하는 helper 라이브러리이다.
    👉 redux-devtools, immerjs, redux-thunk, reselect 등의 라이브러리가 미리 포함되어 있다.

1. configureStore

  • redux의 createStore 함수를 래핑한다.
    👉 createStore를 사용할 때보다 간단하게 작성할 수 있다.
const store = configureStore({
	reducer : {
    	posts: postsReducer,
        users: usersReducer
    }
})

2. createAction

  • Action creator를 만드는 함수
  • 만들어진 action creator에 데이터를 넘기면 payload 필드로 들어간다.
const addPost = createAction('post/addPost')

addPost({title: 'post 1'})

3. createReducer

  • reducer를 만듦
  • builder의 addCase 메서드를 이용해서 action마다 state의 변경을 정의한다.
  • immerjs를 내부적으로 사용하게 때문에 mutable code를 이용해서 간편하게 변경된 코드를 작성할 수 있다.
const postsReducer = createReducer(initState,
    builder => {
      builder.addCase(addPost, (state, action) => {
        state.posts.push(action.payload)
      })
    }
  )

4. createSlice

  • Slice는 action creator, reducer 등 별도로 만들어야 하는 여러 redux 구현체를 하나의 객체로 모은 것이다.
const postsSlice = createSlice({
  name:'posts',
  initialState,
  reducers: {
    addPost(state, action) {
      state.postsSlice.push(action.payload)
    }
  }
})

// 사용
const { addPost } = postsSlice.actions
const reducer = postsSlice.reducer

5. createSelector

  • state를 이용한 특정 데이터를 리턴하도록 한다.
  • 리덕스에서 알아서 캐시 관리를 해주어 성능 향상에 도움이 된다.
const postsSelector = state => state.posts
const userSelector = state => state.user

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

Redux를 React에 연결하기

1. Provider

  • Redux store와 React를 연결하기 위해서는 반드시 Provider로 컴포넌트를 감싸야 한다.
  • provider 안에서 렌더링된 컴포넌트들은 state에 접근할 수 없다.
function App() {
  return (
    <Provider store={store}>
       <div>소개글입니다.</div>
    </Provider>
  );
}

2. useDispatch

  • redux의 dispatch를 가져오기 위한 API
  • dispatch 함수에 action object를 넘겨서 상태 변경을 만든다.
 const dispatch = useDispatch();
 
 <Button onClick={() =>
    dispatch(changeTheme(theme === "light" ? "dark" : "light"))
 }>

3. useSelector

  • Redux store로부터 데이터를 얻기 위한 API
  • selector fuction을 인자로 넘긴다.
function Header() {
  const dispatch = useDispatch();
  const theme = useSelector((state) => state.theme);
  <Button onClick={() =>
    dispatch(changeTheme(theme === "light" ? "dark" : "light"))
 }>

Redux를 이용한 비동기 처리

  • 비동기를 위한 middleware를 추가해야 한다.
  • redux-thunk는 Promise를 이용한 비동기 action을 쉽게 처리하도록 하는 middleware
    👉 이외에도 redux-saga, redux-observable 등이 있다.

1. createAsyncThunk

  • redux-toolkit에서는 thunk middleware를 디폴트로 추가한다.
  • redux-toolkit은 createAsyncThunk API를 제공한다.

사용방법

  • 두 인자 "action type", "async callback(payload creator)"을 받는다.
  • creataeAsyncThunk로 만들어진 action creator는 4가지 함수로 구성된다.
    👉 addPost : async 함수를 dispatch 하는 함수
    👉 addPost.pending : promise를 생성했을 때 발생하는 액션
    👉 addPost.fulfilled : fulfilled 되었을 때 발생하는 액션
    👉 addPost.rejected : rejected 되었을 때 발생하는 액션
const addPost = createAsyncThunk('posts/addPost',
  async (title) => {
    const result = await PostAPI.addPost({ title })
    return result.data
  }
)

useEffect(() => {
  dispatch(addPost("post 1"))
}, [])
  • createSlice의 extraReducers 함수를 이용해서 builder에 각 상황에 대한 리듀서를 추가할 수 있다.
    👉 즉, pending, fulfilled, rejected 각 상황에 대한 리듀서를 추가할 수가 있다.

2. 연속적인 비동기 처리

  • thunk 함수를 dispatch하면 promise를 리턴하는데, 이 때 .then() 메소드로 연속적인 비동기 처리를 이어 실행할 수 있다.
profile
쿄쿄

0개의 댓글