[LUVOOK] 220610-220612 프로젝트 일지 + TIL

프로젝트 일지

context API + useReducer + custom hook 을 이용한 전역 상태 관리

예전에 TIL에서 전역 상태 관리 부분을 내가 맡아보겠다고 적었는데, 역시 우리팀 답게 의견 반영이 잘 되어서, 전역 상태를 구현하게 되었다. 사실 어느정도는 강의 코드보고 적당히 구현하려다가 큰 코 얻어맞고, 한 번 생각을 엎어본 다음에 처음부터 다시 공부해서 구현하였다. 따라서 프로젝트 일지에는 내가 context를 구현하면서, 어떤 사고 흐름을 거쳐서 구현하였는지에 대해서 서술한다. ~이래서 기초가 탄탄해야하나 보다…~

구현하고 나서 이렇게 작성은 하지만.. 나는 아무것도 모르기 때문에 언제 이 내용을 엎어버릴지 모른다…

1. 전역적으로 사용해야 할 상태를 체크하기 

  • React 공식문서에도 나와있듯이, context는 꼭 필요한 곳에만 사용해야한다. 공식문서에 따르면 context의 주된 용도는 다양한 레벨에 네스팅된 많은 컴포넌트들에게 데이터를 전달하는 것이다. (React 공식 문서 뿐 만이 아니라, context에 대한 다양한 article 들에서 해당 내용이 강조되어 있다.)
  • 따라서, context 를 사용하기 전에 고려해야할 것은, 컴포넌트 구조에서 어떤 상태를 컴포넌트 구조와 상관없이 공유하는가? 이다. (부모에서 계속 props를 받아서 사용이 필요한 경우에는 오히려 내려 받는 것이 자연스럽다)  
    즉, 앱의 전체 동작을 생각해보면서 전역적으로 사용되게 될 상태가 무엇인지 체크하는 과정이 필요하다.
  • 대략적인 앱의 전체 동작과, 프로젝트 요구사항을 체크하면서 전역적으로 사용해야 할 상태에 대해서 체크하였다. (자세하게 어떤 내용인지는 우선은 생략하도록 하였다.)
  • 또한 context를 자주 사용하는 부분이 존재하는데, 이 부분을 보면서 그들의 공통점을 추출해보는 것도 좋은 접근 방법인 것 같다. 링크로 걸어둔 article 에서는 다음과 같이 설명한다.
    - app 전체에서 공유해야하는 인증(authentication) 상태
    - app 전체에서 공유해야하는 앱의 테마(theme)
    - 많은 컴포넌트들이 공유하는 값들
    개인적으로는 트리 구조를 생각해가면서, 각 노드들로 들어갈 props들을 생각해보고, 이 props 들 중 이질적인 속성이많이 공유될 때, context를 고려해보는 방법이 생각하기 편했다.

2. state를 성격에 따라 분류하기

  • 전역적으로 관리해야하는 state들을 결정하였다면, 이들을 성격(그 성격이 위에서 말했던 app의 theme과 관련될 수도 있고, authentication과 관련된 부분일 수도 있다)에 따라 분류한다.
  • 이 ‘성격’이 같은 state들은, 같은 context에서 관리되어야 하는 부분이다. 이 후에 한 context 안에서 이 state들에 따라서 상태 관리 로직을 작성한다.

3. 전역 상태 관리 로직 작성하기, reducer 작성하기

  • context 내부에 있는 state들에 관하여 상태 관리 로직을 작성한다. 이 상태들을 useState 혹은 useReducer를 사용하여 관리한다. useReducer의 경우, 다음 state가 이전 state에 의존적인 경우나 복잡한 로직을 구현할 때 이용한다.
  • 다음 state가 이전 state에 의존적인 경우 를 풀어서 설명해보자면, useReducer 은 state라는 데이터베이스를 가지고 reducer라는 데이터베이스의 수정에 관한 로직을 가진 형태라고 생각할 수 있다. 이 데이터베이스를 생각해본다면, 데이터베이스 내의 데이터들은 항상 이전 state에 의존적이라고 볼 수 있다.(데이터를 업데이트 시키는 것은 이전 데이터를 바탕으로 업데이트 하므로) 
    + 이러한 이유로 reducer에 작성하는 로직의 모든 action에서 상태를 업데이트할 때 state들을 spread 하는 과정이 필요하다.
  • useReducer를 사용하는 경우 reducer를 작성한다. 이 reducer은 상태를 제어하는 로직을 가지며, 보통 switch 문으로 작성한다. reducer 내부의 로직은 state와 action 객체를 통하여 작성되는데, state는 reducer를 통하여 관리할 상태를 의미하고 이 action 객체는 useReducer를 사용하기 위해 dispatch를 사용하는 부분에서 넘기는 type (필수) 과 payload (선택) 를 프로퍼티로 가진 객체이다.

