WEEX-EROS | 整合並使用 bindingx

Zzzzzz發表於2019-03-02

文章出處: bmfe.github.io/eros-docs/#…

前言

上篇文章 WEEX-EROS | 入門指南 中我們已經從頭執行了 eros 來進行開發 app,很多同學還不是很清楚 eros 到底能幹嘛,這裡在稍微解釋一下:

eros 是基於 weex 的二次封裝 app 開發解決方案,讓我們能用 vue 語法來開發原生 iOS/Android 應用

eros 已經幫助了數家公司的前端開發者開發了自己的原生 iOS/Android 應用,並通過 weex 使其 app 具有 熱釋出 的能力,在 4 月份,eros 會發布外掛系統,模擬器/真機熱重新整理,全新官方 demo ,更全面的熱更新邏輯等,正式穩定下來。

這篇文章中我們主要來介紹:

  • eros 中如何使用 weex-bindingx
  • 其他 weex 純 native 專案中自行封裝 weex-bindingx 來優化 JS Bundle 大小

bindingx 由來

引用官方文件上的介紹:

  • 由於 weex 底層使用的 JS-Native Bridge 具有天然的非同步特性,這使得 JS 和 Native 之間的通訊會有固定的效能損耗,因此在一些複雜的互動場景中,JS 程式碼很難以高幀率執行。舉個例子,如果我們要實現 檢視隨手勢移動 的效果,那麼按照傳統的方式,需要在這個檢視上繫結 touch 或者 pan 事件,當手勢發生時, Native 會將手勢事件通過 Bridge 傳遞給 JS , 這產生了一次 Native 到 JS 的通訊。而 JS 在接收到事件後,需要根據手指移動的偏移量驅動介面變化,這又會產生一次 JS 到 Native 的通訊。與此同時,手勢回撥事件觸發的頻率是非常高的,頻繁的通訊帶來的時間成本很可能導致介面無法在16ms中完成繪製,進而產生卡頓

  • 事實上,不僅僅是在 weex 上存在這種問題, React Native 等框架同樣存在類似的問題。拿 React Native Animated 元件為例,為了實現流暢的動畫效果,這個元件採用了宣告式的API,在 JS 端僅僅定義了輸入與輸出以及具體的 transform 行為,而真正的動畫是通過 Native Driver 在 Native 層執行,這樣就避免了頻繁的通訊。然而,這個方案只能解決一部分問題,如果是有複雜互動操作的場景就不夠用了。另外,宣告式的方式能夠定義的行為非常有限,無法滿足更復雜的互動場景。

使用之前

請熟讀官方文件:

weex native 專案的使用過程中需要注意:

1.傳入的元素需要多取一層 ref 屬性,假設我們給一個元素上面 ref 屬性賦值為 box ,則使用時候按照以下方式才可:

this.$refs.box.ref
複製程式碼

2.如果引用方式為 requireModule(`bindingx`) 這種 weex 引入 module 的寫法,bind 方法按照文件中的方式是無效的,需要做特殊處理,後面會說到。

3.ios 端引入的時候需要做下頁面手勢返回處理 (eros 中擴充了$router gesBack 屬性),防止右滑時候出現手勢衝突

eros 中使用

如果下載官方的 npm 包 weex-bindingx 直接使用時沒有問題的,但 eros 是主要 focus native,引入 weex-bindingx 在打出來一個簡單的 JS Bundle 就要 190+ kb,很明顯有些過於臃腫,在互動複雜的頁面,打出來的 JS Bundle 的大小就更加不可控。

進入 weex-bindingx 原始碼發現,如果 native 直接使用 requireModule(`bindingx`) 引入,是需要改變 expression 為物件,把填寫的表示式值傳入物件的 origin 屬性,然後傳入一個的 transformed 屬性,這個屬性是很長的 CallNative 指令字串,可以通過下載 npm 包 bindindx-parser 來自動生成。

