라는 문항이 공고 우대사항 항목에 꽤나 많았기에 이 주제를 선정
왜 JavaScript 진영은 함수형 프로그래밍(FP)을 선택했습니까?
2010년대 초반까지 JavaScript는 주로 jQuery와 명령형 코드로 작성되었습니다.
// 명령형 - "어떻게" 할지를 서술const result = [];for (let i = 0; i < users.length; i++) {if (users[i].age >= 20) {result.push(users[i].name.toUpperCase());}}
문제는 상태가 파편화되고, 사이드 이펙트(부수 효과) 추적이 불가능하다는 점이었습니다. 애플리케이션의 규모가 커질수록 "이 값이 왜 변경되었지?"를 추적하는 데 대부분의 시간을 소비했습니다.
그래서 커뮤니티는 Haskell, ML과 같은 순수 함수형 언어의 개념을 도입하기 시작했으며, React 또한 그러한 흐름 위에서 탄생한 것입니다.
핵심 개념 4가지
① 순수 함수 — 동작 원리가 중요
순수 함수의 정의는 다음 두 가지입니다.
// 불순한 함수 — 외부 변수에 의존let tax = 0.1;function getPrice(price) {return price + price * tax; // tax가 언제 변경될지 모름}// 순수 함수function getPrice(price,
왜? 순수 함수는 테스트가 용이하며, 메모이제이션(Memoization)이 가능합니다. 동일한 입력에 대해 항상 동일한 출력이 보장되므로, 결과를 캐싱하더라도 안전합니다. React.memo, useMemo, useCallback 모두 이 원리를 바탕으로 동작합니다.
② 불변성 — React 렌더링의 동작 원리
이것이 가장 중요한 개념입니다. React가 리렌더링을 결정하는 방식을 먼저 이해해야 합니다.
// React 내부 pseudocode (단순화)function shouldUpdate(prevState, nextState) {return prevState !== nextState; // 참조 비교 (Object.is)}
React는 깊은 비교(Deep copy)를 수행하지 않습니다. 그 이유는 성능 때문입니다. 객체 내부를 매번 재귀적으로 비교하면 오히려 처리 속도가 저하됩니다.
JavaScript
const [todos, setTodos] = useState([{ id: 1, text: "운동" }]);// ❌ mutation - 동일한 배열 참조todos.push({ id: 2
Redux, Zustand, Jotai 모두 동일한 원리로 동작합니다.
③ 고차 함수 — 함수도 값이다
JavaScript에서 함수는 일급 객체(First-class citizen)입니다. 변수에 할당할 수 있고, 인자로 전달할 수 있으며, 반환값으로 사용할 수 있습니다.
// 함수를 인자로 전달[1,2,3].map(n => n * 2)// 함수를 반환 — 클로저와 결합const multiply = (x) => (y) => x
클로저 동작 원리: multiply(2)가 실행되면 x=2가 스코프에 캡처됩니다. 반환된 함수는 실행이 종료된 후에도 해당 x에 접근할 수 있습니다. React 훅(Hook)은 전부 이 원리로 동작합니다.
// useCallback 내부의 대략적인 동작 방식function useCallback(fn, deps) {// fn은 컴포넌트 스코프의 값들을 클로저로 캡처하고 있음return memoize(fn, deps);}
④ 함수 합성 — 복잡성을 다루는 방법
작은 함수들을 조합하여 복잡한 로직을 구현하는 것입니다. 유닉스의 파이프(|)와 동일한 개념입니다.
const trim = str => str.trim();const toLowerCase = str => str.toLowerCase();const replaceSpaces = str => str.replace
실무에서 자주 사용되는 패턴입니다.
const processUsers = users =>users.filter(u => u.isActive).map(u => ({ ...u, displayName: `${u.
각 단계가 순수 함수로 구성되어 있어, 테스트를 단계별로 분리할 수 있습니다. filter만 개별적으로 테스트하거나, map만 개별적으로 테스트할 수 있습니다.
배경
객체지향 프로그래밍(OOP)은 '현실 세계를 객체로 모델링하자'는 아이디어에서 출발했습니다. 데이터와 해당 데이터를 다루는 행위를 하나로 묶음으로써, 복잡한 시스템을 사람이 이해하기 쉬운 단위로 분할할 수 있습니다.
핵심 개념 4가지
① 캡슐화 — 정보 은닉의 실제 목적
단순히 private으로 데이터를 숨기는 것이 목적이 아닙니다. 변경의 영향 범위를 제한하는 것이 핵심입니다.
class BankAccount {#balance = 0;deposit(amount) {if (amount <= 0) throw new Error("양수만 가능합니다.");this.#balance
#balance를 직접 변경할 수 없으므로, 잔액이 변경되는 경로는 반드시 deposit 또는 withdraw 메서드를 거쳐야 합니다. 이는 버그 발생 범위를 효과적으로 축소시킵니다.
② 상속 vs 합성 — 실무에서 상속은 위험하다
상속 그 자체보다, 상속을 언제 지양해야 하는지를 아는 것이 더욱 중요합니다.
class Animal { speak() { return "..."; } }class Dog extends Animal { speak() { return "멍!"; } }
이 정도의 구조는 괜찮습니다. 문제는 상속 체인이 깊어질 때 발생합니다.
Vehicle → MotorVehicle → Car → ElectricCar → TeslaModel3
중간 클래스를 하나 수정하면 하위 클래스 전체가 영향을 받습니다. 따라서 최근 실무에서는 '상속보다는 합성(Composition over Inheritance)' 원칙을 지향합니다.
// 상속 대신 합성class Engine { start() {} }class GPS { getLocation() {} }class Car {constructor()
③ 다형성 — 인터페이스의 힘
동일한 메서드 이름으로 다른 동작을 수행할 수 있습니다.
class Shape { area() { return 0; } }class Circle extends Shape {constructor(r) { super(); this.r
실무에서 이 개념이 중요한 이유: API 레이어를 설계할 때, 내부 구현체가 변경되더라도 이를 호출하는 측의 코드는 변경되지 않도록 설계할 수 있습니다. (예: REST에서 GraphQL로 마이그레이션하는 상황)
④ 추상화 — 복잡성을 계층으로 나누기
JavaScript
class ApiClient {#token;async get(url) {const res = await fetch(url, {headers: { Authorization: `Bearer ${
'이 레이어 아래의 세부 구현은 몰라도 된다'는 계층을 형성하는 것입니다. 복잡성은 하위 계층으로 밀어 넣고, 상위 계층은 단순하게 유지합니다.
React의 설계 원리는 곧 함수형 프로그래밍입니다.
컴포넌트를 함수 형태로 설계한 것은 결코 우연이 아닙니다.
// React 컴포넌트 = 순수 함수// UI = f(state)function UserCard({ name, age }) {return <div><h2>{name}</h2><p>{age}세</p
동일한 props가 주어지면 항상 동일한 UI를 반환합니다. 이러한 보장이 있어야만 React가 내부적으로 렌더링을 생략하거나 병렬로 처리할 수 있습니다 (Concurrent Mode).
HOC(고차 컴포넌트)는 고차 함수 그 자체입니다.
function withAuth(WrappedComponent) { // 컴포넌트를 인자로 전달받음return function AuthenticatedComponent(props) { // 새로운 컴포넌트를 반환함const isLoggedIn = useAuth();if (!isLoggedIn) return <
커스텀 훅(Custom Hook) = FP의 합성 + OOP의 캡슐화
function useWebSocket(url) {const [messages, setMessages] = useState([]);const wsRef = useRef(null); // 내부 구현 은닉 (캡슐화)useEffect((
WebSocket 생성, 연결, 해제 로직을 완전히 은닉하고, 외부에서는 messages와 send만 알면 됩니다. 이는 OOP의 캡슐화 개념을 함수형 방식으로 구현한 훌륭한 사례입니다.
| 상황 | 적용 패러다임 | 핵심 개념 |
| UI 컴포넌트 | FP | 순수 함수, 불변성 |
| 데이터 변환 | FP | map, filter, reduce |
| API 레이어 | OOP | 추상화, 캡슐화 |
| 커스텀 훅 | FP + OOP | 합성과 캡슐화의 결합 |
"React를 잘 다룬다는 것은 결국 어느 부분에 FP를 적용하고, 어느 부분에 OOP를 적용할지 판단하는 능력을 의미합니다. 이 판단 기준이 정립되면 코드 리뷰나 아키텍처 설계 시의 관점과 깊이가 완전히 달라질 것입니다."
흠… 공고에 많이 나와있긴 하지만 정말 몰라도 되는 개념이라고 생각이 드는데…. 함수형 프로그래밍 이라는 책을 읽고 스터디도 해보았지만 상태관리 라이브러리, JS, 리액트의 설계 이유 정도를 제외하고는 딱히 느껴지는 점이 없어서 아쉽다. 실무에서 실제로 어떻게 이런걸 이해하고 적용하는지 배워보고 싶다는 생각이 듦!