Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

터보링크를 위한 단일 페이지 앱을 버리시겠습니까?

Turbolinks - 아마도 Rails 세계에서 가장 경멸받는 단어 중 하나일 것입니다.

시도해 보셨을 수도 있습니다. 새 프로젝트 또는 기존 앱에 터보링크를 포함했습니다. 그리고 얼마 지나지 않아 앱이 이상하고 놀라운 방식으로 실패하기 시작했습니다. 수정도 간단해서 다행입니다. 터보링크를 끄십시오.

...하지만 일부 회사에서는 작동합니다. 여기 Honeybadge에서 우리는 그것을 작동시켰습니다. 그리고 우리는 천재가 아닙니다.

대답은 너무 간단해서 거의 꺼내기가 꺼려집니다. 그러나 Ruby Nation 및 Madison+Ruby에서 이 주제에 대해 강연한 후 사람들은 이 주제가 도움이 된다고 생각하는 것 같습니다. 그럼 자세히 살펴보겠습니다.

터보링크를 위한 단일 페이지 앱을 버리시겠습니까?

Turbolinks와 PJAX는 본질적으로 같은 방식으로 작동합니다. 너무 비슷해서 지금부터 PJAX라고 할게요. :)

두 페이지 요청 측면에서 PJAX를 이해할 수 있습니다. 사용자가 페이지를 처음 요청하면 다른 '기존' Rails 페이지와 마찬가지로 제공됩니다. 그러나 사용자가 PJAX 사용 링크를 클릭하면 특별한 일이 발생합니다. 페이지를 완전히 다시 로드하는 대신 페이지의 일부만 업데이트됩니다. AJAX를 통해 수행됩니다.

이것은 우리에게 단일 페이지 앱의 많은 장점을 제공하는 동시에 몇 가지 어려움을 회피합니다.

  • PJAX 앱은 더 이상 모든 요청에 ​​대해 페이지를 완전히 새로고침할 필요가 없기 때문에 단일 페이지 앱만큼 빠른 것처럼 보입니다.
  • 프론트엔드 및 백엔드 개발에 동일한 스택을 사용할 수 있습니다.
  • 사용자가 JS를 비활성화하면 PJAX 앱이 기본적으로 정상적으로 성능이 저하됩니다.
  • PJAX 앱은 더 쉽게 액세스할 수 있고 SEO 친화적입니다.

PJAX의 무거운 작업을 수행할 라이브러리가 많이 있습니다. Turbolinks는 아마도 가장 잘 알려져 있을 것입니다. 설정은 Gemfile에 Turbolinks gem을 포함하기만 하면 됩니다.

gem 'turbolinks' 

...app/assets/javascripts/application.js에 JS 포함

//= require turbolinks

이제 앱을 다시 로드하면 모든 내부 링크가 터보링크가 됩니다. 클릭하면 AJAX를 통해 새 페이지가 요청되고 현재 문서에 삽입됩니다.

jquery-pjax 구현

여기 Honeybadger에서는 원래 Github에서 개발한 PJAX 라이브러리를 사용합니다. Turbolinks보다 약간 더 많은 구성이 필요하지만 훨씬 더 유연합니다.

모든 링크가 PJAX라고 가정하는 대신 이를 제어할 수 있습니다. 또한 페이지에서 PJAX 콘텐츠가 삽입될 위치를 제어할 수 있습니다.

가장 먼저 해야 할 일은 HTML에 컨테이너를 추가하는 것입니다.

<div class="container" id="pjax-container">
  Go to <a href="/page/2">next page</a>.
</div>

이제 PJAX 링크를 설정해야 합니다.

$(document).pjax('a', '#pjax-container')

마지막으로 PJAX 요청에 대한 레이아웃을 렌더링하지 않도록 레일에 지시하겠습니다. 이와 같은 작업을 수행해야 합니다. 그렇지 않으면 머리글과 바닥글이 중복됩니다. 즉. 귀하의 웹사이트는 Inception처럼 보일 것입니다.

def index
  if request.headers['X-PJAX']
    render :layout => false
  end
end

쉽지 않아요!

좋아, 내가 허용한 것보다 조금 더 복잡합니다. 대부분 소수의 사람들이 이야기하는 한 가지 거대한 함정 때문입니다.

페이지를 로드할 때마다 DOM이 지워지지 않으면 기존 Rails 앱에서 작동했을 수 있는 JS가 이제 매우 이상한 방식으로 중단된다는 의미입니다.

그 이유는 우리 중 많은 사람들이 우발적인 이름 충돌을 조장하는 방식으로 JS를 작성하는 법을 배웠기 때문입니다. 가장 교활한 범인 중 하나는 간단한 jquery 선택기입니다.

// I may look innocent, but I'm not!
$(".something")

다시 로드되지 않는 페이지용 JS 작성

다시 로드되지 않는 페이지에 대해 JS를 작성할 때 충돌이 가장 큰 문제입니다. 가장 이상하고 디버그하기 어려운 문제 중 일부는 JS가 절대로 건드릴 수 없는 HTML을 조작할 때 발생합니다. 예를 들어 일반적인 jQuery 이벤트 핸들러를 살펴보겠습니다.