於是 eros 做了很簡單的二次封裝,整合進 widget 中,直接通過 this 呼叫即可。

下面我們來實現一個官方的 demo:

WEEX-EROS | 整合並使用 bindingx

首先編寫模板和樣式:

<template>
  <div class="container" >
    <div class="border">
      <div :ref="`my`" class="box" @touchstart="ontouchstart">
        <div class="head">
          <div class="avatar"></div>
          <text class="username">HACKER</text>
        </div>
        <div class="content">
          <text class="desc">Google announced a new version of Nearby Connections for fully offline.high bandwidth peer to peer device communications.</text>
        </div>
        <div class="footer">
          <text class="action">SHARE</text>
          <text class="action" style="color:#7C4DFF">EXPLORE</text>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
  .container {
    flex: 1;
    background-color:#eeeeee;
    
  }
  .border{
    height:1000px;
    padding-left:35px;
    padding-right:35px;
    padding-top:100px;
  }
  .box {
    width: 680px;
    height: 450px;
    background-color:#651FFF;
  }
  .head {
    background-color:#651FFF;
    width:680px;
    height:120px;
    flex-direction:row;
    align-items:center;
  }
  .content{
    width:680px;
    height:240px;
    background-color:#651FFF;
    padding-left:24px;
    padding-top:24px;
    padding-right:24px;
  }
  .footer {
    width:680px;
    height:90px;
    background-color: #fff;
    align-items:center;
    justify-content:flex-end;
    padding-right:25px;
    flex-direction:row
  }
  .action {
    font-size:35;
    padding-right:20px;
  }
  
  .desc {
    font-size:32;
    color:#fff;
    padding-left:24px;
  }
  
  
  .avatar {
    width:96px;
    height:96px;
    border-radius:48px;
    background-color:#CDDC39;
    margin-left:36px;
    margin-right:48px;
  }
  
  .username {
    color:#fff;
    font-size:32;
  }
  
</style>

複製程式碼

編寫 js 邏輯:

<script>
  export default {
    data () {
      return {
        x: 0,
        isInAnimation: false,
        opacity:1,
        gesToken:0
      }
    },
    methods: {
      ontouchstart (event) {
        // 如果在執行動畫,就不觸發
        if(this.isInAnimation) return 
        // 解綁動畫
        if(this.gesToken != 0) {
          this.$bindingx.unbind({
            eventType:`pan`,
            token:this.gesToken
          })
          this.gesToken = 0
          // return
        }
        // 找到元素 注意多了一個.ref
        let boxRef = this.$refs.box.ref
        let gesTokenObj = this.$bindingx.bind({
          anchor:boxRef,
          eventType:`pan`,
          props: [
              {element:boxRef, property:`transform.translateX`,expression:"x+0"}, // 這裡是表示式,需要有運算子號,不能只寫x
              {element:boxRef, property:`opacity`,expression: "1-abs(x)/600"}]   // 我們這裡期望 opacity 無論在拖動盒子向左向右的時候 都有漸隱 所有取絕對值 移動到絕對 600px 時候為全隱
        }, (e) => {
          if(e.state === `end`) {
            // 拖動結束事件 記錄當前座標 和 透明度
             this.x += e.deltaX
             this.opacity = 1-Math.abs(e.deltaX)/600
            //  開始進行下一步 回彈還是滑出的 動畫
             this.bindTiming()
          }
        })
        // 記錄下取消的token 多次 ontouchstart 時要帶著 token 把上次的解綁
        this.gesToken = gesTokenObj.token
      },
      dismissCallback() {
        this.$notice.toast({
            message: `您已經刪除了小卡片。`
        })
      },
      bindTiming() {
        // 開始執行動畫
        this.isInAnimation = true

	      let boxRef = this.$refs.box.ref
        // 改變的 x 座標,最終的 x 座標,最終的透明值,位移 x 原點的表示式
        let changed_x, final_x, final_opacity, translate_x_origin
        // 通過一個變數來判斷是否已經滑出
        let shouldDismiss = false

        // 生成表示式邏輯
        if(this.x>=-750/2 && this.x<=750/2) {
        // weex 設定寬度預設都是750px 往左拖動或者往右拖動盒子一半以內時
          shouldDismiss = false   // 標記為不消失
          final_x = 0             // 回到原點
          changed_x = 0-this.x    // 計算出需要位置的值
          final_opacity = 1       // 透明度變回 1
          translate_x_origin = `easeOutElastic(t,${this.x},${changed_x},1000)` // 運動曲線為easeOutElastic 生成位移到原點的表示式 1s內執行
        } else if(this.x < -750/2) {
          // 往左拖動盒子超過一半時
          shouldDismiss = true   // 標記為消失
          final_x = -750         // 完全把盒子位移到左邊屏外面
          changed_x = -750-this.x// 計算出需要位置的值
          final_opacity = 0     // 透明度變回 0
          translate_x_origin = `easeOutExpo(t,${this.x},${changed_x},1000)` // 運動曲線為easeOutExpo 生成位移到 -750px 表示式 1s內執行
        } else {
          // 往右拖動盒子超過一半時
          shouldDismiss = true   // 標記為消失
          final_x = 750          // 完全把盒子位移到右邊邊屏外面
          changed_x = 750-this.x // 計算出需要位置的值
          final_opacity = 0      // 透明度變回 0
          translate_x_origin = `easeOutExpo(t,${this.x},${changed_x},1000)` // 運動曲線為easeOutExpo 生成位移到 750px 表示式 1s內執行
        }
       
      //  運動曲線為linear 計算出透明度表示式 1s內執行
       let opacity_origin = `linear(t,${this.opacity},${final_opacity - this.opacity},1000)`
	     let result = this.$bindingx.bind({
          eventType:`timing`,       // 結束的時候是沒有任何監聽的 用 timing 來做定時的動畫
          exitExpression:"t>1000",  // 當時間超過 10000ms 結束動畫
          props: [
              {element:boxRef, property:`transform.translateX`,expression:translate_x_origin},
              {element:boxRef, property:`opacity`,expression:opacity_origin}
            ]
          
        },(e) => {
            if(e.state === `end` || e.state === `exit`) {
              // reset x
              this.x = final_x
              this.opacity= final_opacity
              this.isInAnimation = false
              
              shouldDismiss && this.dismissCallback()
                
            }
        })
      }
    }
  }
</script>
複製程式碼

可以看到,只需要呼叫 $bindingx 即可。

weex native 中使用

在非 eros 的 weex native 專案中,我們可以重寫 bind 方法來保持與官方使用一致,下載 npm 包, lodashbindingx-parser 來進行改造:

// bindingx.js
import { parse } from `bindingx-parser`
import _cloneDeep from `lodash/cloneDeep`

const WeexBinding = weex.requireModule(`bindingx`)
const BindingxFunction = WeexBinding.bind

let _WeexBinding = _cloneDeep(WeexBinding)

// 重寫 bind 方法
_WeexBinding.bind = (options, callback) => {
    if (!options) {
        throw new Error(`should pass options for binding`)
    }

    options.exitExpression = formatExpression(options.exitExpression)

    if (options.props) {
        options.props.forEach((prop) => {
            prop.expression = formatExpression(prop.expression)
        })
    }

    return BindingxFunction(options, options && options.eventType === `timing` ? fixCallback(callback) : callback)
}

module.export =  _WeexBinding
複製程式碼

在業務中使用:

var bindingx = require(`bindingx`)
複製程式碼

這樣在打包之後非壓縮狀態下體積能減少到 34kb 左右。而 eros js bundle 的大小會更小,已經把這部分重寫邏輯放入了 widget,通過 appboard.js 來內建到客戶端執行。

現在,盡情使用 bindingx 吧!

相關文章

相關文章