關於weex

生命週期

<script>
  module.exports = {
    data: {},
    methods: {},

    init: function () {
      console.log('在初始化內部變量,而且添加了事件功能後被觸發');
    },
    created: function () {
      console.log('完成數據綁定以後,模板編譯以前被觸發');
    },
    ready: function () {
      console.log('模板已經編譯而且生成了 Virtual DOM 以後被觸發');
    },
    destroyed: function () {
      console.log('在頁面被銷燬時調用');
    }
  }
</script>

init內通常用於初始化一些內部變量,綁定一些自定義事件,這時尚未數據綁定,沒有建立vdom,因此不能經過this獲取到data和methods,也不能獲取vdom的節點css

created 完成了數據綁定 ,但還未開始編譯模板,能夠經過this獲取data和methods,但不能獲取vdom的節點html

ready表示渲染完成 ,從子組件往上觸發前端

destroyed 組件銷燬,好比頁面跳轉,從子組件開始往上觸發node

工做原理

clipboard.png

clipboard.png

clipboard.png

Weex設計之初就考慮到在三端(iOS、安卓和H5)上可以獲得展示。在最上面的DSL,阿里通常稱之爲Weex文件(.we),經過Transformer轉換成js-bundle,再部署到服務器,這樣服務端就完成了。在客戶端,第一層是JS-Framework,最後到RenderRengine。webpack

clipboard.png

輸入是Virtual DOM輸出是native或者H5 view,還原成內存中的樹型數據結構,再建立view,把事件綁定在view上,把view基本屬性設上去。Weex Render會分三個線程,不一樣的線程負責不一樣的事情,讓JS線程優先保障流暢性。git

工做模式

Weex的三種工做模式。github

  1. 全頁模式web

目前支持單頁使用或整個App使用Weex開發(還不完善,須要開發Router和生命週期管理),這是主推的模式,能夠類比RN。npm

  1. Native Component模式數組

把Weex看成一個iOS/Android組件來使用,類比ImageView。這類需求遍及手淘主鏈路,如首頁、主搜結果、交易組件化等,這類Native頁面主體已經很穩定,可是局部動態化需求旺盛致使頻繁發版,解決這類問題也是Weex的重點。

  1. H5 Component模式

在H5種使用Weex,類比WVC。一些較複雜或特殊的H5頁面短時間內沒法徹底轉爲Weex全頁模式(或RN),好比互動類頁面、一些複雜頻道頁等。這個痛點的解決辦法是:在現有的H5頁面上作微調,引入Native解決長列表內存暴增、滾動不流暢、動畫/手勢體驗差等問題。

另外,WVC將會融入到Weex中,成爲Weex的H5 Components模式。

各類文檔

官方

官網 http://alibaba.github.io/weex/
官方英文 http://alibaba.github.io/weex...
官方示例 http://alibaba.github.io/weex...
Weex Playground http://alibaba.github.io/weex...
github https://github.com/alibaba/weex
工程開發套件 https://github.com/weexteam/w...
命令行工具 https://github.com/weexteam/w...
調試工具 https://github.com/weexteam/w...

第三方

awesome-weex https://github.com/joggerplus...
vczero https://github.com/vczero/wee...
h5weex開發相關文章 https://github.com/h5weex/h5w...
weex中文文檔 http://doc.weexstore.com/177086

社區討論組

gitter https://gitter.im/weexteam/cn
weex-help http://weex.help/
weex.store http://weexstore.com/

demo

weex-demo-dusan 實現splash,guide,home頁面,交互主要是點擊,左右,上下滑動。https://github.com/duqian2919...

hello-weex包括一個Weex App,和本身擴展的WeexiOSKit。
hello-weex https://github.com/coderyi/he...

toolbox-weex 一個小型的項目,幫助新手體會一下如何作出能夠看、能夠用的原生界面。
https://github.com/hugojing/t...

模塊化

weex不少功能都進行了模塊化的封裝,內置模塊引用須要添加@weex-module前綴,使用require('@weex-module/name')能夠進行引用。

