REDUX

2024. 3. 23. 14:15E8

Redux 소개

앱 전체의 상태 관리가 필요할 때 / 복잡한 비동기 처리가 있는 상태 관리가 필요할 때

앱의 상태가 복잡하고, 이를 체계적으로 관리하고 싶을 때 / 상태 관리 패턴을 도입하여, 여러 개발자와 협업하고 싶을 때

logger, devtool 등을 활용하여 상태를 관리할 필요가 있을 때

Action

const action1 = {type:"namespace/getMyData",payload:{id: 123}};

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

ex) payload 이용해서 상태를 변경하는 것 외엔 다른 것은 일어나면 안됨

어떤 형태든지 상관없으나, 주로 type, payload를 포함하는 JavaScript 객체

Action Creator

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

Action을 생성하는 함수

직접 Action을 생성하는 것보다 Action Creator를 활용하면 재사용성이 좋고, 하나의 레이어를 추가할 수 있음

Store

const store=createStore(reducer, initialState)

앱 전체의 상태를 보관

Action에 따라 reducer에서는 새로운 상태를 만들어내며, Store는 그 상태를 저장

Store의 상태는 불변하며, 매 액션이 발생할 때마다 새로운 객체가 만들어짐

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);

Action을 받아 새로운 State를 만듦

(state, action) => state의 인터페이스를 따름

상태 변경 시 사이드이펙트가 없어야 함

Dispatch

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

Action을 redux로 보내는 함수

dispatch 후에 action은 middleware를 거쳐 reducer에 도달

 

Selector

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

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

store의 state는 raw data를 저장하고, 계산된 값 등을 selector로 가져오는 등의 패턴을 구사할 때 유용

Redux의 구조

Redux-toolkit 활용

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

redux의 createStore 함수를 래핑

named parameter로 쉽게 store를 생성

reducer 객체를 받아, combineReducers를 적용

const addPost = createAction("post/addPost");
addPost({ title: "post 1" });
/*{type: 'post/addPost',payload : { title : 'post 1' }}*/

 

Action creator를 만드는 함수

만들어진 action creator의 데이터를 넘기면, payload 필드로 들어감

생성된 action creator는 toString() 메서드를 오버라이드해, 자신이 생성하는 액션의 타입 String을 리턴

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

builder의 addCase 메 서드를 이용하여, action마다 state의 변경을 정의

immerjs를 내부적으로 사용하므로, mutable code를 이용해 간편하게 변경 코드를 작성

const postsSlice = createSlice({
  name: "posts",
  initialState,
  reducers: {
    addPost(state, action) {
      state.posts.push(action.payload);
    },
  },
});
const { addPost } = postsSlice.actions
const reducer = postsSlice.reducer;

Slice는 Action creator, reducer 등 별도로 만들어야 하는 여러 Redux 구현체를 하나의 객체로 모은 것

createSlice 함수를 이용하여, 많은 보일러 플레이트를 없애고 쉽게 action creator, reducer를 만듦

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

createSelector 함수를 이용해, state를 이용한 특정 데이터를 리턴

내부적으로 데이터를 캐시하며, 데이터가 변동이 없다면 캐시된 데이터를 리턴

Redux를 React에 연결

react-redux: redux를 react 앱에 연결하게 하는 라이브러리

const store = configureStore({ reducer: rootReducer });
function App() {
  return (
    <Provider store={store}>
      <MyPage />
    </Provider>
  );
}

Redux store를 React와 연결하기 위해서는 반드시 Provider로 컴포넌트를 감싸야만 함

Provider 안에서 렌더링 된 컴포넌트들은 state에 접근할 수 있음

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

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

dispatch로 action creator가 생성한 action을 보내면 redux 내부로 보내지게 됨

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

Redux store로부터 데이터를 얻기 위한 API

selector function을 인자로 넘김

selector functio은 데이터에 어떤 변경을 가하면 안 됨

데이터를 특정 형태로 계산하여 읽을 수 있음

Redux를 이용한 비동기 처리

redux-thunk는 Promise를 이용한 비동기 Action을 쉽게 처리하도록 하는 middleware

const addPost = createAsyncThunk("posts/addPost", async (title) => {
  const result = awaitPostAPI.addPost({ title });
  return result.data;
});
// Component
useEffect(() => {
  dispatch(addPost("post 1"));
}, []);

두 인자 action type, async callback(payload creator)를 받음

action type을 주어지면, pending, fulfilled, rejected가 각각 postfix로 붙어 reducer로 들어옴

const addPost = createAsyncThunk("posts/addPost", async (title) => {
  const result = awaitPostAPI.addPost({ title });
  return result.data;
});

addPost-async 함수를 dispatch 하는 함수

addPost.pending-promise를 생성했을 때 발생하는 액션

addPost.fulfilled-promise가 fulfilled 되었을 때 발생하는 액션

addPost.rejected-promise가 rejected 되었을 때 발생하는 액션

const postsSlice= createSlice({
  // ...
  extraReducers: builder=> {
    builder
    .addCase(addPost.pending, state=> ...)
    .addCase(addPost.fulfilled, state=> ...)
    .addCase(addPost.rejected, state=> ...)
  }
})

createSlice의 extraReducers 함수를 이용해, builder에 각 상황에 대한 리듀스를 추가

fulfilled 시 데이터는 payload로 들어옴. ex) action.payload.todos

rejected 시에라는 action.error로 들어오며, payload는 undefined

연속적인 비동기 처리

dispatch(addPost("post1")).then(() => dispatch(updatePost("post2")))

thunk 함수를 dispatch 하면 promise가 리턴

동시 비동기 처리

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

thunk의 promise가 rejected 되어도. then() 으로 들어옴

 

 

 

 

#코딩독학 #코딩인강 #코딩배우기 #개발자 #코딩이란 #코딩교육
#프론트엔드부트캠프 #백엔드부트캠프 #국비지원부트캠프 #개발자 #백엔드 #AI부트캠프 #개발자국비지원 #백엔드개발자 #프론트엔드개발자

'E8' 카테고리의 다른 글

SSR  (0) 2024.03.25
React 테스트  (0) 2024.03.24
상태 관리  (0) 2024.03.20
React의 비동기 통신  (1) 2024.03.17
SPA 라우팅  (0) 2024.03.16