4. Consumer 에서 사용할 상태 로직 작성하기

  • state의 경우 consumer에서 소비함과 동시에, 상태를 변경시킬 부분이 있으므로, 이 부분에 대한 로직을 작성한다.
  • 예를 들어, 앱의 테마 정보를 전역 상태로 가지는 context가 있다고 하자. consumer에서 버튼을 클릭하면 theme를 다크모드로 바꾸고 싶다. 그럴 경우, consumer의 button을 눌렀을 때, 이 버튼의 onClick에 전역 상태를 변경하는 부분이 필요할 것이고, 이 부분의 함수를 context에서 제공한다.

5. (선택사항) Consumer에서 사용할 Custom Hook export 

  • custom Hook 을 작성하지 않는다면, consumer 쪽에서 계속하여 useContext를 import 하여 context를 만드는 과정이 필요하므로, context에서 이 동작을 하는 custom hook을 export한다.
  • consumer 쪽에서는 context에서 이 custom hook 만을 import 하여 사용할 수 있다.

TIL 

☔ : 내 생각의 비중이 높음

TECH CONCERT: FRONT END 2019 - 데이터 상태 관리. 그것을 알려주마

  • FE 개발에서 state는 실시간으로 비동기로 계속 변화하므로, 상태가 언제, 어떻게, 왜 변화했는지 제어할 수 없는 상황에 이르게 되었다. 이러한 문제점을 극복하기 위하여 jQuery, AngularJS, Redux로 상태 관리 패러다임이 변하기 시작하였다.

이전의 방식 (jQuery, AngularJS)

  • jQuery의 경우, 마크업에 jQuery를 바른다. 라는 말에서 알 수 있듯이, 각 DOM 요소에 jQuery를 통하여, data attribute를 통해 각각의 DOM 요소에 상태를 저장하는 방식을 사용하였다. 따라서 상태 변경이 필요한 DOM 요소를 먼저 선택하고, 이 후에 필요한 작업을 수행한다.
    이 경우, 각 element에 종속적인 상태관련 로직이 실행될 때 (예를 들어 element A, B, C가 있고 A의 상태를 B에서 사용하고, B의 상태를 C에서 사용하는 경우) A → B  C의 과정에서 B → C 가 일어나는 도중에 A의 상태가 변경된다면, 최종적으로 C에서 정제된 상태는 올바른 상태라고 할 수 없다.
    ☔ 또한, state들을 element들이 가지게 되므로, state들이 복잡해진다면 상태가 언제, 어떻게 바뀌는지 알기가 정말 힘들다. (state를 알아내기 위해 각 element 별로 콘솔을 찍어봐야 한다…)
  • AngularJS의 경우, 양방향 데이터 바인딩(two-way bind)을 가능하게 하였기 때문에, 데이터의 값이 변경되면 출력도 자동으로 수행하도록 되었다. 
    이 경우, controller 라는 지시자를 이용하여, scope를 생성하면, scope 내에서 controller에 지시된 데이터들은 값이 바뀌면 출력이 바뀐다. 즉, 더 이상 데이터를 위하여 DOM에 접근하지 않아도 된다.
    AngularJS의 경우, service - controller - view 의 구조로 상태과 관리된다. 각각의 view가 있고 이 view들은 controller에 연결되어있다. 이 controller은 각각의 logic과 state를 가지며, 이 logic과 state를 통하여 state 에 따른 view 가 만들어지는 것이다. 그리고, 각 controller에서 사용되는 공통의 (전역) logic과 state의 경우, service에 집어 넣고 쓰게 되고, 이 serive는 controller에서 모두 사용 가능 하다.
    ☔ AngularJS의 문제점은 양방향으로 데이터가 바인드 되어 있으므로, 상태가 어디서 변경되었는지는 어떤 controller에서 발생했는지 추적하면 되기에 앞선 jQuery 방식보다는 추적하기 쉽지만, 상태가 언제, 왜, 어떻게 변화했는지는 알기가 어렵다. 
    따라서 상태가 언제, 왜, 어떻게 변화했는지 알기가 어려웠기 때문에 Redux를 이용해서 상태관리를 하게 되었다.

Redux 

  • Redux : Redux는 FLUX + CQRS + Event Sourcing 의 합성어 이다.
  • flux 패턴 : 이 패턴에서 상태는 단방향으로만 흘러간다. (☔ react의 props 가 단방향으로 흘러간다는 점과 상당히 유사하다.)