現有模塊包括:

  • dom

  • steam

  • modal

  • animation

  • webview

  • navigator

文章

阿里巴巴開源前端框架--Weex實踐
http://blog.csdn.net/zhangcan...

Weex詳解:靈活的移動端高性能動態化方案 http://www.imooc.com/article/...

給正在學習Vuejs同窗的幾個小Tipshttp://www.imooc.com/article/...

阿里無線11.11 : Weex——關於移動端動態性的思考、實現和將來 http://www.infoq.com/cn/artic...

細節

文件目錄結構

Weex默認的文件結構是要求全部相關的we文件都在同一級目錄下,以便能準確的找到依賴的組件,例如:

bar.we

<template>
  <div><text>bar</text></div>
</template>
foo.we

<template>
  <div><bar></bar></div>
</template>

當須要提取一些公共組件,這些公共組件通常存放在一個公共目錄下(自建的目錄或經過npm安裝到node_modules目錄),而這樣的文件結構,也每每出如今一些完整的項目工程中,當經過上述的腳手架搭建好示例工程手,能夠經過前端習慣的require方式來引用非相同目錄下的we文件,例如:

components/bar.we

<template>
 <div><text>bar</text></div>
</template>
foo.we

<template>
 <div><bar></bar></div>
</template>

<script>
require('./components/bar')
</script>

其背後的原理,其實是整個轉換和打包過程藉助了webpack以及weex-loader,使得其中的模塊化定義遵循標準的The way of CommonJS。

引用標準JS文件

有了webpack的助力,在we文件中,也能輕鬆使用一個符合CommonJS規範的JS文件。例如,經過npm安裝了業界No.1的工具庫lodash:

foo.we

<template>
 <div><text>{{foo + bar}}</text></div>
</template>

<script>
var _  = require('lodash')
module.exports = {
  data: {
    foo: 'foo',
    bar: 'bar'
  },
  created: function() {
    _.assign(this.data, {foo: 'the foo', bar: 'the bar'})
  }
}
</script>

##用上Tomorrow's css和ES2015
現在前端的開發,通常離不開預處理器,好比postcss和babel。在默認的we文件中,即便有webpack的助力,這類預處理器也是對其無能爲力的。爲此,咱們須要拆分這個we文件,讓它變成標準的html、css或js文件。

bar.we.html

<template>
 <div><text class="hello">Hello {{name}}</text></div>
</template>
bar.we.css

.hello {
  font-size: 40px;
  color: #333;
}
bar.we.js

module.exports = {
  template: require('./foo.we.html'),
  style: require('./foo.we.css'),
  data: {
    name: 'Weex'
  }
}

而且,須要在webpack.config.js中加入幾個能解析這些特殊文件的loader:

loaders: [
  {
    test: /\.we\.js(\?[^?]+)?$/,
    loaders: ['weex?type=script']
  },
  {
    test: /\.we\.css(\?[^?]+)?$/, 
    loaders: ['weex?type=style']
  },
  {
    test: /\.we\.html(\?[^?]+)?$/, 
    loaders: ['weex?type=tpl']
  }
]

以後,仍然使用require的方式來引用這個'we'文件:

foo.we

<template>
 <div><bar></bar></div>
</template>

<script>
require('./bar.we.js')
</script>

當分割了we文件後,你就能夠分別對其中的css或js文件使用你想要的預處理器。

!!!不過須要特別提醒的是,目前weex-loader只支持module.exports={...}的模塊輸出方式,因此即便你在js文件中用了ES6的import,但請勿使用export來導出模塊

調用native提供的模塊方法

Weex的代碼自己是運行在js的runtime下的,因此爲了和native進行通信,就須要藉由hybrid的方式。其中,對於native提供的一系列模塊方法,就須要用一種特殊,但直觀的方式來調用。

本來,Weex中集成了一些預約義的API,例如this.$sendMtop。但這些預約義API的維護成本太高,所以在最新甚至之後的Weex版本中,會漸漸廢棄這類預約義的API,而改用更加通用的方式:

var stream = require('@weex-module/stream')
module.exports = {
  ready: function() {
    if (stream && stream.sendMtop) {
      stream.sendMtop(params, callback)
    } else {
      console.error('stream.sendMtop is invalid')
    }
  }
}

這裏又再次請出了萬能的 require ,不過和普通的 require 不一樣的是,須要指定特定的 @weex-module 前綴方能正確使用

慎用或不用異步函數

爲了解釋異步函數的在Weex中的危害,首先要理解在Weex中產生的兩類task。

一類,是由Weex控制的js和native交互時產生的task(如下簡稱Weex的task),好比一系列異步調用native模塊方法,或者點擊事件等。

一類,是系統原生的task(如下簡稱原生的task),好比·setTimeout,Promise·。

在Weex的task中更新數據時,Weex能夠自動更新View。而在原生的task中,由於Weex喪失了控制權,因此沒法作到自動更新。這就致使,在原生的task中產生的diff,會滯留直到下一次Weex的task纔會被觸發更新View。從表面上看,就是在這些原生的task中改變數據後,並無及時反應到View上。爲了,避免這個問題的產生,目前來講並不推薦使用Promise。而對於setTimeout來講,可使用native提供的timer.setTimeout的模塊方法。

生命週期的一二三

Weex當前版本設計了組件的生命週期,如下的一張圖能夠比較直觀的告訴你們在整個生命週期裏都作了些什麼事情:

clipboard.png

那麼在這些生命週期的Hook裏,能夠作哪些事情呢:

在init中能夠進行數據請求,好比mtop。但這個時候上下文中尚未data對象,同時也不建議在以後的任何階段改變data的數據結構。
在created中,能夠對data進行操做了,且此時更新數據不會產生多餘的diff,但切忌也不能更改data的數據結構。另外,能夠經過this.$on來監聽子組件的dispatch。
在ready中,此時子組件已經ready,能夠獲取子組件的Vm對象了。而此時,若是更新數據,會產生多餘的diff。
特別提醒:這三個階段,都是不容許更改data的數據結構的。

[Bug]設置樣式的默認值

來看一個經過改變class來改變樣式的例子:

<template>
  <div>
    <text class="{{className}}" onclick="toggle">Hello Weex</text>
  </div>
</template>

<style>
.normal {
  font-size: 40px;
}

.hightlight {
  font-size: 40px;
  color: red;
}
</style>

<script>
module.exports = {
  data: {
    className: 'normal'
  },
  methods: {
    toggle: function() {
      if (this.className === 'normal') {
         this.className = 'hightlight'
      } else {
        this.className = 'normal'
      }
    }
  }
}
</script>

上述例子,經過點擊來切換樣式名。可是你會驚奇的發現,在最初一次切換以後,字體的顏色就一直是紅色的了。

這實際上是目前Weex一個bug,討論如何修復的issue在這裏#397。緣由就是,在Weex中樣式表樣式的切換,並不會清除原來的樣式。例如,當前樣式是highlight,其中字體顏色是red,在切換到normal時,由於沒有指定字體顏色,結果原來的red顏色就被保留了下來而並無清除掉。因此,在上面的例子中爲了避開這個bug,須要顯示的設置字體顏色:

.normal {
  font-size: 40px;
  color: black;
}

另外,對於最佳實踐來講,能夠經過組合class名稱的方式,把須要切換的樣式提取出來:

<template>
  <div>
    <text class="common {{className}}" onclick="toggle">Hello Weex</text>
  </div>
</template>

<style>
.common {
  font-size: 40px;
}

.normal {
  color: black;
}

.hightlight {
  color: red;
}
</style>

元素上的屬性定義

Weex擁有一套相似前端開發習慣的DSL,HTML和CSS部分也都會遵循W3C的標準。其中元素上的屬性定義,對於非前端同窗來講會有不少誤區,這裏務必要說明下。

