Skip to main content

Command Palette

Search for a command to run...

React 시대의 함수형 프로그래밍

Published
5 min readView as Markdown
D

프론트엔드 개발자 doit입니다.

바닐라 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)는 프론트에서 언제 쓰면 좋은가?

정확히 이 두 경우입니다:

  1. 상태와 행동이 하나의 '실체'처럼 묶여야 할 때

(도메인 모델, 비디오 플레이어, 게임 캐릭터 등)

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 위주이고, 소수의 경우를 제외하고는 클래스를 사용할 필요가 거의 업다고 생각합니다.

35 views

More from this blog

Agent Skill로 README 자동화하기

저는 요즘 업무에서 AI 에이전트를 적극적으로 활용하여 반복적인 업무를 최대한 AI로 자동화하기 위해 공부 중인데요. 이번에 했던 작업 중 하나가 README 파일 생성 자동화입니다. AI에게 README를 써달라고 하면 꽤 그럴싸한 결과물이 나옵니다. 하지만 여러 프로젝트에 반복하다 보면 프롬프트가 모호할 때마다 매번 다른 방식으로 문제를 풀기 때문에 들

Mar 31, 20268 min read8

배열을 처리하는 세 가지 방법 (chaining, reduce, for loop)

개발을 하다 보면 배열 데이터를 가공할 일이 정말 많은데요.저는 평소에는 filter, map 같은 메서드 체이닝 방식을 주로 사용합니다. 읽기 쉽고, 데이터 흐름이 단계별로 명확하게 보이기 때문이죠. 하지만 어떠한 계기로(?) 인해 이런 의문을 가지게 되었습니다.. "만약 데이터가 수천만 건으로 늘어난다면? 배열을 여러 번 순회하는 체이닝 방식이 성능 병목을 일으키지는 않을까?" 혹은 "reduce 하나로 합치는 게 베스트일까?" 이 질문을 ...

Feb 4, 20263 min read19

[React] 좋은 추상화란 무엇인가(2) 잘못된 추상화는 중복보다 비싸다

지난 글을 보면 성급했던 재사용 컴포넌트 경험으로 고생을 한 적이 있었는데요.(아직 안 보셨다면? [1편: 좋은 추상화란 무엇인가(1)?] ) 애초에 왜 이런 고생을 했을까를 생각해 보면이유는 단순했습니다. 중복 코드가 거슬리니 바로 추상화를 해버린 것이었죠..똑같은 실수를 반복하지 않기 위해 저만의 기준이 필요하다고 느꼈습니다. 이에 대해 이미 깊이 고민하셨던 Cher Scarlett님과 Kent C. Dodds님의 글을 보면서, 제가 취해야...

Dec 16, 20253 min read25

[React] 좋은 추상화란 무엇인가(1) ? 유연한 컴포넌트 설계를 위한 제어의 역전

재사용을 위해 만든 컴포넌트가, 시간이 지날수록 오히려 재사용하기 어려워진 경험이 있으신가요? 저는 최근 프로젝트에서 SearchFilter 컴포넌트가 그러한데요 프로젝트에는 여러 목록 페이지가 존재했고, 페이지마다 검색 조건이 제각각이었습니다. 그래서 처음에는 Configuration 배열만 넘기면 알아서 그려주는 컴포넌트를 만들었습니다. // ❌ 초기의 설정 기반 접근 (Configuration) const filterFields = [ ...

Dec 9, 202510 min read36
D

Dlog!

39 posts

배우고, 나누며, 함께 성장하는 개발자 🚀