리액트-리덕스 앱을 만들 때 고려해야 할 사항
폴더 구조를 어떻게 가져갈까(=컴포넌트를 어떻게 쪼갤까)
리액트만 사용한 경우의 케이스
- 페이스북 데모를 가정했을 때 크게 UI 상에서 Home과 Navigation으로 나눌 수 있다.
- Home 내부에는 일단 포스트를 등록하는 PostForm이 보여지고 있다.
- 또한 포스트를 등록하면 등록된 Post가 보인다.
- Post 내부에 뭐가 있는지 보면 코멘트를 입력할 수 있는 CommentForm과, 코멘트를 등록하면 보이는 Comment, CommentList가 있다. (개인 기호에 따라 Comment와 CommentList로 나눌 수도 있고 그냥 하나로 할 수도 있다.)
- Navigation 내부에는 페이스북 Logo, 로그인 된 상태에서는 Profile과 ''로그아웃''이라는 텍스트를 가진 NaviItem이 보이고 있다.
- 로그아웃 상태에서는 로그인, 회원가입이라는 각각의 텍스트를 지닌 NaviItem 두개가 렌더링 된다.
- 이때 로그인 로그아웃 처리는 HOC을 통해서 했고 따로 폴더를 뺐다.
- 폴더 구조로 나타내면 다음과 같다.
리덕스를 사용할 때에
- dumb 컴포넌트랑 smart 컴포넌트(=컨테이너)로 나누는 방식도 있고, 그냥 컴포넌트 폴더 안에 다 같이 넣어둘 수도 있다.
- 다만 후자의 경우 data 폴더처럼, 액션생성자와 리듀서를 포함하는 폴더를 따로 구성한다.
리액트 컴포넌트를 쪼갤 때
- 하나의 컴포넌트가 하나의 기능을 하도록 한다.
- 리덕스랑 연결하면 리액트 내부에 최대한 상태를 배제한다. 리덕스를 사용하면 컴포넌트에 무엇을 내려보낼 필요가 없다.
- composition이 용이하도록 쪼갠다.
프로그래밍 할 때
state 변경 문제
- 스테이트를 업데이트할 때에 기존의 스테이트를 절대 mutate하면 안 된다. 콘솔에 찍어보면 계속해서 값은 업데이트되는데 리렌더링이 안되는 문제가 있었는데(My Redux state has changed, why doesn't React trigger a re-render?), 결국 리듀서에서 deep copy하지 않고 스테이트를 변경해서 생긴 문제였다.
- 예를 들어 state 객체 내에 commentList 프로퍼티가 배열을 가지는 nested 구조일 때에 아래처럼 카피하여 새로운 객체를 리턴한다.
// Right Code
case ADD_COMMENT:
return state.slice(0).map(post =>
post.seq === action.seq
? {
...post,
commentList: [
...post.commentList,
{
seq: post.commentList.length,
contents: action.contents,
createAt: new Date()
}
],
comments: post.comments + 1
}
: post
);
- nested되지 않은 경우는 slice 메서드를 사용해도 되지만, 스테이트가 nested되면 spread operator를 반복 사용하여 새로운 객체로 복사하거나, 로대쉬 같은 라이브러리의 deep copy 메서드를 사용해서 복사하여야 한다.
- 리덕스 공식홈페이지에 이것 관련 도큐멘테이션이 있다. 링크
리듀서가 리듀서를 참조할 수 없나?
- 리듀서끼리 스테이트를 쉐어할 수 없는지에 대한 답 - 리듀서를 달리 쪼개거나, 커스텀 함수를 만들거나, thunk를 써보라
- Many users later want to try to share data between two reducers, but find that combineReducers does not allow them to do so. There are several approaches that can be used:
- If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
- You may need to write some custom functions for handling some of these actions. This may require replacing combineReducers with your own top-level reducer function. You can also use a utility such as reduce-reducers to run combineReducers to handle most actions, but also run a more specialized reducer for specific actions that cross state slices.
- Async action creators such as redux-thunk have access to the entire state through getState(). An action creator can retrieve additional data from the state and put it in an action, so that each reducer has enough information to update its own state slice.