Observer pattern의 개념에 대해 간단하게 정리한 지난 게시글을 작성하며 '반응형'이라는 키워드에 대해 알아보고 싶었다.
이번 게시글에서는 반응형 프로그래밍이 뭔지 정리하고 다음 게시글에서는 프론트엔드 개발에 있어서 반응형 프로그래밍의 의미와 영향, 그리고 내가 주로 사용하는 리액트와 반응형 프로그래밍의 관계에 대해 정리할 계획이다.
프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해 주고, 결정하는 역할을 한다. 예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 반면에, 함수형 프로그래밍은 상태값을 지니지 않는 함수값들의 연속으로 생각할 수 있게 해준다. 위키백과
반응형 프로그래밍에 대해 알아보기 전에 프로그래밍 패러다임에 대해 찾아봤다. 명령형, 선언형, 객체 지향형 등 여러 패러다임이 있다는 것을 알고 있고, 각각의 패러다임에 어울리는 언어가 있다는 건 알고 있지만 교과서 지식처럼만 외워왔던 것 같다.
예를 들어, 객체 지향형의 특징이 뭔지, 메서드를 어떻게 분리하고 구성하는지와 같은 것들은 책을 통해서 학습하기도 하고 여러 코드도 봐서 익숙했다. 하지만 '프로그램을 설계하고 구현할 때 객체 지향형 프로그래밍 방식을 택하는게 어떤 의미를 갖는지'에 대해서는 명확하게 답변하지 못했다.
아무튼, 이번에 다시 찾아보면서 스스로 정리한 바는 프로그래밍 패러다임이란 프로그램을 바라보는 하나의 '가치관'이라는 것이다. 딱 위의 문구를 보고 이해가 팍 된 것 같았는데, 사실 정말 많은 자료나 책에서 이미 봐왔던 문구라서 조금 머쓱하긴하다. 별것 아닌 개념일 수도 있지만 조금씩이나마 내공이 쌓여서 이제야 제대로 이해된 것 같기도 하고?
In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. (wikipedia)
반응형 프로그래밍 역시 프로그램을 바라보는 가치관 중 하나이다.
그럼 이 가치관은 어떠한 요소들을 중요하게 다루고 있을까? 그 요소들은 다음과 같다.
자, 프로그래밍 패러다임이 어떤 말인지 이제야 이해가 되었으니 이번엔 '선언적' 프로그래밍 패러다임의 차례이다.
사실 이것도 명령형 프로그래밍 패러다임과 대비되는 것으로 Javascript의 forEach
같은 Array 메서드들이 그 예시이고 등등... 알고 있는 지식은 있었지만 이해가 제대로 되지 않던 개념인데 이번에 어느정도 이해가 된 것 같아 정리해본다.
그동안 선언적 혹은 선언형 프로그래밍이 잘 이해가지 않았던 이유는 용어가 너무 모호하기 때문에다, '선언형'이라는 용어와 내가 알고 있는 프로그래밍의 개념이 잘 매칭되지 않았고 그 반대의 유형으로 일컬어지는 '명령형'까지 생각해보면 더 모호했다.
결국 이해한 바는 어떠한 프로그램을 구현할 때, '자신의 책임(기능)을 수행하는 방법'을 코드로 작성하면 명령형 프로그래밍이라고 할 수 있고 '책임을 수행했을 때의 모습'을 코드로 작성한다면 선언형 프로그래밍이라고 할 수 있다.
아 뭔가 내가 참고한 자료들의 용어를 그대로 쓰지 않고 나만의 언어로 바꿔서 작성하려니까 다시 모호해진다... 아직 내공이 부족하긴 한가보다.
이럴 때 유용하게 사용할 수 있는 것이 바로 예시니까, 내가 주로 사용하는 리액트의 예시를 생각해봤다.
const App = () => {
const [num, setNum] = useState(2);
return (
<div>{num}</div>
);
}
화면에 변경이 가능한 숫자를 띄우고 싶다고 하자. 그럼 책임을 'UI를 그리는 것'이라고 할 수 있다.
선언형 프로그래밍을 지향하는 리액트에서는 위와 같은 컴포넌트를 작성하면 된다. 이렇게 컴포넌트를 작성해두면 num
의 값이 바뀔 때마다 화면의 숫자도 바뀌게 된다.
우리의 관심은 숫자를 어떻게 바꾸는 지가 아니라 '화면에 해당 숫자가 떠있는 것(UI를 그리는 것)'에 있으므로, 숫자가 바뀐 값이 화면에 반영되는 과정과 방법이 아닌 화면에 그려지는 UI의 모습을 기술하는 리액트의 JSX 문법은 선언형 프로그래밍에 해당한다고 할 수 있다.
한편, 바뀐 숫자의 값을 다음과 같은 방법으로 변경할 수도 있다.
// 1. 바뀐 숫자를 담을 tag Element를 가져오고
const divElem = document.getElementById('numDiv');
// 2. 새로운 숫자를 해당 element에 넣어준다.
divElem.innerText = newNumber;
위 방법에서는 바뀐 숫자를 화면에 반영해주는 과정을 코드로 구현하고 있다. 이렇게 자신의 책임을 수행하는 방법을 코드로 작성하는 방식이 명령형 프로그래밍에 해당한다고 볼 수 있다.
반응형 프로그래밍으로 돌아와서, 이제 데이터 흐름과 변화의 전파에 대해 알아보자.
const App = () => {
const [num, setNum] = useState(2);
const [num2, setNum2] = useState(1);
useEffect(() => {
setNum2(prevState => prevState + num);
}, [num]);
return (
<div>{num} {num2}</div>
);
}
위 리액트 컴포넌트를 보자, useEffect
훅에서는 num
의 값이 바뀔 때마다 num2
의 값도 같이 바꿔주고 있다.
즉, useEffect
를 통해서 (1) num
값의 변화가 (2) num2
에 전파되는 데이터의 흐름이 만들어진 것이다.
이와 같은 과정을 조금 더 확장해 '프로그래밍을 이런 관점에서 수행하는 것'이 반응형 프로그래밍이라고 할 수 있겠다.
여기서 Observer 패턴을 간단하게 구현한 지난 게시글과 연결점을 생각해보자.
const orderManager = new OrderManager();
orderManager.subscribe(new SteakSession());
orderManager.subscribe(new PastaSession());
orderManager.takeOrder({
pasta: '봉골레',
steak: '티본',
});
위 코드에서는
이제 Observer 패턴이 반응형 프로그래밍 방식의 구현체라는 것을 확인할 수 있다.
반응형 프로그래밍에 대해 잘 몰랐더라도 프론트엔드 개발자라면 반응형 프로그래밍에 어느정도는 익숙할텐데, 바로 DOM에서 사용하는 이벤트리스너가 반응형으로 동작하기 때문이다.
ex. document.addEventListener('click', () => {...});
1. 특정 dom 요소의 클릭 이벤트(데이터) 발생
2. 클릭 이벤트의 생성(데이터의 변화)가 전파
3. 해당 이벤트에 연결된 핸들러의 실행