屬性名必須所有小寫,可使用鏈接符-。
屬性值,儘可能保證是原始類型,即number/string/boolean/undefined/null。對象類型的值通常用於大數據量的數據綁定。
一些HTML文章裏會推薦在屬性上用data-xxx的方式,這裏並不須要特地加data前綴,由於Weex的js中並無dataset的API可供調用。

搞定子組件的數據綁定

趁熱打鐵,來講下子組件的數據綁定。由於數據綁定也是經過屬性來定義的,因此首先要遵循上一段所說的規則。

綁定數據經過屬性來定義,不只須要在使用的元素上指定屬性並綁定父組件中的數據,也要在子組件的data中指定對應的鍵,而且元素上的屬性和子組件中的鍵名的對應規則是:若是屬性中有鏈接符,則鍵名爲去掉鏈接符後的駝峯寫法,不然所有以小寫命名。
若是僅僅須要給子組件傳遞數據,而其中的數據結構對父組件是透明的,那麼建議直接使用一個屬性來映射;若是,屬性是子組件的一些功能(且數量小於等於5個),則能夠獨立開來(基本上和API行爲的設計原則差很少),例如:

<we-element name="sub1">
  <template>
    <div><text>sub1</text></div>
  </template>

  <script>
    module.exports = {
      data: {
        aMtopData: {}
      }
    }
  </script>
</we-element>

<we-element name="sub2">
  <template>
    <div><text>sub2</text></div>
  </template>

  <script>
    module.exports = {
      data: {
        option1: '',
        option2: ''
      }
    }
  </script>
</we-element>

<template>
    <div>
      <sub1 a-mtop-data="{{mtopdata}}"></sub1>
      <sub2 option1="{{options.op1}}" options2="{{options.op2}}"></sub2>
    </div>
</template>

<script>
  module.exports = {
    data: {
      options: {
        op1: 'op1',
        op2: 'op2'
      },
      mtopdata: {}
    },
    created: function() {
      var self = this
      this.$sendMtop({...}, function(r) {
        self.mtopdata = r.data
      })
    }
  }
</script>

遍歷長列表

在咱們各種大型運營活動的頁面中,你們對樓層/坑位這些詞應該不陌生。而這些名詞的界面,基本都要靠循環列表來完成。而循環列表的性能又是整個運營頁面的關鍵。因此在遍歷這樣的列表或者數組的時候,就須要一些技巧。

一般來講,由於存在樓層的概念,而樓層裏又是多個坑位,坑位又常常是雙列寶貝,眼瞅着這得用個三重循環才能搞定。不過實際上,雙列寶貝能夠優化成不使用循環的結構。固然了,前端的童靴們必定要對着大家的服務端童靴保持堅決立場,要求得到清晰且正確的數據結構,確保前端不須要對數據結構作二次處理。

一般的數據結構和對應的模板通常是這樣的:

<template>
  <div onclick="update">
    <div class="tabheader">
      <div repeat="{{headers}}" track-by="name" append="tree">
        <text>{{name}}</text>
      </div>
    </div>
    <div class="floor" repeat="{{floor in floors}}" track-by="floorId">
      <div class="items" repeat="{{items in floor.items}}" track-by="lineId" append="tree">
        <text>{{items.list[0].name}}</text>
        <text>{{items.list[1].name}}</text>
      </div>
    </div>
  </div>
</template>

<style>
  .tabheader {
    flex-direction: row;
  }
  .items {
    flex-direction: row;
  }
</style>

