搜尋
首頁微信小程式小程式開發微信小程式架構分析及實例

微信小程式架構分析及實例

Jan 24, 2017 am 11:24 AM
微信小程式

微信小程式的公測掀起了學習小程式開發的浪潮,天生跨平台,即用即走、媲美原生體驗、完善的文檔、高效的開發框架,小程式給開發者帶來了很多驚喜。透過這篇文章和大家一起分析小程式的架構,分享開發經驗。

小程式特色:

微信小程式架構分析及實例

小程式架構

微信小程式的框架包含兩部分View視圖層、App Service邏輯層,View層用來渲染頁面結構,AppService層用來邏輯處理、資料請求、接口調用,它們在兩個執行緒裡運行。

視圖層使用WebView渲染,邏輯層使用JSCore運作。

視圖層和邏輯層透過系統層的JSBridage進行通信,邏輯層把資料變化通知到視圖層,觸發視圖層頁面更新,視圖層把觸發的事件通知到邏輯層進行業務處理。

微信小程式架構分析及實例

小程式啟動時會從CDN下載小程式的完整包 

微信小程式架構分析及實例

View (頁視圖)

視圖層由 WXML 與 WXSS 編寫,並由元件來展示。

將邏輯層的資料反應成視圖,同時將視圖層的事件傳送給邏輯層。

1、View - WXML

WXML(WeiXin Markup Language)

支援資料綁定

支援邏輯算術、運算

支援範本、引用

支援邏輯算術、運算

支援範本、引用

支援邏輯算術、運算

支援範本、引用wxml檔轉為js   執行方式:wcc index.wxml  

2、View - WXSS

WXSS(WeiXin Style Sheets)

支援大部分CSS特性

添加尺寸單位來適應寬度@import語句可以導入外聯樣式表

不支援多層選擇器-避免被元件內結構破壞微信小程式架構分析及實例

wxss編譯器:wcsc 把wxss檔轉換為js 執行方式: wcsc index.wxxss編譯器:wcsc 把wxss檔轉換成js 執行方式: wcscscindex.wxxxss 、View – WXSS Selectors

WXSS目前支援以下選擇器:

微信小程式架構分析及實例

4、View - Component

小程式提供了一系列組件用於開發業務功能,按照功能與HTML5的標籤進行對比如下:

小程式提供了一系列元件用於開發業務功能,按照功能與HTML5的標籤進行對比如下:

微信小程式架構分析及實例

小程式的元件是基於Web Component標準

使用Polymer框架實現Web Component

微信小程式架構分析及實例

5、View - Native Component

>

Native組件層在WebView層之上

微信小程式架構分析及實例

App Service(邏輯層)

邏輯層將數據進行處理後發送給視圖層,同時接受視圖層的事件反饋

1 、App( ) 小程式的入口;Page( ) 頁面的入口

3、提供豐富的API,如微信用戶數據,掃一掃,支付等微信特有能力。

4、每個頁面有獨立的作用域,並提供模組化能力。

5、資料綁定、事件分發、生命週期管理、路由管理

運行環境

IOS - JSCore

Android - X5 JS解析器

DevTool - nwjs 核心

資料綁定使用 Mustache 語法(雙大括號)將變數包起來,動態資料均來自對應 Page 的 data,可以透過setData方法修改資料。


事件綁定的寫法同組件的屬性,以key、value 的形式,key 以bind或catch開頭,然後跟上事件的類型,如bindtap, catchtouchstart,value 是一個字符串,需要在對應的Page中定義同名的函數。

微信小程式架構分析及實例

微信小程式架構分析及實例

2、App Service - Life Cylce

微信小程式架構分析及實例

3、App Service - API

API透過JSBridge、

navigateTo( OBJECT)

微信小程式架構分析及實例保留目前頁面,跳到應用程式內的某個頁面,使用navigateBack可以回到原始頁面。頁面路徑只能是五層

redirectTo(OBJECT)

關閉目前頁面,跳到應用程式內的某個頁面。

navigateBack(OBJECT)

關閉目前頁面,返回上一頁或多級頁面。可透過 getCurrentPages()) 取得目前的頁面棧,決定需要傳回幾層。

五、小程式開發經驗

1、小程式存在的問題

小程式仍使用WebView渲染,並非原生渲染

需要獨立開發,無法在非微信環境中運作 。

