[TIL No.21] Git-commit-convention / Redux
오늘의 주제
- git commit convention
- redux
- 돌아온 Cmarket with redux 🤨🤨🤨
#1. git commit convention
왜 이제서야 이것에 대해 관심을 가지는지 의문이지만... 이제서라도 관심을 가지고 조그만 과제하나를 할 때라도 신경써서 커밋을 해보고자 한다.
내용에 대해 찾아볼 수 있는 링크는 다양하고, 무엇이 정석이다라고 하기 어려울만큼 컨벤션이라는 것은 어느정도 틀은 있으나 회사에서 또는 프로젝트 내에서 정하기 나름이라고 생각이 된다.
이번에 내가 한 커밋을 통해 가볍게 맛만 보자.
오후 2시쯤에 페어분과 함께 벌써 끝난건가..? 하면서 과제를 제출하고 커밋을 한 번 해놨었다. 이 때 커밋컨벤션을 따라서 해보자 싶어서 새로운 기능을 추가했으므로 FEAT라고 type을 설정하고 추가한 기능을 우리말로 적어놓았다. 영어로 적는 것이 좋다고 하지만 일단은 친숙하게 느껴보고 싶어서 이렇게 적어봤다.
그리고 body 부분은 상세하게 설명할수록 좋다니까 컴포넌트나 함수 이름까지 세세하게 적어주면서 어떤 함수를 어떤 역할로 만들었고 상태관리를 위해 어떻게 했고 작동이 어떻게 되는지까지 자세하면 좋겠지만 위와 같이 간단하게 적어놓았다.
그리고 #1 이런식으로 꼬리말을 붙여서 넘버링을 해줄 수도 있으나 그 정도의 프로젝트는 아니므로 일단은 생략하였다.
type은 뭐고 body는 뭐고 꼬리말은 무엇인지~ 내가 설명하기 보다는 다음과 같은 링크를 통해 살펴보면 더 좋을 것 같다.
Writing Better Commit Messages. See how a minor change to your commit…
git 커밋 컨벤션 설정하기
#2. redux
대망의 리덕스이다.. 자 덤벼라!! 얼마나 어려운지 보자..😁😁😁
리덕스 개념을 간단하게 요약해보고 실습을 통해 적용하면서 어떻게 활용되는지 찬찬히 살펴보자.
*** 이하의 내용은 거의 복붙 및 번역임을 감안해주시기 바랍니다. ^^
(1) data 흐름으로 살펴본 redux의 구조
Redux에서 data 흐름의 방향: Action → Dispatch → Reducer → Store
Redux의 상태 관리 순서
- 상태를 변경시키고자하는 이벤트 발생 시, 변경될 상태에 대한 정보가 담긴 Action 객체 생성.
- Dispatch 함수의 인자로 Action 객체가 전달되고, 이는 Reducer 함수로 전달됨.
- Action 객체의 값에 따라 전역 상태 저장소 Store의 상태가 변경.
- 상태 변경 시 > 화면을 다시 렌더링함.
(2) Store
store란 어플리케이션의 global state(전역 상태)를 저장해두는 공간을 말한다. store 또한 JavaScript 객체 중 하나인데 다른 global object들과 다른 특별한 함수와 기능을 지닌다.
리덕스 store를 다룰 때, 직접적으로 state를 변경해서는 안된다. 그 대신 action 객체에 어플리케이션에서 "어떤식으로 작동해야하는지"를 만들어 놓고 이 액션객체가 스토어에 어떤 일이 일어났는지 전달한다.
액션이 dispatch되면 store는 root reducer를 실행하고, 액션을 기반으로 기존의 상태로부터 새로운 상태를 계산한다.
결과적으로 store는 subscribers에게 UI가 새 데이터로 업데이트 될 수 있도록 상태가 업데이트 되었음을 알린다.
store는 createStore 라이브러리로 store instance를 생성할 수 있다. 생성하면서 ()안에 업데이트에 관여할 reducer를 넣어줌으로써 reducer와 연결하여 사용할 수 있다.
(3) Action과 Dispatch
(4)Reducer
(5)redux-hooks
(hooks는 react 16.8부터 도입된 개념으로... )
redux에서 사용할 수 있는 hooks로는 useSelector와 useDispatch가 있다.
1)useSelector
State에서 특정 값을 가져와서 보여주고 싶을 때 사용한다. 기존에는 props로 state를 넘겨주어 console.log(state)와 같은 형태로 상태 값을 출력했다면, useSelector로 다음과 같이 state를 꺼내와서 사용할 수 있다.
const state = useSelector(state => state); // 이렇게 state 그대로 꺼내올 수도 있고
const state = useSelector(state => state.itemReducer); // 이렇게 약간 다르게 꺼내올 수도 있다.
2)useDispatch
dispatch 함수를 반환하는 메서드이다. action 객체를 reducer로 전달하기 위해 다음과 같이 사용한다.
#3. Cmarket with redux
지난번에 상태관리 라이브러리의 도움없이 만들었던 Cmarket의 경우 기능 작동은 잘 되었지만, 상태관리 측면에서 굳이 상태를 다루지 않아도 될 컴포넌트를 거쳐서 타겟이 되는 컴포넌트에 넘겨주어야 했다. a.k.a. props drilling.
이번에 redux를 이용해서 관리해 본 결과 코드의 양은 더 많아졌지만, 이는 notification같은 추가기능을 넣거나 파일의 수가 파일관리를 위해 늘어나는 등의 눈속임 때문이지 결코 지난번보다 코드의 양이 훨씬 많아서 그런 것이 아니다. 오히려 상태관리만 생각해보면 매우 효율적이고 편해졌다. 단지, 내 숙련도가 불편할 뿐...(redux 공식문서를 이번에는 빡세게 보면서 보충할 예정..!!)
redux의 기본요소와 과제의 흐름에 따라 각각의 기능을 어떻게 구현했는지 되돌아보자.
(1) 폴더의 구조 확인
이번에도 역시나 많은 파일들과 폴더들로 이루어진 폴더를 딱 열면 버퍼링이 걸리기 시작하므로 빠르게 파일들의 구조를 파악하려고 노력했다.
이번 폴더는 저번과 달리 src 폴더 밑에 actions, components, pages, reducers, store 이렇게 다섯가지 폴더로 구성되어서 각각 redux의 action,reducer,store와 관련된 js파일이 있는 폴더들과 react component와 UI적으로 구분된 page들이 담겨있는 폴더로 구분된다.
지난번 포스팅에 의하면 cartItems와 items 상태를 변경하기 위해 App.js와 ItemListContainer.js 또는 ShoppingCart.js 파일을 거쳐서 이벤트 핸들러 함수에 전달되어야했는데 이번에는 store에 저장해두고 필요한 부분에서만 불러서 사용할 수 있었다.
store는 미리 만들어져 있었는데 다음과 같이 구현되어 있었다.
//store.js
import { compose, createStore, applyMiddleware } from "redux";
import rootReducer from '../reducers/index';
import thunk from "redux-thunk"; // 추가학습 요소 (1) thunk란?
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? // 추가학습 요소 (2) redux devtools
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
createStore를 이용하여 rootReducer와 연결하여 store를 생성하고 export로 전역에서 접근가능하도록 해준 것을 확인할 수 있다.
rootReducer에 어떤 내용이 있는지 너무 궁금하지만, data 흐름의 시작인 action 객체부터 살펴보자.
action 객체는 redux에서만 사용하는 것은 아니고 useReduce에서도 사용하는데, redux 한정으로 서술하겠다. redux에서는 action객체는 필수적으로 type 값을 가져야하고 나머지는 임의대로 주어도 상관없다. type은 대문자와 Snake_Case로 작성한다.
export const addToCart = (itemId) => {
return {
type: ADD_TO_CART,
payload: {
quantity: 1,
itemId
}
}
}
export const removeFromCart = (itemId) => {
return {
//TODO
type: REMOVE_FROM_CART,
payload: {
itemId
}
}
}
export const setQuantity = (itemId, quantity) => {
return {
//TODO
type: SET_QUANTITY,
payload: {
itemId,
quantity
}
}
}
//여기까지가 직접 구현한 기능과 관련된 부분
export const notify = (message, dismissTime = 5000) => dispatch => {
const uuid = Math.random()
dispatch(enqueueNotification(message, dismissTime, uuid))
setTimeout(() => {
dispatch(dequeueNotification())
}, dismissTime)
}
export const enqueueNotification = (message, dismissTime, uuid) => {
return {
type: ENQUEUE_NOTIFICATION,
payload: {
message,
dismissTime,
uuid
}
}
}
export const dequeueNotification = () => {
return {
type: DEQUEUE_NOTIFICATION
}
}
// 장바구니 추가와 관련된 메세지가 이미 구현되어 있던 부분!!
액션 생성자 함수(action creator)를 이용하여 전부 생성되어 있었다. 액션객체는 반드시 action creator로 사용할 필요는 없지만 이렇게 하는 방법을 쓰다보니 이것을 이용하지 않으면 이하에 사용할 reducer에 어떻게 사용할지 갑자기 헷갈린다.(2023.02.27 곧 업데이트 예정)
이번에 추가한 기능에서는 addToCart, removeFromCart, setQuntity를 사용하였다. 각각 장바구니에 추가하고 장바구니에서 삭제하고 장바구니 안의 항목의 수를 변경하는 기존에 구현했던 기능이라 설명은 생략한다.
Dispatch를 이용하여 Reducer에 생성한 action 객체를 전달해주기만 하면 임무 완료이다.
itemReducer라는 리듀서를 편집하여 기능을 추가하였는데 그 코드는 다음과 같다.
import { REMOVE_FROM_CART, ADD_TO_CART, SET_QUANTITY } from "../actions/index";
import { initialState } from "./initialState";
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
//TODO
return Object.assign({}, state, {
cartItems: [...state.cartItems, action.payload]
})
break;
case REMOVE_FROM_CART:
//TODO
let newCart = state.cartItems.filter(el => el.itemId !== action.payload.itemId)
return Object.assign({}, state, {
cartItems: newCart
})
break;
case SET_QUANTITY:
let idx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)
//TODO
state.cartItems[idx].quantity = action.payload.quantity;
return Object.assign({}, state, {
cartItems: state.cartItems
})
break;
default:
return state;
}
}
export default itemReducer;
여기서 잠깐... 분명 store를 살펴볼 때, rootReducer와 연결해주었지 itemReducer와는 연결한 적이 없고 이대로면 분명 작동을 안하는 것이 맞다. 그래서 아까 store에서 연결된 rootReducer의 경로를 보니 index.js라는 파일에 있는 rootReducer였고 그 곳에 가보니 combineReducers를 사용하여 다음과 같이 두 리듀서를 묶어서 연결해주어 작동될 수 있었다.
import { combineReducers } from 'redux';
import itemReducer from './itemReducer';
import notificationReducer from './notificationReducer';
const rootReducer = combineReducers({
itemReducer,
notificationReducer
});
export default rootReducer;
이렇게까지 하고나니 테스트는 다 통과되고 의외로 간단하게 끝났다 싶었다.
그런데, useSelector를 직접 사용해보지도 못한 채 이미 구현된 것을 구경하고 이용만해봤고, combine하는 것도 사실 어려운 것은 아니지만 직접 해본 것이 아니라 손에 익지는 않는 것 같고, notification 기능은 정말 구현이 잘 되어 있는데 이거 어떻게 구현하는거지 싶어서 내가 직접 한 것보다는 탐구할 대상이 많다고 느껴졌다.
당장 이 모든 내용을 정리 할 수는 없지만 오늘 공부한 내용이니 최대한 찾아보고 TIL 작성을 완료하되 추후에 더 공부함에 따라 업데이트를 하겠다.
++ 추가정보
추가학습 요소
(1) thunk란?
redux-thunk는 리덕스에서 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어 라는데... 이것도 벨로퍼트 책을 보며 실습하면서 업로드 할 예정.
(2) redux_devtools
리덕스 상태들이 어떻게 관리되고 있는지 리스트로 볼 수 있는 툴.
(3) propTypes
props에 지정한 타입이 들어오지않으면 오류메세지를 띄워주는 Typescript 같은 기능이라는데... 조금만 더 찾아보고 사용해보면 좋겠다.(노마드코더 강의에서도 알려준다 하더라!)
(4) React - styles(feat. airBnB)
게시글로 업로드 예정...
출처 : 리덕스 공식 문서, 코드스테이츠 유어클래스