[##Image|kage@c1Pe00/btrEzJ0rSC2/Zstakv2o1uGTZxbLMecZkk/img.png|CDM|1.3|{“originWidth”:636,“originHeight”:186,“style”:“alignCenter”,“caption”:“flux 패턴 (출처 : https://medium.com/hcleedev/web-react-flux-%ED%8C%A8%ED%84%B4-88d6caa13b5b)“}##]

view 는 store 에 있는 상태를 display 하는 역할을 한다. 만약 store의 상태를 변경하고 싶다면 dispatcher를 통하여 store의 상태를 업데이트 한다. (이 전의 AngularJS의 방식에서는 양방향으로 핸들링 하였으므로 view에서 store의 값을 바꾸고, store에서 view를 바꾸는 방식이였다.)
☔ 이 방식은 위의 useReducer 를 사용하는 것과 아주 유사하다. useReducer에서도, component (view) 단에서 state를 핸들링하기 위하여 dispath를 이용하는 데, 이 dispatch를 위해 넘겨주는 객체가 action 객체이므로, 결론적으로는 view 에서 action 을 통하여 dispatcher를 통하 상태를 변경하는 방식이다.

  • CQRS (Command and Query Resoponsibility Segregation) 
    Backend 의 database 개발에서 사용하는 방식으로, 상태를 읽어오는 부분과 상태를 업데이트(update, delete, post 등)하는 부분을 분리시킨다.
    즉, 앞의 flux 패턴에 적용시켜보면, view 는 store에 있는 상태를 읽기만 하고, 상태를 업데이트 하는 로직은 dispatcher 에서 관리한다.

☔ 위의 flux와 CQRS를 통하여, 상태가 언제, 왜 변경하였는지 (데이터 흐름이 단방향이고, 변경 로직은 한 부분에서만 담당하므로) 파악할 수 있다.

  • EventSourcing
    내가 상태를 변경하고 싶다면, 이를 event sequence로 저장한다. 즉, 상태를 바꾸고 싶다면 이벤트를 통하여 기록을 남긴다. 이 이벤트를 action이라고 하고, 이 action은 log로 남게 된다. 이를 통하여, 어떻게 상태가 바뀌었는지 체크할 수 있다.

이 세 가지를 통하여 Redux가 만들어졌다.

  • Redux 
    앞의 dispatcher 의 기능을 reducer 가 수행하고, actioh 과 reducer의 중간에 middleware가 있는 형태이다. 이 middleware는 reducer를 통하여 상태를 바꾸기 전에, api 호출ㅇ르 하거나 데이터를 바꿔야 할 로직이 있다면 middleware 에 집어 넣어서  처리를 하는 개념이다.
  • redux에서 store는 하나의 JS 오브젝트로 관리된다. 앞에서 말헀다 싶이, redux에서는 상태를 읽는 부분과 변경하는 부분이 분리되어있으므로 이 store는 오직 읽기만 가능하다. (즉, 이 store에 직접 접근하여 상태를 변경할 수 없다.) 그래서 상태의 불변성을 유지할 수 있다.

useReducer 

  • useReducer의 상태 관리 방식은 backend database를 관리하는 것에 비유할 수 있다. (☔ 위에서 언급한 flux 패턴과 CQRS를 결합시킨 형태로 생각하면 된다.)
  • ☔ useReducer가 관리하는 state를 데이터베이스라고 생각하고, 이 database를 업데이트하는 로직을 dispatch 라고 생각한다. 그리고 이 dispatch를 위하여 flux 패턴에서 action 을 넘겨야 하므로 dispatch 에는 action 객체가 넘어가고, 이 action 객체는 type과 payload로 이루어져있는데 type의 경우 database의 수정 메소드 (GET, POST 와 같은 부분) 을 뜻하고, payload는 database api 호출을 위한 optional 한 부분이라고 생각하면 된다. 따라서 이 action 객체의 type에 따라서 reducer 에 다른 로직이 구현된다. (이는 method 에 따른 데이터베이스 업데이트 방식이라고 생각한다.)

팀 개발을 위한 Git, GitHub 입문 (인프런)

  • 사고친건 아니였는데 사고 칠 미래가 갑자기 주마등처럼 보여서 급하게 git 강의를 들었다ㅋㅋㅋ 하도 git 관련으로 사고를 많이 쳐와서, 오히려 강의가 이해가 아주 잘 되는 이상한 현상이 벌어졌다… 화요일까지 다듣는게 목표다.

간단하게 정리하는 git 협업 시나리오

1. 내 폴더에서 버전 관리를 할 것이라는 것을 알려준다. 이는 로컬 저장소를 생성하겠다는 의미이다.
git init 명령어를 작성하면 .git 이라는 숨김폴더가 만들어지는데 이 숨김폴더가 로컬 저장소 이다.

2. 어떤 기능 만들고 이 기능을 위하여 변경된 파일들을 선택후 (git add), 기능 단위를 덩어리로 만든다. (git commit)

3. 로컬에 있는 파일을 원격에 올린다. git push origin main

4. 다른 곳에서 협업을 할 때, 원격 저장소의 내용을 git clone 을 통하여, 원격 저장소와 완전히 똑같은 버전 정보를 받아온다.

5. 그리고, 로컬에서 언제든지 다른 사람이 개발한 버전을 받아오기 위하여 pull 을 사용할 수 있다.

잘못된 내용을 수정해주신 익명의 프롱이님!!! 감사합니다!! 오늘은 버그가 없는 하루일거에요~!~!~!

Reference

더보기

Youtube - TECH CONCERT: FRONT END 2019 - 데이터 상태 관리. 그것을 알려주마

How to use React useReducer hook like a pro - Devtrium

How to use React Context like a pro - Devtrium


Written by@AllSilver
철학적인 개발자를 꿈꿉니다.

GitHub