$(document).on("click", ".hide-form", function() {
  $(".the-form").hide();
});

이것은 한 페이지에서만 실행되는 경우 완벽하게 합리적입니다. 그러나 DOM이 다시 로드되지 않으면 누군가가 와서 .hide-form 클래스로 다른 요소를 추가하는 것은 시간 문제일 뿐입니다. 이제 갈등이 생겼습니다.

이와 같은 충돌은 전역 참조가 많을 때 발생합니다. 그리고 어떻게 글로벌 참조의 수를 줄일 수 있습니까? 네임스페이스를 사용합니다.

네임스페이스 선택기

아래 Ruby 클래스에서는 하나의 전역 이름(클래스 이름)을 사용하여 많은 메서드 이름을 숨깁니다.

# One global name hides two method names
class MyClass
  def method1
  end

  def method2
  end
end

네임스페이스 DOM 요소에 대한 기본 제공 지원은 없지만(적어도 ES6 WebComponent가 도착할 때까지는) 코딩 규칙을 사용하여 네임스페이스를 시뮬레이션할 수 있습니다.

예를 들어 태그 수정 위젯을 구현하려고 한다고 가정해 보겠습니다. 네임스페이스가 없으면 다음과 같이 보일 수 있습니다. 세 가지 글로벌 참조가 있음을 유의하십시오.

// <input class="tags" type="text" />
// <button class="tag-submit">Save</button>
// <span class="tag-status"></span>

$(".tag-submit").click(function(){
  save($(".tags").val());
  $(".tag-status").html("Tags were saved");
});

그러나 "네임스페이스"를 만들고 모든 요소를 ​​이에 상대적으로 조회함으로써 전역 참조 수를 하나로 줄일 수 있습니다. 또한 여러 클래스를 완전히 없앴습니다.

// <div class="tags-component">
//   <input type="text" />
//   <button>Save</button>
//   <span></span>
// </div>

$container = $("#tags-component")
$container.on("click", "button" function(){
  save($container.find("input").val());
  $container.find("span").html("Tags were saved");
});

간단하다고 말씀드렸죠.

위의 예에서 'tags-component' 클래스가 있는 컨테이너 내부에 DOM 요소를 넣어 네임스페이스를 지정했습니다. 그 특정 이름에 대해 특별한 것은 없습니다. 그러나 모든 네임스페이스 컨테이너에 "-component"로 끝나는 클래스가 있는 명명 규칙을 채택하면 몇 가지 매우 흥미로운 일이 발생합니다.

잘못된 전역 선택자를 한 눈에 알아볼 수 있습니다.

허용하는 유일한 전역 선택기가 구성 요소이고 모든 구성 요소에 "-component"로 끝나는 클래스가 있는 경우 잘못된 전역 선택기가 있는지 한 눈에 알 수 있습니다.

// Bad
$(".foo").blah()

// Ok
$(".foo-component".blah()

HTML을 제어하는 ​​JS를 찾기 쉬워진다

대화형 태그 양식을 다시 살펴보겠습니다. 양식에 대한 HTML을 작성했습니다. 이제 JS와 CSS를 추가해야 합니다. 그런데 그 파일들을 어디에 두나요? 운 좋게도 네임스페이스에 대한 명명 체계가 있으면 JS 및 CSS 파일에 대한 명명 체계로 쉽게 변환됩니다. 디렉토리 트리는 다음과 같습니다.

.
├── javascripts
|   ├── application.coffee
│   └── components
│       └── tags.coffee
└── stylesheets
    ├── application.scss
    └── components
        └── tags.scss

자동 초기화

기존 웹 앱에서는 페이지 로드 시 모든 JS를 초기화하는 것이 일반적입니다. 그러나 PJAX 및 Turbolinks를 사용하면 DOM에서 항상 요소가 추가되고 제거됩니다. 이 때문에 코드가 새로운 구성 요소가 dom에 들어갈 때 자동으로 감지하고 해당 구성 요소에 필요한 JS를 즉시 초기화할 수 있다면 정말 유용합니다.

일관된 명명 체계를 사용하면 이 작업을 매우 쉽게 수행할 수 있습니다. 자동 초기화에 사용할 수 있는 방법은 셀 수 없이 많습니다. 여기 하나가 있습니다:

Initializers = {
  tags: function(el){
    $(el).on("click", "button", function(){  
      // setup component
    });
  }

  // Other initializers can go here
}

// Handler called on every normal and pjax page load
$(document).on("load, pjax:load", function(){
  for(var key in Initializers){
    $("." + key + "-component").each(Initializers[key]);
  }
}

CSS도 더 좋아집니다!

JavaScript는 웹 페이지에서 충돌의 유일한 원인이 아닙니다. CSS는 더 나빠질 수 있습니다! 다행히도 우리의 멋진 네임스페이스 시스템을 사용하면 충돌 없이 CSS를 훨씬 더 쉽게 작성할 수 있습니다. SCSS에서는 더욱 좋습니다.

.tags-component {
  input { ... }
  button { ... }
  span { ... }
}