웹 앱을 단순하게 유지하는 일은 놀라울 정도로 복잡할 수 있습니다. 이 모듈에서는 웹 API가 스레딩과 작동하는 방식과 상태 관리와 같은 일반적인 PWA 패턴에 웹 API를 사용하는 방법을 알아봅니다.
단순성과 복잡성
Rich Hickey는 Simple Made Easy 강연에서 단순한 사물과 복잡한 사물의 자질에 대해 이야기합니다. 그는 간단한 것을 다음에 집중하는 것으로 설명합니다.
"역할 1개, 태스크 1개, 개념 1개, 차원 1개"
하지만 단순함이 다음을 의미하지는 않는다는 것을 강조합니다.
"하나의 인스턴스를 보유하거나 하나의 작업을 수행"
무엇인가가 단순한지 여부는 그것이 얼마나 서로 연결되어 있는지에 달려 있습니다.
복잡성은 결합, 직조 또는 Rich의 용어를 사용하여 서로를 완성하는 것에서 비롯됩니다. 무언가를 관리하는 역할, 태스크, 개념 또는 측정기준의 수를 집계하여 복잡성을 계산할 수 있습니다.
코드가 간단하면 이해하고 유지하기가 더 쉬우므로 소프트웨어 개발에서 단순성이 필수적입니다. 웹 앱에도 단순성이 필요합니다. 가능한 모든 컨텍스트에서 앱의 속도와 접근성을 높이는 데 도움이 될 수 있기 때문입니다.
PWA 복잡성 관리
우리가 웹용으로 작성하는 모든 JavaScript는 한 지점에서 기본 스레드에 접촉합니다. 하지만 기본 스레드는 개발자가 제어할 수 없는, 즉시 사용할 수 있는 많은 복잡성을 수반합니다.
기본 스레드는 다음과 같습니다.
- 페이지 그리기를 담당합니다. 페이지 그리기 자체는 스타일 계산, 레이어 업데이트 및 합성, 화면에 페인팅이 포함된 복잡한 다단계 프로세스입니다.
- 스크롤과 같은 이벤트를 포함하여 이벤트를 수신 대기하고 반응합니다.
- 페이지 로드 및 로드 취소를 담당합니다.
- 미디어, 보안, ID 관리 작성한 코드가 해당 스레드에서 실행되기 전에 모든 작업이 완료되었습니다. 예를 들면 다음과 같습니다.
- DOM 조작.
- 민감한 API 액세스(예: 기기 기능 또는 미디어/보안/ID)
Surma가 2019 Chrome Dev Summit 대담에서 언급했듯이 기본 스레드는 업무가 과중하고 보급이 부족합니다.
그럼에도 불구하고 대부분의 애플리케이션 코드도 기본 스레드에 존재합니다.
이러한 모든 코드는 기본 스레드의 복잡성을 더합니다. 기본 스레드는 브라우저가 화면에 콘텐츠를 배치하고 렌더링하는 데 사용할 수 있는 유일한 스레드입니다. 따라서 코드를 완료하는 데 점점 더 많은 처리 능력이 필요한 경우 신속하게 실행해야 합니다. 애플리케이션 로직을 실행하는 데 걸리는 1초가 브라우저가 사용자 입력에 응답하거나 페이지를 다시 그릴 수 없기 때문입니다.
상호작용이 입력과 연결되지 않거나 프레임이 드롭되거나 사이트를 사용하는 데 시간이 너무 오래 걸리면 사용자는 불만을 느끼고 애플리케이션이 고장 났다고 느끼고 자신감이 떨어집니다.
나쁜 소식은 기본 스레드에 복잡성을 추가하면 이러한 목표를 달성하기 어렵게 만드는 거의 확실한 방법입니다. 다행히 기본 스레드가 해야 할 일은 명확하므로 애플리케이션의 나머지 부분에서 기본 스레드에 대한 의존도를 줄이는 가이드로 사용할 수 있습니다.
관심사 분리
웹 애플리케이션이 수행하는 작업에는 여러 가지가 있지만 대체로 UI에 직접 영향을 미치는 작업과 그렇지 않은 작업으로 나눌 수 있습니다. UI 작업은 다음과 같은 작업입니다.
- DOM을 직접 터치합니다.
- 알림 또는 파일 시스템 액세스와 같이 기기 기능을 터치하는 API를 사용합니다.
- 사용자 쿠키, 로컬 또는 세션 저장소와 같은 ID를 터치합니다.
- 이미지, 오디오, 동영상 등의 미디어를 관리합니다.
- 웹 일련번호 API와 같이 사용자의 개입이 필요한 보안에 영향을 미칩니다.
UI 외 작업에는 다음이 포함될 수 있습니다.
- 단순한 계산.
- 데이터 액세스 (fetch, IndexedDB 등)
- 암호화.
- 메시지
- blob 또는 스트림 생성이나 조작
비 UI 작업은 종종 UI 작업에 의해 예약됩니다. 즉, 사용자가 API에 대한 네트워크 요청을 트리거하는 버튼을 클릭하면 파싱된 결과가 반환되고 이 결과는 DOM을 업데이트하는 데 사용됩니다. 코드를 작성할 때 이러한 엔드 투 엔드 환경을 고려하는 경우가 많지만 흐름의 각 부분에는 일반적으로 그렇지 않습니다. UI 작업과 비 UI 작업의 경계는 엔드 투 엔드 환경만큼 고려해야 합니다. 기본 스레드 복잡성을 줄일 수 있는 첫 번째 영역이기 때문입니다.
단일 작업에 포커스
코드를 단순화하는 가장 간단한 방법 중 하나는 함수를 분리하여 각각 하나의 작업에 집중하는 것입니다. 작업은 엔드 투 엔드 환경을 살펴보면서 식별된 경계를 통해 결정할 수 있습니다.
- 먼저 사용자 입력에 응답합니다. 이것이 UI 작업입니다.
- 그런 다음 API 요청을 수행합니다. 이는 UI가 아닌 작업입니다.
- 그런 다음 API 요청을 파싱합니다. 이 역시 UI가 아닌 작업입니다.
- 그런 다음 DOM 변경사항을 확인합니다. UI 작업일 수도 있고, 가상 DOM 구현 등을 사용 중이라면 UI 작업이 아닐 수도 있습니다.
- 마지막으로 DOM을 변경합니다. 이것이 UI 작업입니다.
첫 번째 명확한 경계는 UI 작업과 비 UI 작업 사이의 경계입니다. 그리고 API 요청을 만들고 파싱하는 작업이 한두 개의 작업인지 판단해야 합니다. DOM 변경사항이 UI가 아닌 작업인 경우 API 작업과 함께 번들로 제공되나요? 같은 대화목록에 계신가요? 다른 대화목록에 계신가요? 여기서 적절한 수준의 분리는 코드베이스를 단순화하고 코드베이스의 일부를 기본 스레드 외부로 성공적으로 이동하는 데 모두 중요합니다.
구성 가능성
대규모 엔드 투 엔드 워크플로를 작은 부분으로 나누려면 코드베이스의 구성 가능성을 고려해야 합니다. 함수 프로그래밍에서 힌트를 얻어 다음 사항을 고려해 보세요.
- 애플리케이션이 수행하는 작업의 유형 분류
- 일반적인 입력 및 출력 인터페이스 빌드
예를 들어 모든 API 검색 작업은 API 엔드포인트를 가져와서 표준 객체의 배열을 반환하며, 모든 데이터 처리 함수는 이를 받아 표준 객체의 배열을 반환합니다.
JavaScript에는 복잡한 JavaScript 객체를 복사하기 위한 구조화된 클론 알고리즘이 있습니다. 웹 작업자는 메시지를 보낼 때 이를 사용하고 IndexedDB는 객체를 저장하는 데 사용합니다. 구조화된 클론 알고리즘과 함께 사용할 인터페이스를 선택하면 더욱 유연하게 실행할 수 있습니다.
이를 염두에 두고 코드를 분류하고 이러한 카테고리의 공통 I/O 인터페이스를 만들어 구성 가능한 기능의 라이브러리를 만들 수 있습니다. 구성 가능한 코드는 심층적으로 상호 연결되어 쉽게 분리할 수 없는 복잡한 코드와 달리 느슨하게 결합된 상호 교환 가능한 조각인 단순한 코드베이스의 특징입니다. 웹에서 구성 가능한 코드는 기본 스레드의 과도한 작업 여부에 대한 차이를 의미할 수 있습니다.
구성 가능한 코드를 준비했으니 이제 일부 코드를 기본 스레드에서 가져올 차례입니다.
웹 작업자를 사용하여 복잡성 감소
잘 활용되지는 않지만 널리 사용되는 웹 기능인 웹 작업자를 통해 기본 스레드 외부로 작업을 이동할 수 있습니다.
웹 작업자는 PWA가 기본 스레드 외부에서 (일부) JavaScript를 실행할 수 있도록 합니다.
작업자에는 세 가지 유형이 있습니다.
전용 작업자는 웹 작업자를 설명할 때 가장 흔히 사용되는 것으로, PWA의 단일 인스턴스에서 단일 스크립트로 사용될 수 있습니다. 가능하면 DOM과 직접 상호작용하지 않는 작업은 성능 향상을 위해 웹 워커로 이동해야 합니다.
공유 작업자는 여러 스크립트가 열린 여러 창에서 공유할 수 있다는 점을 제외하면 전용 작업자와 유사합니다. 이렇게 하면 전용 작업자의 이점을 누리면서도 창과 스크립트 간에 상태 및 내부 컨텍스트를 공유할 수 있습니다.
예를 들어 공유 작업자는 PWA의 IndexedDB에 대한 액세스와 트랜잭션을 관리하고 모든 호출 스크립트에 트랜잭션 결과를 브로드캐스트하여 변경사항에 반응할 수 있습니다.
마지막 웹 워커는 이 과정에서 광범위하게 다루는 서비스 워커입니다. 서비스 워커는 네트워크 요청의 프록시 역할을 하며 PWA의 모든 인스턴스 간에 공유됩니다.
직접 해보기
코드 타임입니다. 이 모듈에서 배운 모든 내용을 바탕으로 PWA를 처음부터 빌드합니다.