리덕스 스타일링 가이드 요약

반응형

 

 

redux.js.org/style-guide/style-guide#reducers-must-not-have-side-effects

 

 

 

Redux Style Guide

 

 

 

 

[필수]

* 상태를 Mutate 하지 말 것.

 

* 리듀서는 Side Effect 를 가지면 안된다.

  - ajax, timeouts, promises, random, date.now, 리듀서 밖에서 관련 변수 수정 x, 리듀서 밖에 영향을 줄 수 있는 코드가 리듀서 함수에 있으면 x

 

* Non-Serializable 값이 상태나 액션에 있으면 안된다.

  - Promises, Symbols, Maps/Sets, funtions, class instances 와 같은 것들이 Redux store state 나 dispatched action 에 있으면 안된다.

  - 하지만 redux-thunk 나  redux-promise 같이 액션이 리듀서에 도달하기 전 미들웨어에 의해 잡혀서 처리되는 경우는 예외이다.

 

* 앱 당 하나의 리덕스 스토어만 가져야 한다.

 

 

 

 

 

[매우 권고]

* 리덕스 로직을 작성하기 위해 Redux Toolkit 을 사용하라.

 

* Immutable 업데이트를 위해 Immer 를 사용하라.

 

* 파일 구성을 Feature Folder 구조로 하고 하나의 파일 구조로 하라.

 

* 리듀서에 가능한 한 많은 로직을 작성하라.

  - 리듀서 던지기 전 로직에 넣는 것보다 가능하다면 리듀서 안에 상태 변화로직을 넣어라.

 

* 리듀서들은 State Shape 을 가지도록 하라.

  - 리덕스는 하나의 root state 를 가지며 key/value 로 나뉘어진 slice reducer 들을 가지며 이 slice reducer 들은 초기값, 상태의 slice 를 계산하고 업데이트하는 책임을 진다.

  - slice reducer 들은 계산된 상태의 부분으로서 리턴되는 값들을 컨트롤하는 것을 수행한다. 이때 깜깜이 spread/returns 사용을 최소화 하라. return action.payload 나 return {...state, ...action.payload} 와 같은 것을 최소화하라. 내용을 올바르게 포맷팅하기 위해 dispatched 된 액션의 코드에 의존하기 때문에 리듀서는 그 영향으로 본래의 state 의 모양을 포기하게될 가능성이 높아진다. action 의 내용이 올바르지 않다면 버그로 이어질 수 있다.

  - spread return 은 하지만 여러 액션 타입에 대해 각각의 개별 필드들을 수정해야하는 그런 시간이 많이 드는 귀찮은 코드 작성을 줄여줄 수 있는 편의성이 있기 때문에 어떤 경우에는 이성적으로 이것이 선택될 수도 있다.

 

* Stored Data 에 기반하여 State Slice 들을 네이밍 하라.

  - 위의 State Shape 에서 언급하였듯 리듀서 로직의 분할을 위한 표준적 접근 방식은 state 의 slices 에 기반한다. combineReducers 가 그 slice reducer 들을 더 큰 reducer 함수로 조합하는 기본적인 함수이다.

- combineReducers 에 전달되는 object 의 key 이름들은 결과 state object 의 key 들의 이름들을 정의한다. 해당 키들에 각각 데이터들이 들어간다는 것을 염두하고 이름을 지어라. {users: {}, posts: {}} 같은 object 가 될 것이다. 뒤에 usersReducer 와 같이 reducer 라고 뒤에 붙이는 것을 피하라.

 

* Component 에 기반하는 것이 아니라 Data Type 들에 기반하여 State 의 구조를 구성하라. 

  - Root state slice 들은 UI 에 따른 것이 아니라 주요 데이터 타입이나 기능에 따라 정의되거나 이름지어져야 한다. 리덕스 스토어의 데이터와 UI 의 컴포넌트 들 사이에서 확실한 1:1 상관관계가 없기 때문이기도 하고 많은 컴포넌트 들은 같은 데이터를 접근하기도 하기 때문이다. 앱의 일부인 어떤 컴포넌트에서 상태의 데이터 중 원하는 부분을 읽어올 수 있는 전역 데이터베이스로서의 state tree 를 생각해보면 된다. 예를 들어 로그인, 작성자들, 게시글들, 그리고 어떤 UI 가 활성 상태인지 등의 상태를 가지는 것과 같은 앱이 있다하면 {auth, posts, users, ui} 와 같이 구성하는 것이 좋다. {loginScreen, usersList, postsList}  와 같이 화면 컴포넌트에 따른 구성은 좋지 않다.

 