開發者不可以擴充新組件。

服務端介面回傳的頭無法執行,例如:Set-Cookie。

依賴瀏覽器環境的js庫不能使用,因為是JSCore執行的,沒有window、document物件。

WXSS中無法使用本地(圖片、字體等)。

WXSS轉換成js 而不是css,為了相容rpx。

WXSS不支援級聯選擇器。

小程式無法開啟頁面,無法拉起APP。

小程式不能和公眾號重名,於是小程式的名字就成了:自選股+、滴滴出行DiDi 。

2、小程式可以參考的優點

提前新建WebView,準備新頁面渲染。

View層和邏輯層分離,透過資料驅動,不直接操作DOM。

使用Virtual DOM,進行局部更新。

全部使用https,確保傳輸中安全。

使用離線能力 。

前端組件化開發。

加入rpx單位,隔離設備尺寸,方便開發。

3、脫離微信的「小程式」:PWA 漸進式應用

PWA 全名是 Progressive Web Apps ,譯成中文就是漸進式應用,是 Google 在 2015 年 6 月 15 日提出的概念。

Progressive Web Apps 是結合了 web 和 原生應用中最好功能的一種體驗。對於首次訪問的用戶它是非常有利的, 用戶可以直接在瀏覽器中進行訪問,不需要安裝應用。隨著時間的推移當用戶漸漸地和應用程式建立了聯繫,它將變得越來越強大。它能夠快速地加載,即使在弱網絡環境下,能夠推送相關消息, 也可以像原生應用那樣添加至主屏,能夠有全屏瀏覽的體驗。

PWA具有以下特點:

漸進增強 - 支援的新特性的瀏覽器獲得更好的體驗,不支援的保持原來的體驗。

微信小程式架構分析及實例離線存取 - 透過 service workers 可以在離線或網路速差的環境下工作。

類原生應用  - 使用app shell model做到原生應用般的體驗。

可安裝 - 允許用戶保留對他們有用的應用在主螢幕上,不需要透過應用程式商店。

容易分享 - 透過 URL 可以輕鬆分享應用程式。

持續更新 - 受益於 service worker 的更新進程,應用程式能夠始終保持更新。

安全 - 透過 HTTPS 來提供服務來防止網路窺探,確保內容不被竄改。

可搜尋 - 得益於 W3C manifests 元資料和 service worker 的登記,讓搜尋引擎能夠找到 web 應用。

再次存取 - 透過訊息推送等特性讓使用者再次存取變得容易。

Web App Manifest讓Web更像Native

Web App Manifest以JSON的格式定義Web應用的相關配置(應用名稱、圖標或圖像連接、啟動URL、自訂特性、啟動預設配置、全螢幕設定等)。

Service Workers增強Web能力

透過Service Works實現資源離線快取與更新

微信小程式架構分析及實例

App Shell 提升顯示效率

App Shell(應用程式外殼)是應用程式的使用者介面所需的最基本的HTML、CSS 和JavaScript,首次載入後立刻被快取下來,不需要每次使用時都被下載,而是只非同步載入所需的數據,以達到UI保持本地化。

微信小程式架構分析及實例

一個根據小程序的框架開發的todos app

下面來介紹下這個app開發的要點:

1. 這個app的目錄結構以及配置等就不詳細介紹了,這些在文檔-框架部分都有很詳細的描述。這個平台裡面沒有html和css,取而代之的是wxml和wxss。 wxss跟css幾乎沒有差別,缺點就是不如css強大,支援的選擇器有限。但是好處是由於只有微信這一個平台,所以幾乎沒有相容性問題,能夠使用標準的,更新的css技術。 wxml裡面只能用平台提供的那些元件的標籤,html的標籤不能直接用,各個元件的在wxml的使用方式,都可以在文件-元件這一部分找到說明的範例。所以實際上wxml跟wxss寫起來都沒有什麼難題。

2. wxml支援以下這些特性:

在todo app裡面除了模板和引用沒有用到之外,其它的都使用到了,不過沒有使用到每個特性的各個細節,只根據app的需要選用合適的功能。前幾天看到有文章說,微信小程式可能是基於vue框架來實現的,所以就看了下vue的文檔。對於資料綁定,條件渲染,列表渲染,事件這幾部分都詳細看了vue的用法。對比下來,wxml提供的這些特性,跟vue的相關特性是還比較像,不過功能並沒有那麼多,所以也不能輕易地直接拿vue框架的特性用到小程式裡面。最佳實踐,還是基於官方文件中提供的說明來,如果官方文件中沒有提到的功能,透過猜測的方式去用,肯定是行不通的。我透過列印的方式,查看一些物件的原型,也沒有發現比官方文件要多的一些實例方法,說明小程式的框架功能確實是有限的。