<script>
module.exports = {
  data: {
    floors: [
      {
        floorId: 1,
        name: 'f1', 
        items:[
          {lineId: 1, list: [{itemId:1, name: 'i1'}, {itemId:2, name: 'i2'}]},
          {lineId: 2, list: [{itemId:3, name: 'i3'}, {itemId:4, name: 'i4'}]}
        ]
      }
    ]
  },
  computed: {
    headers: function() {
      return this.floors.map(function(v) {
        return {name: v.name}
      })
    }
  },
  methods: {
    update: function() {
      this.floors[0].items.push(
        {lineId: 3, list: [{itemId:5, name: 'i5'}, {itemId:6, name: 'i6'}]},
        {lineId: 4, list: [{itemId:7, name: 'i7'}, {itemId:8, name: 'i8'}]} 
      )
      this.floors.push({
        floorId: 2,
        name: 'f2',
        items: [
          {lineId: 5, list: [{itemId:9, name: 'i9'}, {itemId:10, name: 'i10'}]},
          {lineId: 6, list: [{itemId:11, name: 'i11'}, {itemId:12, name: 'i12'}]}
        ]
      })
    }
  }
}
</script>

其中比較常見的數據結構問題,好比items只是一個一維數組。若是能在服務端就處理好items的多維數組問題,那麼前端的效率會高不少。

再仔細剖析其中的模板:

<div class="tabheader" repeat="{{headers}}">

...

  computed: {
    headers: function() {
      return this.floors.map(function(v) {
        return {name: v.name}
      })
    }
  }

這裏綁定了一個computed特性的數據。當某類數據不太適合展現的時候,推薦能夠用computed的方式來達到數據預處理的目的,而不是在created中吭哧吭哧的算一份新的數據結構出來。

...
    <div repeat="{{headers}}" track-by="name" append="tree">
...

<div class="floor" repeat="{{floor in floors}}" track-by="floorId">
      <div class="items" repeat="{{items in floor.items}}" track-by="lineId">

在這三個repeat中都用了track-by。它的特色是,能夠記錄數組中某個項的一個主鍵,並在以後的更新中複用這個特定的項,而不是重構整個數組。例如

樓層1增長兩行坑位

this.floors[0].items.push(
  {lineId: 3, list: [{itemId:5, name: 'i5'}, {itemId:6, name: 'i6'}]},
  {lineId: 4, list: [{itemId:7, name: 'i7'}, {itemId:8, name: 'i8'}]} 
)
增長一個樓層

this.floors.push({
  floorId: 2,
  name: 'f2',
  items: [
    {lineId: 5, list: [{itemId:9, name: 'i9'}, {itemId:10, name: 'i10'}]},
    {lineId: 6, list: [{itemId:11, name: 'i11'}, {itemId:12, name: 'i12'}]}
  ]
})

若是沒有設置track-by,那麼Weex會重構整個數組,致使元素被刪除後又從新添加。而添加了id做爲track-by的主鍵後,id相同的元素會經過移動的方式來優化操做。

優化長列表

長列表相信你們都作過。Weex中,對列表的優化已經很是接近原生系統的列表了,這個要歸功於咱們的Native團隊。但即便有了性能不錯的列表,對於首屏的渲染仍是有追求的。

在Weex中,要作無盡列表其實很是簡單,由於在list和scroll的元素上,已經實現了onloadmore事件,這個事件會在滾動觸底(或者離底部必定的距離)時觸發,因此這樣看起來,作無盡列表變得很是容易。不過,這樣的無盡列表體驗絕對算不上極致。這個時候,能夠藉助loading這個組件,並配合onloading事件,來展示更加出色的無盡列表。

<template>
  <list>
    <cell repeat="{{v in items}}" track-by="id">
      <text>{{v.name}}</text>
    </cell>
    <loading class="loading" onLoading="loadingHandler">
      <text>{{loadingText}}</text>
    </loading>
  </list>
</template>

<script>
module.exports = {
  data: {
    index: 0,
    size: 50,
    count: 10,
    loadingText: '加載更多...',
    items: []
  },
  created: function() {
    this.addPage()
  },
  methods: {
    addPage: function() {
      for (var i = 0; i < this.size; i++) {
        var id = this.index * this.size + i
        this.items.push({id: id, name: 'item-' + id})
      }
      this.index++
    },
    loadingHandler: function() {
      if (this.index === this.count) {
        this.loadingText = '沒有更多了'
      } else {
        this.addPage()
      }
    }
  }
}
</script>