* 리듀서들을 State Machine 으로 취급하라.

  - 많은 리덕스 리듀서들은 무 조건적으로 작성된다. 오직 디스패치 된 액션만을 고려하여 새로운 상태값을 도출한다. 현재 상태가 어떻다라는 어떠한 로직에 기반을 두지 않는다. 이것은 버그를 유발할 수 있다. 어떤 액션들은 앱의 로직의 어떤 부분에 의존성이 있는 경우가 있는데 이때 그러한 상황 속에서 논리적으로 유효하지 않을 수 있는 경우도 있다. 예를 들면 request succeeded 라는 액션은 이미 상태가 loading 이라는 값을 가지는 경우에만 새로운 값으로서 가질 수가 있다. 또한 update this item 액션은 being edited 인 아이템일 경우에만 디스패치 될 수 있다. 한마디로 현재 상태값들에 따라 고려되어야 할 로직이 있다면 이를 고려해야한다는 것이다.

  - 이것을 고치기 위해서는 오직 액션 자체만을 무 조건적으로 다루는 것이 아니라, 리듀서들을 현재 상태와 디스패치된 액션, 이 둘의 조합이 새로운 상태값이 실제로 도출되었는지를 결정하는 state machine 으로 다루면 된다. (Detailed 설명 참고. finite state machine 에 대한 설명이 적혀있다. 현재 상태값들에 따라 다른 리듀서가 적용되어 idle 일때는 loading 상태로만 가고 loading 일 때는 success 나 failure 로만 가고 success 나 failure 일 때는 idle 로 만 가는 리듀서를 반환하는 리듀서를 구성하는 식으로 state machine 을 만들었다.)

 

* 복잡한 Nested/Relational 상태를 정규화 하라.

  - 많은 애플리케이션은 복잡한 데이터를 스토어에 캐싱한다. 데이터는 종종 API 로 부터 중첩된 형태로 받거나 또는 다른 엔터티들 사이에 관계를 가지는 경우가 있다. (가령 blog 가 users, posts, comments 를 가지는 것과 같이) 이런 것들을 normalized form 으로 store 에 저장하라.

 

* 액션들은 셋터가 아닌 이벤트로서 모델링하라.

  - 리덕스는 action.type 필드 내용이 무엇인지 상관하지 않는다. 이것은 그저 정의되어져야만 하는 것이다. users/update (현재형) , users/updated (과거형) , upload/progress (이벤트형) , users/setUserName (셋터형) 과 같이 작성될 수 있다. 어떤 것이든 상관없이 가능하다. 하지만 액션을 "발생한 이벤트"로서의 형태로 다루는 것을 추천한다. 액션들을 이벤트로 다루는 것이 일반적으로 좀 더 의미있는 액션 이름을 줄 수 있고 액션의 디스패치 횟수를 줄일 수 있으며 그리고 더 의미있는 액션의 로그 히스토리를 가질 수 있게 한다. 셋터 형태는 종종 너무 많은 개별 액션 타입들을 만들게 하고 많은 디스패치를 유발하며 액션의 로그는 덜 의미있게 한다. (Detailed 설명 참고. {type: "food/orderAdded", payload: {pizza: 1, coke: 1}} 과 같이 발생한 이벤트 형태로 작성하는 것이 좋다.)

 

* 의미있는 액션 이름 작성.

  - action.type 필드는 두가지 주요 목적을 가진다. (새로운 상태 도출에 이 액션이 다루어져야하는지 리듀서로직이 action.type 을 체크하는 용도와 Redux DevTools 히스토리 로그에 표시되는 용도) 이벤트로서 모델링하라는 위의 내용에서와 같이 action.type 의 내용은 어떤 것도 될 수 있고 어떤 것이든 리덕스에게는 중요하지 않다. 개발자인 당신 바로 이를 이용하는 입장에서 중요하다. 액션들은 의미있고 정보를 담고 있고 잘 설명된 type 필드를 가지는 것이 좋다. 이상적으로는 디스패치된 액션 타입들의 리스트를 통해서 읽을 수 있고 각 액션의 내용을 들여다보지 않아도 애플리케이션에서 무슨일이 일어났는지 알 수 있는 것이 좋다. 매우 일반적인 SET_DATA, UPDATE_STORE 와 같은 액션 이름들은 의미있는 정보를 제공하지 않기 때문에 사용하지 않는 것이 좋다.

 

