반응성(Reactivity)

Sidebar 6.5

번역 완료율

이 장에서는:

  • 미티어의 반응형 코드 의존 시스템에 대해 배운다.
  • 그 동기와 코드를 선언적으로 구현하는 방법을 이해한다.
  • 반응형 데이터를 사용하는 고급 코드 작성법을 배운다.
  • 컬렉션이 미티어의 핵심 기능이라면, 반응성(reactivity)은 그 핵심 기능을 쓸모있게 하는 껍질이다.

    컬렉션은 애플리케이션이 데이터의 변경을 처리하는 방법을 확 바꾼다. 데이터의 변경 여부를 직접 체크(예를 들면, AJAX 호출을 통해)하여 그 변경 사항을 HTML에 반영하기 보다는, 데이터의 변경이 어느 시점에든 발생하면 미티어가 사용자 인터페이스에 부드럽게 반영한다.

    잠깐 이에 대하여 생각해보자: 미티어는 컬렉션이 변경될 때, 연관된 사용자 인터페이스의 어느 부분이든지 은연중에 바꿀 수 있다.

    이렇게 하기 위한 필수적인 방법은 .observe()를 사용하는 것인데, 이것은 커서 함수로 해당 커서에 일치하는 도큐먼트가 변경될 때 콜백들을 호출한다. 그러면, 이 콜백들을 통해서 DOM (웹페이지의 렌더링된 HTML)을 수정할 수 있다. 그 결과 코드는 아래와 같은 형태가 될 것이다:

    Posts.find().observe({
      added: function(post) {
        // when 'added' callback fires, add HTML element
        $('ul').append('<li id="' + post._id + '">' + post.title + '</li>');
      },
      changed: function(post) {
        // when 'changed' callback fires, modify HTML element's text
        $('ul li#' + post._id).text(post.title);
      },
      removed: function(post) {
        // when 'removed' callback fires, remove HTML element
        $('ul li#' + post._id).remove();
      }
    });
    

    이런 코드가 얼마나 빨리 복잡해지는 지는 아마도 이미 볼 것이다. Post의 각 속성의 변경을 처리하는 경우와 post의 <li> 내부에 있는 복잡한 HTML을 변경해야 하는 경우를 상상해보라. 우리가 실시간으로 변하는 정보의 다중 소스에 의존하기 시작할 때 발생하는 복잡한 경우는 말할 것도 없다.

    언제 observe()사용해야 할까?

    때로는 위의 패턴을 사용할 필요가 있는데, 특히 써드파티 위젯을 처리할 때이다. 예를 들어, 지도에 있는 컬렉션 데이터(이를테면, 현재 로그인한 사용자들의 위치를 보여주는)에 기반한 핀을 실시간으로 추가하거나 삭제하고자 하는 경우를 상상해보자.

    이런 경우에, 지도와 미티어 컬렉션과의 “통신"을 구현하고 데이터 변경에 반응하는 방법을 알기 위해서는 observe() 콜백이 필요할 것이다. 예를 들면, 지도 API의 dropPin()이나 removePin() 메서드를 호출하기 위해서 addedremoved 콜백에 의존하게 될 것이다.

    선언적 접근법

    미티어는 반응성의 구현에 있어 더 나은 방법을 제공한다: 그 핵심은 선언적 접근법이다. 선언적이란 객체들 사이의 관계를 한 번 정의하면 그들이 동기화를 유지하는 것이다. 모든 가능한 변경에 대하여 일일이 명시하지 않아도 된다.

    이것은 강력한 개념인데, 그 이유는 실서비스되는 시스템에서는 많은 입력이 예측할 수 없는 시점에 변경되기 때문이다. 어떤 반응형 데이터 소스이든 HTML을 렌더링하는 방법을 선언적으로 지정함으로써, 미티어는 그 소스들을 모니터하면서 사용자 인터페이스를 최신의 상태로 유지하는 복잡한 작업을 수행한다.

    말하자면, observe 콜백을 생각하는 대신에, 미티어는 다음과 같이 작성하기를 권한다:

    <template name="postsList">
      <ul>
        {{#each posts}}
          <li>{{title}}</li>
        {{/each}}
      </ul>
    </template>
    

    그리고 post 목록을 아래와 같이 구한다:

    Template.postsList.helpers({
      posts: function() {
        return Posts.find();
      }
    });
    

    보이지 않는 내부에서, 미티어는 observe() 콜백을 호출하여 반응형 데이터가 변경될 때, 적절한 HMTL 영역을 다시 그린다.

    미티어에서의 의존성 추적: 컴퓨테이션(Computation)

    미티어가 실시간, 반응형 프레임워크이지만, 미티어 앱 내부의 모든 코드가 반응형인 것은 아니다. 만약 그렇다면, 무엇이든 변경될 때마다 앱 전체가 재구동될 것이다. 그게 아니라, 반응성은 코드의 특정한 영역에 한정되는데, 이 영역을 컴퓨테이션(computation)이라 부른다.

    다시말하면, 컴퓨테이션은 연계된 반응형 데이터 소스 가운데 하나라도 변경되면 실행되는 코드 블록이다. 만약 반응형 데이터 소스(예를 들면, 세션 변수)가 있고 이 변경에 반응형으로 응답하게 하려면, 이 데이터 소스에 대한 컴퓨테이션을 설정해야 한다.

    보통 이런 작업을 명시적으로 구현해야 할 필요는 없는데, 이것은 미티어가 각 템플릿마다 특정한 컴퓨테이션(템플릿 헬퍼와 콜백의 코드는 기본적으로 반응형이라는 것을 의미한다)을 이미 부여한 상태이기 때문이다.

    모든 반응형 데이터 소스는 이를 이용하는 모든 컴퓨테이션을 추적하여 이들에게 그 값이 언제 변경되는 지를 알려준다. 이를 위해, 반응형 데이터 소스는 컴퓨테이션에 invalidate() 함수를 호출한다.

    컴퓨테이션은 일반적으로 무효화(invalidation)가 발생했을 때 그 콘텐츠를 재계산하도록 설정되어 있다. 그리고 이것이 템플릿 컴퓨테이션(비록 템플릿 컴퓨테이션이 페이지를 보다 효율적으로 다시 그리는 마술을 부리기도 하지만)에서 일어난다. 무효화가 일어나면 컴퓨테이션이 할 일에 대하여 제어를 더 잘할 수 있겠지만, 실제로 이것은 거의 항상 독자가 이용하고 있을 것이다.

    컴퓨테이션 설정

    컴퓨테이션의 배경 이론을 이해하면, 실제로 이를 설정하는 것은 매우 쉽다. 컴퓨테이션의 코드 블럭을 감싸고 이를 반응형으로 만들기 위해서는 단순히 Tracker.autorun 함수를 사용하면 된다:

    Tracker.autorun(function() {
      console.log('There are ' + Posts.find().count() + ' posts');
    });
    

    Tracker 블록을 Meteor.startup() 블록 내부에 두어야 한다는 점에 유의하기 바란다. 이것은 미티어가 Posts 컬렉션 로딩을 끝낸 후에 한 번만 실행하도록 하기 위함이다.

    보이지 않는 내부에서, autorun은 컴퓨테이션을 만들고, 연계된 데이터소스가 변경될 때마다 재계산한다. 우리는 콘솔에 post의 숫자를 로그로 출력하는 간단한 컴퓨테이션을 설정했다. 이것은 Posts.find()가 반응형 데이터 소스이므로 post 갯수가 변경될 때마다 재계산하도록 컴퓨테이션에게 지시할 것이다.

    > Posts.insert({title: 'New Post'});
    There are 4 posts.
    

    이 전체의 실질적 결론은 우리가 아주 자연스럽게 반응형 데이터를 사용하는 코드를 작성할 수 있다는 것이다. 은연중에 그 연관 시스템이 적절한 시간에 재실행시키는 것도 알면서 말이다.