React 시대의 함수형 프로그래밍
바닐라 JS처럼 개발자가 DOM과 상태 변경을 직접 제어하던 시기에는
불변성·순수 함수 같은 함수형 프로그래밍 철학이 지금처럼 중요하지는 않았습니다.
하지만 React(Vue/Redux/SWR/Recoil 등)처럼 선언적·상태 기반 UI가 등장하면서
상태를 “데이터 흐름의 스냅샷”으로 다뤄야 했고, 그때부터 함수형 프로그래밍이 중요해지기 시작했습니다.
🔥 바닐라 JS에서는 왜 FP가 별로 중요하지 않았을까?
바닐라 JS 시대의 UI 개발은 명령형 방식이었습니다.
user.age++;
document.querySelector("#age").textContent = user.age;
개발자가 DOM 업데이트 시점도 직접 제어
상태도 직접 제어
DOM도 직접 제어
렌더링 순서도 직접 관리
언제 UI가 업데이트될지 모든 제어권이 개발자에게 있음
그래서 이때는 상태를 직접 변경 해도 UI가 깨지지 않음
“UI가 언제, 어떻게 바뀌는지 전부 개발자가 직접 관리하던 시대였기 때문에
불변성이나 순수함수 같은 안전장치가 필요 없었다.”
🔥 React 등장 → UI 렌더링 방식 자체가 완전히 바뀜
React의 핵심 철학:
UI = 상태의 함수
그리고 이 계산 과정(렌더링)을 React가 자동으로 처리한다.
개발자는 단지 “상태를 바꾸기만” 하고, UI 업데이트의 타이밍, 횟수, 순서는 React가 자동 관리한다.
React는 내부적으로:
렌더링을 “나중에” 할 수도 있고
여러 개의 setState를 묶어서(batch) 처리할 수도 있고
렌더링을 중단했다가 다시 할 수도 있고(Fiber)
데이터 업데이트를 여러 번 합쳐서 처리할 수도 있다
React 18에서는 concurrent rendering까지 수행
즉, React는 화면을 “내가 모르는 시점”에 다시 그린다.
그래서 불변성과 순수 함수 같은 “안전 규칙”이 필요해진 것입니다.
🔷 불변성이 왜 필요해? — “원본을 바꾸면 React가 헷갈려서”
React는 화면 갱신 여부를 결정할 때, 계산 비용이 높은 깊은 비교 대신 얕은 비교를 수행합니다. React는 state가 참조(주소) 기반으로 변경되었는지 감지하기 때문에, 원본을 직접 변경하면 변경을 감지하지 못할 수 있습니다.
그래서 React는 요구합니다
“원본은 건들지 마! 복사본으로 ‘새 상태’를 만들어서 알려줘!”
그래야 React가 이전 상태와 새로운 상태를 정확하게 비교할 수 있습니다.
🔷 순수 함수는 왜 필요해? — “같은 상태면 같은 화면이 나와야 해서”
UI = 함수(state)
여기서 이 함수(컴포넌트)는:
같은 입력(state)
같은 출력(UI)
을 보장해야 합니다.
하지만 순수하지 않은 함수(랜덤, 외부 변수 수정, 전역 접근 등)는 같은 state로 호출해도 다른 UI가 나오게됩니다.
그러면 React는:
렌더링 도중 UI가 바뀌고
비교 결과가 꼬이고
화면이 이상하게 업데이트되고
디버깅이 어려워진다
그래서 React는 “컴포넌트는 순수하게 동작해야 한다”는 규칙을 가지고 있습니다.
함수형 프로그래밍(FP)의 핵심인: 불변성, 순수 함수, 데이터 변환 중심 사고는 React 생태계와 정확히 맞물립니다.
불변성 → “이전 상태 vs 새로운 상태 비교”를 정확하게 만들기 위한 것
순수 함수 → “state → UI 계산이 항상 똑같아야” 안정적인 렌더링이 가능하기 때문
그래서 React 시대에 들어와서 FP 방식이 중요해지게되었습니다.
🔥 쉽게 말하면
바닐라 JS:
“DOM과 상태 변경을 내가 직접 다 하니까
mutable(직접 수정) 해도 괜찮았음.”
React:
“렌더링 시점은 React가 마음대로 조절한다.
mutable이면 UI가 바로 꼬인다.
→ immutable + pure function 필수”
🧠 함수형 프로그래밍(FP) vs 객체지향(OOP)
함수형 프로그래밍과 클래스(객체 지향)가 추구하는 방식이 근본적으로 다릅니다.
✔ 함수형(FP)
데이터는 “흐름(Flow)”
원본은 바꾸지 않고 새 값을 만든다 (불변성)
동일 입력 → 동일 출력 (순수 함수)
작은 로직들을 조립해 사용 (Composition)
✔ 객체지향(OOP)
데이터 + 행동을 “하나의 객체”로 묶음
객체 내부 상태를 직접 바꾼다
this, new, extends를 중심으로 구조를 짠다
상속 기반 재사용(크고 고정된 구조에 강함)
함수형 프로그래밍 예시 코드: increaseAge 함수
// 1. 순수한 데이터 객체
const user = { name: 'Alice', age: 30 };
// 2. 순수 함수: 원본 객체(user)를 수정하지 않고, 새로운 객체를 반환합니다.
function increaseAge(person) {
// 스프레드 문법(...)을 사용하여 복사본을 만들고 age만 변경
return {
...person,
age: person.age + 1
};
}
const newUser = increaseAge(user);
console.log('원본:', user); // { name: 'Alice', age: 30 }
console.log('결과:', newUser); // { name: 'Alice', age: 31 }
// 💡 불변성 확인: 원본은 그대로 유지됩니다.
console.log(user === newUser); // false (서로 다른 객체)
장점
| 장점 | 설명 |
| 예측 가능성 | this가 없고 외부 상태를 건드리지 않으므로, 함수 호출 시 항상 같은 결과를 기대할 수 있습니다. |
| 쉬운 테스트 | 단순히 입력과 출력을 비교하면 되므로 테스트가 간편합니다. |
this 회피 | this의 복잡성과 실수(바인딩 문제)를 원천적으로 차단합니다. |
클래스 예시 코드: Person 클래스
class Person {
constructor(name, age) {
this.name = name;
this.age = age; // ❗️ 인스턴스의 내부 상태
}
// 메서드: 자신의 내부 상태를 직접 변경합니다.
increaseAge() {
this.age += 1; // ❗️ this.age를 직접 수정 (가변적)
}
getGreeting() {
return `Hello, I'm ${this.name} and I'm ${this.age}.`;
}
}
const person = new Person('Bob', 25);
person.increaseAge(); // 메서드를 호출하여 내부 상태를 변경
console.log('상태:', person); // { name: 'Bob', age: 26 }
// 💡 가변성 확인: 인스턴스 자체가 변경됩니다.
장점
| 장점 | 설명 |
| 캡슐화 | 데이터와 기능을 한 곳에 묶어 관리하여 구조가 명확해집니다. |
| 성능 효율 | 메서드가 프로토타입에 공유되어 메모리 사용량이 절약됩니다 (대량 인스턴스 생성 시 유리). |
| 상속 | extends를 통해 코드 재사용 및 계층적인 모델을 구축하기 편리합니다. |
🔥 프론트에서 함수형(FP)이 더 맞는 이유
프론트는:
상태 변화가 빈번하고
UI가 계속 다시 그려지고
React가 렌더링 시점을 조절하며
이벤트, 비동기, 동시 업데이트가 겹침
이 구조에서는 원본을 직접 바꾸는 OOP 방식이 위험합니다.
반면
불변성
순수 함수
작은 로직의 조합
이런 함수형 철학은 UI = f(state) 구조에 완벽히 들어맞습니다.
🔥 그럼 클래스(OOP)는 프론트에서 언제 쓰면 좋은가?
정확히 이 두 경우입니다:
- 상태와 행동이 하나의 '실체'처럼 묶여야 할 때
(도메인 모델, 비디오 플레이어, 게임 캐릭터 등)
class Player {
constructor(name) {
this.name = name;
this.hp = 100;
this.x = 0;
}
move(dx) {
this.x += dx;
}
hit(damage) {
this.hp -= damage;
}
}
const p = new Player("Alice");
p.move(5);
p.hit(20);
2) 동일한 객체를 대량 생성해야 하고 성능이 중요할 때
(WebGL 객체, Particle 시스템 등)
class Particle {
constructor(x, y, speed) {
this.x = x;
this.y = y;
this.speed = speed;
}
update() {
this.x += this.speed;
}
}
const particles = [];
for (let i = 0; i < 1000; i++) {
particles.push(new Particle(0, 0, Math.random()));
}
// 매 프레임 업데이트
function tick() {
particles.forEach(p => p.update());
requestAnimationFrame(tick);
}
tick();
클래스 메서드는 인스턴스마다 복사되지 않고, prototype이라는 공유 저장소에 만들어져 모든 인스턴스가 그 하나를 함께 사용하기 때문에 메모리 효율을 높이는 장점이 있습니다.
1000개, 5000개 인스턴스를 만들어도 메모리 낭비 없음
update() 메서드는 프로토타입에서 공유됨
매우 빠르고 자연스럽다
이걸 함수형으로 만들면?
객체 복사 비용 증가
매번 새로운 상태 객체 생성
메모리와 연산 비용 증가
→ 애니메이션 성능이 떨어짐
이런 건 진짜 OOP가 자연스럽고 성능도 좋습니다.
⭐ 최종 요약
React처럼 선언적 UI를 사용하면 불변성과 순수 함수가 필수입니다.
왜냐면 React가 상태를 “이전과 이후”로 비교하며 UI를 다시 그리고, UI는 동일 상태에서 항상 같은 결과가 나와야 하기 때문입니다.
반면 객체지향은 상태를 내부에서 직접 바꾸는 방식이라 UI 렌더링 구조와 충돌합니다.
클래스는 “실체가 있는 객체”를 모델링하거나 대량 인스턴스가 필요할 때에 유리합니다.
대부분 FP 위주이고, 소수의 경우를 제외하고는 클래스를 사용할 필요가 거의 업다고 생각합니다.