* 많은 리듀서들이 같은 액션에 대응하도록 하라.

  - 리덕스 리듀서 로직은 많은 작은 리듀서들로 나누어지도록 되어 있고 각각은 독립적으로 그들 자신의 state tree 의 부분을 업데이트하며 이들이 모두 함께 다시 구성되어 root reducer 함수를 구성한다. 액션이 디스패치되었을 때 전체 또는 몇몇 리듀서에서 다루어질 수 도 있고 어떻한 리듀서들에서도 다루어지지 않을 수도 있다.

  - 가능하다면 많은 리듀서 함수들이 하나의 액션에 대해 분리되어 모두 다루질 수 있도록 하는 것이 좋다. 실제로 대다수 액션들은 일반적으로 하나의 리듀서 함수에서만 다루어진다. 이것도 괜찮다. 그러나 이벤트로서 액션을 모델링하고 많은 리듀서들이 해당 액션을 다루는 식으로 구성을 하면 일반적으로 어플리케이션의 코드베이스를 확장하기 좋고 한번의 의미있는 업데이트를 수행하기 위해 액션을 디스패치하는 횟수를 줄일 수 있다.

 

* 많은 액션들을 순차적으로 디스패치 하는 것을 피하라.

커다란 트랜잭션을 수행하기 위한 많은 액션들을 순차적으로 디스패치하는 것을 피하라. 안되는건 아니지만 이것은 여러 관련된 비싼 UI 업데이트를 유발하며 중간 state 들이 잠재적으로 애플리케이션의 다른 로직 부분에 의해 유효하지 않게 될 가능성도 있다. 관련된 모든 상태의 업데이트를 한번에 하는 single event 타입 액션을 디스패치 하는 식으로 하거나 또는 여러 액션 디스패치에 대해서 마지막에 한번의 UI 업데이트를 하도록 해주는 배치 애드온을 사용하는 것을 추천한다.

 

* 각 상태의 부분들이 어디에 위치할 지를 적절히 고려하라.

전체 어플리케이션의 상태는 하나의 트리 안에 저장된다는 구문은 문맥상으로만 해석하면 전체 앱의 모든 값은 리덕스 스토어에만 존재해야 한다고 해석될 수도 있지만 그런 의미가 아니다. 글로벌하고 앱 전반에 사용될 것이라 여겨지는 모든 값들은 하나의 장소에 존재해야 한다는 의미로 보아야 한다. 로컬 값들은 일반적으로 UI 컴포넌트 근처에 있게 하는 것이 좋다. 어떤 값을 리덕스 스토어에 넣을지 어떤값을 컴포넌트의 상태에 넣을지는 개발자에게 달렸다. ... 해당 링크를 참고하라.

 

* React-Redux Hooks API 를 사용하라.

컴포넌트와 리덕스 스토어 간 연결 방법으로 useSelector, useDispatch 와 같은 React-Redux hooks API 를 사용하는 것을 추천한다.

 

* 스토어로 부터 데이터를 읽어오는 컴포넌트들은 최대한 많이 연결하라.

더 조각조각난 컴포넌트들에서 스토어로 부터 데이터를 읽어들이는 것이 좋다. 이는 해당 상태에 해당하는 부분의 변화에 있어 UI 성능을 향상시킬 것이다. 예를 들어  <UserList> 에서 전체 user 리스트를 불러오는 것 보다 <UserListItem userId={userId}> 와 같이 하여 각 UserListItem 에서 각 user entry 를 가져와서 연결시키는 것이 좋다.

 

* connect 와 함께 사용하는 mapDispatch 의 경우 object shorthand 형태를 사용하라.

  - (data) => dispatch(...) 이렇게  막 긴 함수를 축약한 형태로 object 리턴되도록 하여 사용하라는 말 이지만 요즘은 useDispatch 훅만 사용하기 때문에 이걸 사용할 일은 없다.

 

* 함수형 컴포넌트에서 useSelector 를 여러번 호출하라.

  - 더 조각난 컴포넌트 들에서 스토어로 부터 데이터를 읽어들이는 것이 좋듯이 useSelector 도 최대한 조각난 부분들에서 각각 다 호출되어지는 방향이 좋다. 하나의 컴포넌트에 상태의 slice 에 있는 모든 필드가 필요한 상황이라면 useSelector 를 한번만 불러와도 된다. 굳이 이런 경우까지 더 조각난 부분에서 다시 또 불러오거나 할 필요는 없다.

 

* 정적 타이핑을 사용하라. (typescript 같은거)

 