3. wxss其實是可以用less或sass來寫的,只要選擇器滿足框架的要求即可。由於時間原因,就沒有在這個app裡面去嘗試了。

4. 沒有雙向綁定。在vue裡面,一個vue實例就是一個view-model;view層對資料的更新,會即時回饋到model;model的更新,也會即時回饋的到view。在小程式裡面,沒有雙向綁定,view的更新不會直接同步到model;需要在相關事件回調裡面,直接從view層拿到數據,然後透過setData的方式,更新model,小程式內部會在setData之後重新渲染page。例如單一todo項,toggle的操作:

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]拿到單一todo項裡面checkbox的值,透過該值來判斷todo的complete狀態。最後在updateData的內部,也會透過setData方法,刷新model的內容。只有這樣,在toggle作業之後,app底部的統計資料才會更新。

5. 事件綁定的時候,無法傳遞參數,只能傳遞一個event。例如上面那個toggle的操作,我其實很想在回調裡面把目前todo的id傳到這個回調裡面,但是想盡辦法都做不到,最後只能透過id的方式來處理:就是在wxml中綁定事件的元件上面,加一個id,這個id全page不能重複,所以id得加前綴,然後在id最後加上todo的id值;當事件觸發的時候,透過e.currentTarget.id就能拿到該組件的id,去掉對應的id前綴,就得到todo的id值了。這是目前用到的方法,我認為不是很優雅,希望後面能發現更好的方法來實現。

微信小程式架構分析及實例

6. app中考慮到了loading的效果,要利用button組件的loading屬性來實現。但是loading只是一個樣式的控制,它不會控制這個按鈕是否能重複點擊。所以也要利用buttong的disabled屬性,防止重複點擊。

剩下的實作細節,都在下面兩個文件的源碼中,歡迎大家指出其中的問題。

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 ? &#39;&#39; : &#39;todo-item-not-first&#39;}} {{todo.complete ? &#39;todo-item-complete&#39; : &#39;&#39;}}" 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: &#39;&#39;,
  addOneLoading: false,
  loadingHidden: true,
  loadingText: &#39;&#39;,
  toastHidden: true,
  toastText: &#39;&#39;,
  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( &#39;todos&#39;, storage );
 },
 onLoad: function() {
  this.setData( {
   todos: wx.getStorageSync( &#39;todos&#39; ) || []
  });
  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, &#39;todo-item-chk-&#39; );
  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, &#39;btn-del-item-&#39; );
  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: &#39;Success&#39;
   });
  }, 500 );
 
 },
 startEdit: function( e ) {
  var id = this.getTodoId( e, &#39;todo-item-txt-&#39; );
  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, &#39;todo-item-edit-&#39; );
  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: &#39;Waiting...&#39;
  });
 
  var that = this;
  setTimeout( function() {
   //close loading and toggle button loading status
   that.setData( {
    loadingHidden: true,
    addOneLoading: false,
    loadingText: &#39;&#39;
   });
 
   that.data.todos.push( {
    id: app.getId(),
    text: that.data.newTodoText,
    compelte: false
   });
 
   that.setData( {
    newTodoText: &#39;&#39;
   });
 
   that.updateData( true );
   that.updateStorage();
  }, 500 );
 },
 loadingChange: function() {
  this.setData( {
   loadingHidden: true,
   loadingText: &#39;&#39;
  });
 },
 toastChange: function() {
  this.setData( {
   toastHidden: true,
   toastText: &#39;&#39;
  });
 }
});

最后需要补充的是,这个app在有限的时间内依据微信的官方文档进行开发,所以这里面的实现方式到底是不是合理的,我也不清楚。我也仅仅是通过这个app来了解小程序这个平台的用法。希望微信官方能够推出一些更全面、最好是项目性的demo,在代码层面,给我们这些开发者提供一个最佳实践规范。欢迎有其它的开发思路的朋友,帮我指出我以上实现中的问题。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。