WeChat 미니 프로그램의 공개 베타는 미니 프로그램 개발에 대한 학습의 물결을 시작했습니다. 이 프로그램은 크로스 플랫폼이며 바로 사용할 수 있으며 기본 경험과 비슷하며 완전한 문서화 및 효율적인 개발 프레임워크를 제공합니다. 개발자에게 놀라움을 선사합니다. 이번 글을 통해 미니 프로그램의 구조를 분석하고 개발 경험을 모두와 공유하겠습니다.
미니 프로그램 기능:
미니 프로그램 아키텍처
WeChat 미니 프로그램의 프레임워크는 보기 레이어와 앱의 두 부분으로 구성됩니다. 서비스 논리 계층, 보기 계층은 페이지 구조를 렌더링하는 데 사용되며 AppService 계층은 논리적 처리, 데이터 요청 및 인터페이스 호출에 사용됩니다.
뷰 레이어는 WebView를 사용하여 렌더링되고 로직 레이어는 JSCore를 사용하여 실행됩니다.
뷰 레이어와 로직 레이어는 시스템 레이어의 JSBridage를 통해 통신합니다. 로직 레이어는 데이터 변경 사항을 뷰 레이어에 알리고 뷰 레이어에서 페이지 업데이트를 트리거합니다. 비즈니스 처리를 위한 로직 레이어.
미니 프로그램이 시작되면 CDN에서 미니 프로그램 전체 패키지가 다운로드됩니다.
뷰(페이지뷰)
뷰 레이어는 WXML과 WXSS로 작성되며, 컴포넌트별로 표시됩니다.
로직 레이어의 데이터를 뷰에 반영하고, 뷰 레이어의 이벤트를 로직 레이어로 보냅니다.
1. 보기 - WXML
WXML(WeiXin Markup Language)
데이터 바인딩 지원
논리적 산술 및 연산 지원
지원 템플릿 및 참조
이벤트 추가 지원(bindtap)
wxml 컴파일러: wcc wxml 파일을 js로 변환 실행 방법: wcc index.wxml
보기 - WXSS
WXSS(WeiXin 스타일 시트)
대부분의 CSS 기능 지원
화면 너비에 따라 조정될 수 있는 크기 단위 rpx 추가
@import 문 사용 외부 스타일 시트로 가져올 수 있습니다.
다중 레벨 선택기를 지원하지 않습니다. 구성 요소의 내부 구조에 의해 파괴되는 것을 방지하기 위해
wxss 컴파일러: wcsc wxss 파일 변환 js 실행 방법: wcsc index.wxss
3. 보기 – WXSS 선택기
WXSS는 현재 다음 선택기를 지원합니다:
4 , 보기 - 구성요소
애플릿은 비즈니스 기능 개발을 위한 일련의 구성요소를 제공합니다. 기능에 따른 HTML5 태그와의 비교는 다음과 같습니다.
미니 프로그램 컴포넌트는 Web Component 표준을 기반으로 합니다.
Polymer 프레임워크를 사용하여 Web Component 구현
5. Component
현재 Native로 구현된 구성 요소 위에는
Native 구성 요소 레이어가 있습니다. WebView 레이어
App Service(논리 레이어)
로직 레이어는 데이터를 처리하여 뷰 레이어로 전달하는 동시에 이벤트를 받아들이는 역할을 합니다. 뷰 레이어의 피드백
1. 앱 진입( ) 애플릿 ; 페이지( ) 페이지 진입
3. WeChat 사용자 데이터, 스캔, 결제 및 기타 WeChat과 같은 풍부한 API 제공 - 특정 기능.
4. 각 페이지는 독립적인 범위를 가지며 모듈화 기능을 제공합니다.
5. 데이터 바인딩, 이벤트 배포, 라이프사이클 관리, 라우팅 관리
실행 환경
IOS - JSCore
Android - X5 JS 파서
DevTool - nwjs Chrome 커널
1. 앱 서비스 바인딩
데이터 바인딩은 Mustache 구문(이중 중괄호)을 사용하여 변수를 래핑합니다. 동적 데이터는 해당 페이지의 데이터에서 가져옵니다. 데이터는 setData 메서드를 통해 수정할 수 있습니다.
이벤트 바인딩은 구성 요소 속성과 동일한 방식으로 키와 값의 형식으로 작성됩니다. 키는 바인딩 또는 catch로 시작하고 그 뒤에는 바인딩, catchtouchstart, 이벤트 유형이 옵니다. 값이 문자열이면 해당 페이지에 동일한 이름의 함수를 정의해야 합니다.
2. 앱 서비스 - 라이프사이클
3. - API
API는 JSBridge를 통해 Native와 통신합니다
4. App Service - Router
navigateTo(OBJECT)
현재 페이지를 유지하고 앱의 페이지로 이동한 후 다시 탐색을 사용하여 원래 페이지로 돌아갑니다. 페이지 경로는 5단계만 가능합니다.
redirectTo(OBJECT)
현재 페이지를 닫고 애플리케이션 내 페이지로 이동합니다.
navigateBack(OBJECT)
현재 페이지를 닫고 이전 페이지 또는 다단계 페이지로 돌아갑니다. getCurrentPages())를 통해 현재 페이지 스택을 가져오고 반환할 수준 수를 결정할 수 있습니다.
5. 미니 프로그램 개발 경험
1. 미니 프로그램의 문제점
미니 프로그램은 여전히 네이티브 렌더링이 아닌 WebView를 사용합니다
독립적인 개발 필요 , WeChat 이외의 환경에서는 실행할 수 없습니다.
개발자는 새로운 구성요소를 확장할 수 없습니다.
서버 인터페이스에서 반환된 헤더(예: Set-Cookie)는 실행할 수 없습니다.
브라우저 환경에 의존하는 JS 라이브러리는 JSCore로 실행되며 윈도우나 문서 객체가 없기 때문에 사용할 수 없습니다.
WXSS에서는 로컬(이미지, 글꼴 등)을 사용할 수 없습니다.
WXSS는 rpx와의 호환성을 위해 CSS 대신 js로 변환됩니다.
WXSS는 계단식 선택기를 지원하지 않습니다.
애플릿에서 페이지를 열 수 없고 앱을 실행할 수 없습니다.
미니 프로그램은 공식 계정과 동일한 이름을 가질 수 없기 때문에 미니 프로그램의 이름은 Optional Stock+, Didi Chuxing DiDi가 되었습니다.
2. 작은 프로그램에서 배울 수 있는 장점
미리 새로운 WebView를 생성하고 새로운 페이지 렌더링을 준비하세요.
View 레이어와 로직 레이어는 분리되어 있으며, 데이터를 중심으로 구동되며 DOM을 직접 운영하지 않습니다.
로컬 업데이트에는 Virtual DOM을 사용하세요.
전송 중 보안을 보장하기 위해 모두 https를 사용합니다.
오프라인 기능을 활용하세요.
프론트엔드 구성요소 개발.
장치 크기를 분리하고 개발을 촉진하려면 rpx 단위를 추가하세요.
3. 위챗에서 탈피한 '미니 프로그램': PWA 프로그레시브 애플리케이션
PWA의 정식 명칭은 프로그레시브 웹 앱(Progressive Web Apps)으로 중국어로 프로그레시브 애플리케이션(Progressive Application)으로 출시됐다. Google은 2015년 6월 15일 개념을 발표했습니다.
프로그레시브 웹 앱은 웹과 기본 앱의 최고의 기능을 결합한 경험입니다. 별도의 어플리케이션 설치 없이 브라우저에서 바로 접속할 수 있어 처음 사용하시는 분들에게 매우 유리합니다. 시간이 지나면서 사용자가 애플리케이션과의 연결을 발전시키면 애플리케이션은 점점 더 강력해질 것입니다. 빠르게 로드되고 약한 네트워크 환경에서도 관련 메시지를 푸시할 수 있으며 기본 애플리케이션처럼 홈 화면에 추가하여 전체 화면 탐색 경험을 제공할 수 있습니다.
PWA는 다음과 같은 특징을 가지고 있습니다.
점진적 개선 - 새로운 기능을 지원하는 브라우저는 더 나은 경험을 제공하고, 이를 지원하지 않는 브라우저는 원본을 유지합니다. 경험.
오프라인 액세스 - 서비스 작업자는 오프라인이나 네트워크 속도가 좋지 않은 환경에서도 작업할 수 있습니다.
네이티브 유사 애플리케이션 - 앱 셸 모델을 사용하여 네이티브 애플리케이션과 유사한 환경을 구현합니다.
설치 가능 - 사용자가 앱 스토어를 거치지 않고도 자신에게 유용한 앱을 홈 화면에 보관할 수 있습니다.
손쉬운 공유 - URL을 통해 앱을 쉽게 공유할 수 있습니다.
지속적인 업데이트 - 서비스 워커의 업데이트 프로세스 덕분에 애플리케이션은 항상 업데이트된 상태를 유지할 수 있습니다.
보안 - HTTPS를 통해 서비스를 제공하여 네트워크 스누핑을 방지하고 콘텐츠가 변조되지 않도록 합니다.
검색 가능 - W3C 매니페스트 메타데이터 및 서비스 워커 등록 덕분에 검색 엔진에서 웹 애플리케이션을 검색할 수 있습니다.
재방문 - 메시지 푸시 등의 기능을 통해 사용자가 쉽게 다시 방문할 수 있도록 합니다.
Web App Manifest는 웹을 더욱 기본 기본 구성, 전체 화면 설정 등으로 만듭니다.
Service Works를 통한 웹 기능 향상
Service Works를 통한 리소스 오프라인 캐싱 및 업데이트
디스플레이 효율성을 향상시키는 App Shell
App Shell(애플리케이션 쉘)은 애플리케이션의 사용자 인터페이스에 필요한 가장 기본적인 HTML, CSS 및 JavaScript로, 첫 번째 로드 캐시되므로 사용할 때마다 다운로드할 필요가 없습니다. 대신 UI를 현지화하기 위해 필요한 데이터만 비동기식으로 로드됩니다.
미니 프로그램 프레임워크를 기반으로 개발된 할일 앱
다음은 이 앱 개발의 핵심 사항을 소개합니다. :
1. 이 앱의 디렉토리 구조와 구성은 문서-프레임워크 섹션에서 자세히 설명하지 않습니다. 이 플랫폼에는 html과 css가 없으며 wxml과 wxss로 대체되었습니다. wxss와 CSS 사이에는 거의 차이가 없습니다. 단점은 CSS만큼 강력하지 않고 제한된 선택기를 지원한다는 것입니다. 하지만 장점은 위챗이라는 플랫폼 하나만 있기 때문에 호환성 문제가 거의 없고 표준 및 업데이트된 CSS 기술을 사용할 수 있다는 점입니다. wxml에서는 플랫폼에서 제공하는 컴포넌트의 태그만 사용할 수 있으며, HTML 태그를 직접 사용할 수는 없습니다. wxml의 각 컴포넌트를 사용하는 방법의 예는 문서 - 컴포넌트 섹션에서 확인할 수 있습니다. 그래서 실제로 wxml과 wxss를 작성하는데는 문제가 없습니다.
2.wxml은 다음 기능을 지원합니다.
템플릿과 참고자료를 제외하고 나머지는 모두 todo 앱에서 사용되지만, 각 기능의 세부 사항은 사용되지 않으며, 적절한 기능만 선택합니다. 앱의 필요에 따라. 며칠 전 위챗 애플릿이 vue 프레임워크를 기반으로 구현될 수 있다는 기사를 보고 vue 문서를 살펴봤습니다. 데이터 바인딩, 조건부 렌더링, 목록 렌더링, 이벤트에 대해서는 vue의 사용법을 자세히 살펴보았습니다. 이에 비해 wxml에서 제공하는 기능은 vue의 관련 기능과 상당히 유사하지만, 기능이 그리 많지 않기 때문에 vue 프레임워크의 기능을 작은 프로그램에 직접 활용하기는 쉽지 않습니다. 모범 사례는 여전히 공식 문서에 제공된 지침을 기반으로 합니다. 공식 문서에 기능이 언급되지 않은 경우 추측하여 사용하면 확실히 작동하지 않습니다. 일부 객체의 프로토타입을 인쇄하여 확인해 보았는데, 공식 문서에서 나온 것보다 더 많은 인스턴스 메소드를 찾지 못했습니다. 이는 미니 프로그램의 프레임워크 기능이 실제로 제한되어 있음을 보여줍니다.
3. 선택기가 프레임워크의 요구 사항을 충족하는 한 Wxss는 실제로 less 또는 sass로 작성할 수 있습니다. 시간적 제약으로 인해 이 앱에서는 시도하지 않았습니다.
4. 양방향 바인딩이 없습니다. Vue에서 Vue 인스턴스는 뷰 모델입니다. 뷰 계층의 데이터 업데이트는 실시간으로 모델에 피드백됩니다. 미니 프로그램에는 양방향 바인딩이 없으며 뷰 업데이트가 모델에 직접 동기화되지 않습니다. 관련 이벤트 콜백의 뷰 레이어에서 직접 데이터를 가져온 다음 모델을 업데이트해야 합니다. setData를 통해 미니 프로그램은 내부적으로 setData를 사용합니다. 그런 다음 페이지를 다시 렌더링합니다. 예를 들어, 단일 할 일 항목의 경우 토글 작업은 다음과 같습니다.
toggleTodo: function( e ) { var id = this.getTodoId( e, 'todo-item-chk-' ); var value = e.detail.value[ 0 ]; var complete = !!value; var todo = this.getTodo( id ); todo.complete = complete; this.updateData( true ); this.updateStorage(); },
위 코드에서 단일 할 일 항목의 체크박스 값은 e.detail.value[0]을 통해 얻어지며, 이 값을 통해 할일의 완료 여부를 판단합니다. 마지막으로 updateData 내에서 setData 메소드를 통해 모델의 내용이 새로 고쳐집니다. 이 방법을 통해서만 앱 하단의 통계가 토글 작업 후에 업데이트됩니다.
5. 이벤트 바인딩 시 매개변수는 전달할 수 없으며 하나의 이벤트만 전달할 수 있습니다. 예를 들어, 위의 전환 작업에서 실제로는 현재 할 일의 ID를 콜백에 전달하고 싶었지만 가능한 모든 방법으로 이를 수행할 수는 없었습니다. 결국 ID 메서드(바인딩)를 통해서만 처리할 수 있었습니다. wxml에서 이벤트 구성 요소에 ID를 추가합니다. 이 ID는 전체 페이지에서 반복될 수 없으므로 ID 앞에는 이벤트가 트리거될 때 ID 값을 추가해야 합니다. , e.currentTarget.id 를 통해 얻을 수 있습니다. 컴포넌트의 id는 해당 id 접두사를 제거하여 todo의 id 값을 가져옵니다. 이는 현재 사용되는 방법이므로 나중에 구현하는 더 나은 방법을 찾을 수 있기를 바랍니다.
6. 앱에서는 로딩 효과를 고려하며, 이는 버튼 구성 요소의 로딩 속성을 사용하여 달성해야 합니다. 그러나 로딩은 단지 스타일 제어일 뿐 버튼을 반복적으로 클릭할 수 있는지 여부를 제어하지는 않습니다. 따라서 반복 클릭을 방지하려면 버튼의 비활성화 속성도 사용해야 합니다.
나머지 구현 세부 사항은 다음 두 파일의 소스 코드에 있습니다. 문제를 지적해 주세요.
index.wxml 소스 코드:
<!--list.wxml--> <view class="container"> <view class="app-hd"> <view class="fx1"> <input class="new-todo-input" value="{{newTodoText}}" auto-focus bindinput="newTodoTextInput"/> </view> <button type="primary" size="mini" bindtap="addOne" loading="{{addOneLoading}}" disabled="{{addOneLoading}}"> + Add </button> </view> <view class="todos-list" > <view class="todo-item {{index == 0 ? '' : 'todo-item-not-first'}} {{todo.complete ? 'todo-item-complete' : ''}}" wx:for="{{todos}}" wx:for-item="todo"> <view wx-if="{{!todo.editing}}"> <checkbox-group id="todo-item-chk-{{todo.id}}" bindchange="toggleTodo"> <label class="checkbox"> <checkbox value="1" checked="{{todo.complete}}"/> </label> </checkbox-group> </view> <view id="todo-item-txt-{{todo.id}}" class="todo-text" wx-if="{{!todo.editing}}" bindlongtap="startEdit"> <text>{{todo.text}}</text> </view> <view wx-if="{{!todo.editing}}"> <button id="btn-del-item-{{todo.id}}" bindtap="clearSingle" type="warn" size="mini" loading="{{todo.loading}}" disabled="{{todo.loading}}"> Clear </button> </view> <input id="todo-item-edit-{{todo.id}}" class="todo-text-input" value="{{todo.text}}" auto-focus bindblur="endEditTodo" wx-if="{{todo.editing}}"/> </view> </view> <view class="app-ft" wx:if="{{todos.length > 0}}"> <view class="fx1"> <checkbox-group bindchange="toggleAll"> <label class="checkbox"> <checkbox value="1" checked="{{todosOfUncomplted.length == 0}}"/> </label> </checkbox-group> <text>{{todosOfUncomplted.length}} left.</text> </view> <view wx:if="{{todosOfComplted.length > 0}}"> <button type="warn" size="mini" bindtap="clearAll" loading="{{clearAllLoading}}" disabled="{{clearAllLoading}}"> Clear {{todosOfComplted.length}} of done. </button> </view> </view> <loading hidden="{{loadingHidden}}" bindchange="loadingChange"> {{loadingText}} </loading> <toast hidden="{{toastHidden}}" bindchange="toastChange"> {{toastText}} </toast> </view>
index.js 소스 코드:
var app = getApp(); Page( { data: { todos: [], todosOfUncomplted: [], todosOfComplted: [], newTodoText: '', addOneLoading: false, loadingHidden: true, loadingText: '', toastHidden: true, toastText: '', clearAllLoading: false }, updateData: function( resetTodos ) { var data = {}; if( resetTodos ) { data.todos = this.data.todos; } data.todosOfUncomplted = this.data.todos.filter( function( t ) { return !t.complete; }); data.todosOfComplted = this.data.todos.filter( function( t ) { return t.complete; }); this.setData( data ); }, updateStorage: function() { var storage = []; this.data.todos.forEach( function( t ) { storage.push( { id: t.id, text: t.text, complete: t.complete }) }); wx.setStorageSync( 'todos', storage ); }, onLoad: function() { this.setData( { todos: wx.getStorageSync( 'todos' ) || [] }); this.updateData( false ); }, getTodo: function( id ) { return this.data.todos.filter( function( t ) { return id == t.id; })[ 0 ]; }, getTodoId: function( e, prefix ) { return e.currentTarget.id.substring( prefix.length ); }, toggleTodo: function( e ) { var id = this.getTodoId( e, 'todo-item-chk-' ); var value = e.detail.value[ 0 ]; var complete = !!value; var todo = this.getTodo( id ); todo.complete = complete; this.updateData( true ); this.updateStorage(); }, toggleAll: function( e ) { var value = e.detail.value[ 0 ]; var complete = !!value; this.data.todos.forEach( function( t ) { t.complete = complete; }); this.updateData( true ); this.updateStorage(); }, clearTodo: function( id ) { var targetIndex; this.data.todos.forEach( function( t, i ) { if( targetIndex !== undefined ) return; if( t.id == id ) { targetIndex = i; } }); this.data.todos.splice( targetIndex, 1 ); }, clearSingle: function( e ) { var id = this.getTodoId( e, 'btn-del-item-' ); var todo = this.getTodo( id ); todo.loading = true; this.updateData( true ); var that = this; setTimeout( function() { that.clearTodo( id ); that.updateData( true ); that.updateStorage(); }, 500 ); }, clearAll: function() { this.setData( { clearAllLoading: true }); var that = this; setTimeout( function() { that.data.todosOfComplted.forEach( function( t ) { that.clearTodo( t.id ); }); that.setData( { clearAllLoading: false }); that.updateData( true ); that.updateStorage(); that.setData( { toastHidden: false, toastText: 'Success' }); }, 500 ); }, startEdit: function( e ) { var id = this.getTodoId( e, 'todo-item-txt-' ); var todo = this.getTodo( id ); todo.editing = true; this.updateData( true ); this.updateStorage(); }, newTodoTextInput: function( e ) { this.setData( { newTodoText: e.detail.value }); }, endEditTodo: function( e ) { var id = this.getTodoId( e, 'todo-item-edit-' ); var todo = this.getTodo( id ); todo.editing = false; todo.text = e.detail.value; this.updateData( true ); this.updateStorage(); }, addOne: function( e ) { if( !this.data.newTodoText ) return; this.setData( { addOneLoading: true }); //open loading this.setData( { loadingHidden: false, loadingText: 'Waiting...' }); var that = this; setTimeout( function() { //close loading and toggle button loading status that.setData( { loadingHidden: true, addOneLoading: false, loadingText: '' }); that.data.todos.push( { id: app.getId(), text: that.data.newTodoText, compelte: false }); that.setData( { newTodoText: '' }); that.updateData( true ); that.updateStorage(); }, 500 ); }, loadingChange: function() { this.setData( { loadingHidden: true, loadingText: '' }); }, toastChange: function() { this.setData( { toastHidden: true, toastText: '' }); } });
最后需要补充的是,这个app在有限的时间内依据微信的官方文档进行开发,所以这里面的实现方式到底是不是合理的,我也不清楚。我也仅仅是通过这个app来了解小程序这个平台的用法。希望微信官方能够推出一些更全面、最好是项目性的demo,在代码层面,给我们这些开发者提供一个最佳实践规范。欢迎有其它的开发思路的朋友,帮我指出我以上实现中的问题。