* 디버깅을 위해 Redux DevTools 확장을 사용하라.  (다음과 같은 기능들을 제공해준다.)

  - 디스패치된 액션들의 히스토리 로그

  - 각 액션의 내용들

  - 액션이 디스패치된 후 마지막 상태

  - 액션 후 상태의 차이

  - 액션이 실제로 어디서 디스패치 되었는지를 보여주는 function stack trace

  - time travel 디버깅

 

* 상태를 위해서 Plain Javascript Object 들을 사용하라.

  - immutable.js 같은 특화된 라이브러리 사용 보다 state tree 를 위해 plain javascript objects 와 arrays 를 사용하는 것이 좋다. 불변성있게 상태 변화를 주고 싶다면 이미 redux toolkit 에는 적용되어 있는 Immer 를 사용하라.

 

 

 

 

 

 

 

[권고]

 

* 액션 타입은 domain/eventName 모양으로 작성 (todos/addTodo [domain/action])

 

* Flux Standard Action 컨벤션 대로 액션을 작성 (type, payload, meta, error)

 

* 액션 생성자 사용

 

* 비동기 로직을 위해 Thunk 를 사용

  - 대부분의 async 로직 처리에는 Thunk 로 충분하다. async/await 문법 사용에도 매우 적합하다. cancelation, debouncing, running logic after a given action was dispatched 또는 background-thread 타입의 행위와 같은 정말로 복잡한 비동기 워크플로우를 가진다면 더 강력한 Redux-Saga, Redux-Observable 과 같은 비동기 미들웨어 추가를 고려해보라. 기본적으로는 Thunk 를 사용하는 것을 권고.

 

* 복잡한 로직은 컴포넌트 밖으로 옮겨라.

  - 과거 Container/Presentational 패턴 (훅 나온 이후로 요즘은 그냥 구분없이 사용한다.) 과 같이 Component 에서는 단순히 props 를 받아 UI 를 보여주는 것 이고 또한 class component 의 lifecycle 메소드에서 비동기 로직을 다루는 것은 관리하기 힘들기도 해서 복잡한 로직은 Component 밖으로 빼는 것이 권고된다.

  - 여전히 복잡한 동기 또는 비동기 로직을 component 밖으로 옮기는 것을 권고한다. (보통은 thunks) 로직이 store state 로부터 읽을 필요가 있다면 특별히 더욱 그러하다.

 

* store state 로 부터 읽어올 때는 Selector 함수를 사용하라.

  - store state 를 읽어올 때 가능하다면 memoized selector 함수를 사용하는 것을 강력히 추천한다. Reselect 를 사용하길 권한다. 그렇다고 너무 강제적으로 할 필요는 없고 얼마나 자주 읽어오고 업데이트 되는지 등을 고려하여 적절히 선택해서 사용하라.

 

* selectThing 과 같이 Selector 함수를 명명하라.

  - selector 함수 이름은 리턴되는 값에 대한 설명과 조합하여 select 를 앞에 붙여주는 것을 추천한다. 예로 selectTodos, selectVisibleTodos, selectTodoById 와 같이 하라.

 

* 리덕스에서 폼 상태를 넣는 것을 피하라.

  - 대부분의 폼 상태는 리덕스에 넣지 마라. 대부분의 경우 데이터는 실제로 전역이 아니며 캐싱되지 않으며 한번에 여러 컴포넌트에서 사용되어지지 않는다. 게다가 리덕스에 폼을 연결하는 것은 성능 저하와 실질적으로 전혀 장점이 없이 모든 하나의 변경 이벤트에 액션들을 디스패치하는 것을 유발한다. (name: "Mark" 에서 name: "Mar" 로 한글자 입력에 대한 time-travel backwards 는 필요하지 않을 것이다.)

  - 리덕스에서 데이터가 궁극적으로는 끝이나도 로컬 컴포넌트에서는 폼 수정에 대한 상태를 유지하고 싶을 경우가 많으며 오직 사용자가 폼 작성을 완료하면 한번에 리덕스 스토어를 수정하는 액션을 디스패치하길 바랄 것이다.

  - 리덕스에서 폼 상태를 유지하는 것이 실제로 필요한 경우도 있는데 WYSIWYG 에서 수정된 아이템의 속성들의 라이브 미리보기 와 같은 경우 그러하다. 하지만 대부분의 경우 이것은 필요없다.

 

 

 

반응형

'REACT & NODE' 카테고리의 다른 글

dns  (0) 2021.05.11
테스트 json api 제공 서비스  (0) 2021.05.10
redux toolkit 관련 참고용 코드들  (0) 2021.05.09
access token, refresh token 그리고... jwt? 그냥 unique token  (2) 2021.05.02
redux-requests  (0) 2021.04.21

댓글

Designed by JB FACTORY