1990년대 에 만들어진 디자인 패턴
디자인 패턴에 대한 오해
5주차는 그동안 가졌던 편견이 많이 깨지는 시간이었습니다.
소프트웨어에는 다양한 디자인 패턴이 존재합니다. 싱글톤, 옵저버, MVC, MVVM 등등..
그런데 지금까지 실무에서 사용한적이 없고 필요가 없었으니 반쪽짜리 이해만 한 상태였습니다.
애초에 디자인패턴은 객체지향 언어가 주류였을 시절 여러 문제들을 해결하기 위해 나왔었고
함수형 프로그래밍이 가능한 javascript 언어의 특성상 20년도 전에 만들어진 디자인 패턴들을 사용할 일이 많이 없었습니다.
게다가 React 가 많은 것들을 추상화 레이어로 한단계 가려주니 레이어 안쪽에서 여러 디자인 패턴들을 사용하는 것과 별개로
hooks와 커스텀hook 만으로 충분히 개발이 가능했습니다.
그럼 전통적인 디자인패턴은 배울 필요가 없는건가?
직접 사용하지 않는 것과 별개로 우리가 사용하는 대부분의 라이브러리 내부에는 이러한 패턴들이 감추어져 있습니다.
우리가 '딸깍'으로 만든 전역 상태의 깊은 곳에선 싱글톤 패턴과 옵저버 패턴의 조합이 있었습니다.
그리고 개발자간 커뮤니케이션을 할 때도 필요합니다.
디자인패턴을 알아야 기능의 설명이 아닌 개념의 전달이 된다고 생각합니다.
"하나의 스토어를 두고 해당 스토어를 구독을해서 구독을 하고있는 모든 곳에서 변경 사항을 감지하게 했어요" 를
=> "옵저버 패턴을 썼어요" 로 간단히 설명할 수 있습니다.
프론트엔드가 진짜 배워야 할 패턴들
1. 함수형 프로그래밍
함수형 프로그래밍은 순수 함수, 불변성, 선언적 프로그래밍 등의 개념을 중심으로 하는 프로그래밍 패러다임입니다. 이는 코드의 예측 가능성과 테스트 용이성을 높입니다.
2. 컴포넌트 기반 아키텍쳐
UI를 재사용 가능한 독립적인 부분들로 나누는 설계 방식으로, 현대 프론트엔드 프레임워크의 핵심 개념입니다.
3. 상태 관리 패턴
애플리케이션의 데이터 흐름을 관리하는 방식으로, Context Api, Redux, Zustand 등의 라이브러리가 이를 구현합니다.
4. 비동기 프로그래밍 패턴
Promise, async/await 등 비동기 작업을 효과적으로 다루는 패턴들입니다.
함수형 프로그래밍과 개발의 상관관계
함수형 프로그래밍이 뭔데요? 그거 하면 개발 잘하나요?
함수형 프로그래밍은 프로그램을 더 간단하고, 예측 가능하며, 테스트하기 쉽게 만드는 것을 목표로합니다.
위의 목표를 향해 조금 더 딥 다이브 해보겠습니다.
1. 코드를 데이터 / 계산 / 액션으로 분류해야합니다.
const 유저데이터 = {
name: "김항해",
age: 7
};
const 계산함수 = (a, b) => {
return a + b;
// 동일한 input에 대해 동일한 output을 보장합니다 (*반드시 input과 output이 있습니다)
// 외부 상태에 의존하지 않고, 실행 시점에 관계없이 결과가 동일합니다.
};
const [name, setName] = useState("김항해")
const 액션함수 = () => {
setName('양금명');
// 외부 세계와 상호작용 하는 함수입니다
// side effect를 발생시키거나 외부 상태에 의존합니다
// 실행 시점과 환경에 따라 결과가 달라질 수 있습니다
// 예: 파일 읽기/쓰기, DB 조회/수정, setState 등
}
데이터는 구분하기 쉽습니다. 하지만 계산함수와 액션함수는 그렇지 않은것 같습니다.
2. 계산을 분리해서 액션을 최대한 작게 만드는 것이 핵심입니다
예시를 통해 액션에서 계산을 분리해보겠습니다.
/// 예시 코드
var shopping_cart = [];
var shopping_cart_total = 0;
// 액션 함수
function add_item_to_cart(name, price) {
// 외부 환경을 (전역 변수) 변경시키니 액션입니다
shopping_cart.push({
name: name,
price: price,
});
}
// 액션 함수
function calc_cart_total() {
// 외부 환경을 (전역 변수) 변경시키니 액션입니다
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}
// 외부 환경을 변경시키니 액션입니다
set_cart_total_dom();
}
모든 함수에서 side effect가 발생해 테스트하기 어렵습니다.
계산함수를 최대한 분리하고 액션함수를 작게 만들어보겠습니다.
// 액션 함수 덩어리를 (계산 -> 액션) 으로 나누었습니다
function add_item_to_cart(cart, name, price) {
const newCart = get_added_cart(cart, name, price);
setCart(newCart); // 이 함수가 종착지라면 액션을 호출합니다.
}
// get_added_cart는 계산함수여서 테스트 가능합니다🎉
const get_added_cart = (cart, name, price) => {
return [
...cart,
{
name: name,
price: price,
},
];
};
// 액션 함수 덩어리를 (계산 -> 액션) 으로 나누었습니다
function calc_cart_total(cart) {
const shopping_cart_total = calc_total_price(cart);
set_cart_total_dom(shopping_cart_total);
}
// calc_total_price는 계산함수여서 테스트 가능합니다🎉
function calc_total_price(cart) {
return cart.reduce((val, item) => {
val + item.price;
}, 0);
}
get_added_cart 함수
1. cart를 인자로 받아 불변성을 유지해 외부환경에 영향을 주지 않습니다
2. 동일한 input에 동일한 output이 나옵니다
calc_total_price 함수
1. 명령형인 for문 대신 *고차 함수(선언적) reduce로 작성해 코드를 추상화합니다
2. 동일한 input에 동일한 output이 나옵니다
* 함수형 프로그래밍에선 고차 함수 (map, filter, reduce 등)를 사용하는 방식이 선호됩니다.
함수형 프로그래밍을 적용하니 테스트 가능한 계산함수가 많아졌고 그만큼 액션함수가 작아졌습니다.
side effect가 적어져 보다 안정적인 환경에서 프로그래밍을 할 수 있게 되었습니다🥳
이번엔 함수형 프로그래밍을 리액트에 적용해보겠습니다.
장바구니에 관련된 컴포넌트입니다. 클릭한 상품을 카트에 넣어주는 함수가 있네요.
export const CartPage = () => {
const [cart, setCart] = useState([]);
const addToCart = (product) => {
setCart(prevCart => {
const existingItem = prevCart.find(item => item.product.id === product.id);
if (existingItem) {
return prevCart.map(item =>
item.product.id === product.id
? { ...item, quantity: Math.min(item.quantity + 1, product.stock) }
: item
);
}
return [...prevCart, { product, quantity: 1 }];
});
}
return (
<div>
<button onClick={() => addToCart(product)}></button>
... 나머지 코드
</div>
)
}
setCart 가 외부환경에 변화를 주기 때문에 addToCart는 액션함수입니다.
그런데 순수함수로 추출 할 수 있는 부분이 있는것 같습니다.
// 액션 함수 덩어리를 (계산 -> 액션) 으로 나누었습니다
const addToCart = (product: Product) => {
setCart((prev) => {
return getAddedToCart(prev, product);
});
};
// getProductIndexInCart는 계산함수여서 테스트 가능합니다🎉
const getProductIndexInCart = (cart: CartItem[], productId: string) => {
return cart.findIndex((item) => item.product.id === productId);
}
// getAddedToCart는 계산함수여서 테스트 가능합니다🎉
const getAddedToCart = (cart: CartItem[], product: Product) => {
const targetIndex = getProductIndexInCart(cart, product.id);
if (targetIndex === -1) {
return [...cart, { product, quantity: 1 }];
}
return cart.map((item, index) =>
index === targetIndex ? { ...item, quantity: item.quantity + 1 } : item
);
};
액션이 작아진건 좋은데 코드양이 더 늘어나서 보기가 좋지 않습니다.
커스텀 훅으로 관심사를 분리하면 좋을것 같습니다.
export const CartPage = () => {
const {cart, addToCart} = useCart();
return (
<div>
<button onClick={() => addToCart(product)}></button>
... 나머지 코드
</div>
)
}
컴포넌트가 클린해 졌네요.
계층형 설계 (aka. 추상화 레이어 )
함수형 프로그래밍에는 계층형 설계라는 내용이 있습니다.
코드를 추상화 계층으로 구성해서 각 계층을 볼 때 다른 계층의 구체적인 내용은 몰라도 됩니다.
위의 커스텀 훅은 계층형 설계입니다.
useCart 를 모듈화 시키고 cart, addToCart만 인터페이스로 제공했습니다.
컴포넌트 계층에선 addToCart 가 어떻게 동작하는지 몰라도 됩니다. 그냥 사용해서 카트에 물건을 담기만 하면 됩니다.
다만, 인터페이스만 제공되니 함수 이름을 잘 지어야합니다.
이렇게 이번 5주차에는 디자인 패턴과 함수형 프로그로밍에 대해서 알아봤습니다.
테오 코치가 추천해준 <쏙쏙 들어오는 함수형 코딩> 을 읽었더니 이번 회고는 뭔가 학습지처럼 진행이 되어버렸네요🤔
함수형 프로그래밍에 대한 내용만 다루었지만 자바스크립트는 많은 부분 객체지향 언어의 특징을 가집니다.
리액트도 얼마전까지 객체지향인 클래스 컴포넌트를 사용했으니 지금 사용하지 않더라도
알아두는게 좋지 않을까 생각됩니다 (뭐.. 클래스 컴포넌트를 유지보수 할 일이 있을수도 있잖아요..?)
여러가지를 배웠지만 핵심은 "액션을 작게 만들자" 인거 같습니다.
사실 <쏙쏙>만 읽어서는 리액트에 적용하기 쉽지 않았을거 같은데 항해 커리큘럼의 도움으로 리액트에 적용하는 부분까지
큰 어려움 없이 해낸것 같습니다.
그럼 과제하면서 추가로 알게된 내용을 끝으로 회고 마칩니다🌠
setState 사용법
setState 사용에는 함수형 업데이트 방식이 있습니다.
위의 내용 중
const addToCart = (product: Product) => {
setCart((prev) => getAddedToCart(prev, product));
};
가 그렇습니다.
아래 코드처럼 작성했다고 가정해보겠습니다.
이 코드도 동일한 기능을 합니다.
const [cart, setCart] = useState([]);
const addToCart = (product: Product) => {
const newCart = getAddedToCart(cart, product);
setCart(newCart);
};
다만, 이 코드는 버그가 생길 여지가 있습니다.
React의 상태 업데이트가 비동기적으로 처리되어 빠르게 여러번 업데이트가 되거나
다른 이벤트 처리 중에 상태 업데이트가 일어나면, 클로저에 갇힌 cart 변수는 최신 상태를 반영하지 못할 수 있습니다.
이때 함수형 업데이트 setCart((prev) => ...) 를 사용하면 React는 항상 최신 상태값을 함수에 전달하므로 이런 문제를 방지할 수 있습니다.
실제로 수동 테스트로는 문제가 없었지만 테스트 코드를 통해 addToCart 를 테스트 해보니
cart가 최신 상태를 반영하지 못하는 버그가 있었습니다.
'항해➕플러스' 카테고리의 다른 글
항해 4주차 클린코드와 리팩토링 (2) | 2025.04.18 |
---|---|
항해 3주차 _React, Beyond the Basics (0) | 2025.04.14 |
프레임워크 없이 SPA 만들기 (항해 2주차 회고) (0) | 2025.04.07 |
프레임워크 없이 SPA 만들기 (항해 1주차 회고) (0) | 2025.04.04 |