設計優秀的Weex組件

在Weex原生功能愈來愈豐富的前提下,開發者能夠設計出各種符合業務需求的UI組件,這些UI組件基本能夠遵循標準的模塊化開發,已達到複用和高度定製的目的。

用腳手架來初始化Weex組件的倉庫再合適不過了。
Weex是一種數據驅動的設計框架,組件並非經過API來暴露行爲,而是經過數據綁定來給組件設置行爲。
組件的通訊,能夠經過$dispath/$broadcast來完成,不過這得付出一點點性能的代價。而在父組件拿到直接子組件的對象後,其實能夠經過$on/$emit來減小性能的開銷。
若是組件須要高度定製UI,能夠考慮使用content/slot標籤,具體能夠參考下wxc-marquee。
藉由腳手架初始化Weex組件工程,能夠輕鬆發佈到npm/tnpm,而且開發者在經過npm install安裝後,能夠輕鬆的以require方式來引入這些組件。

調試代碼(查看日誌)

Weex將來會接入Chrome Dev-tools,甚至Debugger for IDE,這些均可以小小期待下的。而當下能夠經過輸出日誌的原始方式來調試。

在最開始的利器一章中,我已經讓你們安裝了weex-toolkit,並擁有了weex命令。那麼如今要用它來開啓調試的大門:

weex --debugger
或者在腳手架工程中運行:

npm run debugger
這個時候會輸出一段本地的ip地址,在瀏覽器裏輸入這個地址,會展現一個二維碼。用手淘debug包或Playground掃碼以後,就開啓了輸出日誌模式。

clipboard.png

在這個界面中,你能夠經過選擇設備的日誌級別,以及展現的輸出級別來找到你想要的日誌。同時在代碼中,能夠經過console.log/debug/info/warn/debug來輸出相應級別的日誌。

在日誌debug級別中,以[js framework]開頭的,即是js-framework的解析操做。在日誌verbose級別中,以Calling JS和Calling Native開頭的,就是js和native互相通訊的操做。

頁面間通訊

頁面跳轉是經過指定下一個頁面的url,而後經過openurl或者push的方式來跳轉

獲取url的方式能夠經過下面這段JS代碼

function getAppBaseUrl(self) {
    var dir ='examples'
    var url = self.$getConfig().bundleUrl;
    var bundleUrl = url;
    bundleUrl = new String(bundleUrl);

    var nativeBase;
    var isAndroidAssets = bundleUrl.indexOf('file://assets/') >= 0;

    var isiOSAssets = bundleUrl.indexOf('file:///') >= 0;
    if (isAndroidAssets) {
      nativeBase = 'file://assets/';
    }
    else if (isiOSAssets) {
      nativeBase = bundleUrl.substring(0, bundleUrl.lastIndexOf('/') + 1);
    }
    else {
      var host = 'localhost:12580';
      var matches = /\/\/([^\/]+?)\//.exec(self.$getConfig().bundleUrl);
      if (matches && matches.length >= 2) {
        host = matches[1];
      }
      nativeBase = 'http://' + host + '/' + dir + '/build/';
    }
    var h5Base = './index.html?page=./' + dir + '/build/';
    //Native端
    var base = nativeBase;
    //H5端
    if (typeof window === 'object') {
      base = h5Base;
    }
    return base
}

第六篇 導航、頁面跳轉、stream、webview

頁面通訊有兩種方式

1.經過 url 參數傳遞。

/**
 * 獲取URL參數
 */
getUrlParam: function (key) {
    var t = this.$getConfig().bundleUrl;
    var reg = new RegExp('[?|&]' + key + '=([^&]+)');
    var match = t.match(reg);
    return match && match[1];
}

2.經過 localStorage 數據存儲。

若是是組件間通訊不是頁面通訊,則參考:組件之間通訊 - (Communicate Between Components)

weex cheatsheet

https://github.com/alibaba/we...

相關文章
相關標籤/搜索