renderjs有什麼用?聊聊uniapp中用renderjs的一些細節

2022-01-07 22:00:46
本篇文章帶大家瞭解一下renderjs,通過在uniapp中使用better-scroll,聊聊renderjs的一些細節,希望對大家有所幫助!

包含內容:

  • 使用renderjs在app端獲取dom

  • renderjs和service層之間的通訊

  • renderjs中如何接收到service層中的自定義id(重點,官方檔案沒有的)

一、renderjs

1.1 renderjs的概念

  • 官方檔案

  • 執行在檢視層的js,只支援app-vue和h5(簡單來說就是開了另外一條執行緒)

1.2 renderjs的作用

  • 大幅降低邏輯層和檢視層的通訊損耗,提供高效能檢視互動能力(減少通訊損耗提升效能,例如一些手勢或canvas動畫的場景)

  • 在檢視層操作dom,執行for web的js庫(可以操作dom,意味著擁有window、document等這些全域性變數,在app-vue的service層沒有這些)

1.3 renderjs的使用

  • 官方檔案給出的範例 (內容較為詳細,也可以看我接下來的簡要概括)
  • 在原先的script標籤的同級新增一個script,設定lang=renderjsmodule=(值任意,相當於名稱空間,之後會根據這個名字呼叫其中的方法)
  • 新script標籤內的結構和之前的幾乎一致,有幾點不同的需要注意:
    1. 生命週期不和uniapp相同,而是和vue相同,onLoad應該寫成原生vue的created
    2. 官方檔案好像說了renderjs中無法使用uni這個全域性變數,具體哪個地方忘了。實測結果是:部分可以。例如uni.upx2px是可以用的,uni.request不可以,所以使用uni全域性變數之前先輸出看一下有沒有
  • 在template中使用一開始給renderjs的命名加.的方式呼叫其中的方法
<template>
  <view>
    <button @tap="test.handleClick">點選</button>
  </view>
</template>

<script>
  export default {
    // 原先的script,這裡被稱為service層
  }
</script>

<script module="test">
  export default {
    data() {
      return {}
    },
    methods: {
      handleClick(event, ownerInstance) {
        // event是事件物件
        // ownerInstance和this.$ownerInstance是一樣的,用來呼叫service層的方法
        console.log('點選了按鈕')
      }
    },
    created() {
      console.log('renderjs初始化完畢')
    }
  }
</script>

二、renderjs和service層的通訊

具體分為三部分:

  • 在template中通過使用者手動操作觸發事件

  • 在service層中呼叫方法

  • 在renderjs中呼叫方法

從renderjs到service層:通過this.$ownerInstance.callMethod()方法可以呼叫service中的方法,第一個引數是方法名,第二個引數是傳過去的引數

<template>
  <view>
    <button @tap="test.onClick">點選</button>
  </view>
</template>

<script>
  export default {
    methods: {
      acceptDataFromRenderjs(data) {
        console.log('從renderjs中接收到的資料', data)
      }
    }
  }
</script>

<script module="test">
  export default {
    data() {
      return {}
    },
    methods: {
      onClick(event, ownerInstance) {
        ownerInstance.callMethod('acceptDataFromRenderjs', { content: '測試文字' })
        // 或this.$ownerInstance.callMethod('acceptDataFromRenderjs', { content: '測試文字' })
        // 需要注意的是:只有通過在template中使用者手動操作觸發renderjs的方法引數是這兩個:event, ownerInstance;
        // 通過其他方法觸發的函數引數不一樣,後面會說
      }
    }
  }
</script>

從service層到renderjs:

這裡就需要template了,首先在template中繫結一個service中定義的值,然後在同樣的位置增加:change:(屬性名)=(觸發的方法)來實現通訊。

簡單來說就是service負責資料的更改,通過template監聽資料的變化來通知renderjs

<template>
  <view>
    // prop是個名字,可以隨意改,注意:change:[name]這兩個名字需要相同就行了
    <text :prop="options" :change:prop="test.onChange">無內容</text>
    <button @tap="changeOptionFn">點選修改options</button>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        options: {
          // 這裡存放準備傳遞給renderjs的資料
          token: null,
          num: 1
        }
      }
    },
    methods: {
      changeOptionFn() {
        this.options = {
          // 這個地方我用時間戳來修改token,用於觸發change,實際需要傳遞的資料是num
          token: Date.now(),
          num: Math.random()
        }
      }
    }
  }
</script>

<script module="test">
  export default {
    methods: {
      onChange(newValue, oldValue, ownerInstance, instance) {
        console.log('service層中的options發生變化')
        console.log('新值', newValue)
        console.log('舊值', oldValue)
        // ownerInstance和this.$ownerInstance一樣,可用來向service層通訊
        // instance和ownerInstance的區別是:
        // instance.$el指向的是觸發事件的那個節點;ownerInstance.$el指向當前vue檔案中的根節點;
        // instance的作用目前尚不明確,官方沒有給出用法
      }
    }
  }
</script>

在上面的例子中,prop初次繫結到節點時,事件不會觸發。

使用者首先通過點選按鈕觸發changeOptionFn事件,函數中修改了this.options的值。

而在text節點監聽到繫結值發生了改變就會觸發test.onChange,從而實現service到renderjs的通訊

上面的例子中有一點需要注意:在this.options中我定義了一個token屬性,每次修改時都將最新的時間戳賦值給他,這樣就保證了我每一次的點選都會使options發生改變。

如果沒有這個token的話就會出現明明修改了options但是並未觸發onChange的情況。

瞭解js基礎的都知道,修改options是我是直接重新賦值的,改變了索引,所以即使我num值和原先的相同,他也應該做出改變(例如vue中的watch),但是事實並不是

所以可以推測出這個監聽資料改變監聽的是內部的屬性值,只有屬性的增刪改才能觸發回撥。(如果一開始繫結的就是基礎資料型別的話,直接修改就好了)

故,當繫結值使用物件的時候,在物件中增加一個每次都一定會變的值,即可保證事件的觸發(如上例中的token

補充一點:進行過prop繫結的值,觸發過一次監聽事件之後,在renderjs中可以直接使用this.屬性名的方式獲取到

例如上面的程式碼中,options改變導致test.onChange觸發一次之後,在renderjs中可以直接通過this.options獲取到值

三、renderjs中如何接收到service層中的自定義id

3.1 在renderjs中使用better-scroll

做過app-vue開發的話應該知道在service層中沒有document物件,無法獲取dom節點。

所以參照一些外部js的時候,如果初始化的時候需要傳入一個選擇器的,那基本就斷定用到了document物件獲取節點。

這時候就需要用到renderjs了,首先看一個better-scroll的範例。根據官方給出的範例做一些修改,我們可以得到以下程式碼

<template>
  <view id="my-scroll">
    <view><slot /></view>
  </view>
</template>

<script>
  export default {}
</script>

<script module="BScroll">
  export default {
    mounted() {
      // 如果這個外掛支援ESModule的話就不用這麼寫,直接import匯入就好了
      if (typeof window.BScroll == 'function') return this.initBScroll()
      const script = document.createElement('script')
      script.src = 'static/better-scroll.core.min.js'
      script.onload = this.initBScroll
      document.head.appendChild(script)
    },
    methods: {
      initBScroll() {
        this.bs = new BScroll(document.querySelector('#my-scroll'))
      }
    }
  }
</script>

3.2 better-scroll自定義id

重點來了,上面的例子中雖然實現了效果,但是也出現了一個問題:id是固定的。

如果我在同一頁面中多次使用該元件,就會導致出現多個重複id,導致無法預料的錯誤。

在官方給出的範例中,包括我研究過的外掛市場中的很多專案,都是使用固定的id。

解決的方法就是由外部傳入自定義id或是由內部生成隨機id。那麼應該如何在renderjs中如何接收到service層中的自定義id呢

下面我給出的方法算是我自己測試過最有效的方法了,直接看程式碼

<template>
  <view :id="bsId" :prop="bsId" :change:prop="BScroll.initBScroll">
    <view><slot /></view>
  </view>
</template>

<script>
  export default {
    props: {
      bsId: {
        type: String,
        default: 'bs-container'
      }
    }
  }
</script>

<script module="BScroll">
  export default {
    mounted() {
      if (typeof window.BScroll == 'function') return this.initBScroll()
      const script = document.createElement('script')
      script.src = 'static/better-scroll.core.min.js'
      script.onload = this.initBScroll
      document.head.appendChild(script)
    },
    methods: {
      initBScroll() {
        this.bs?.destroy()
        this.bs = new BScroll(document.querySelector(`#${this.$ownerInstance.$vm.bsId}`))
      }
    }
  }
</script>

在父級中傳入自定義的bsId,元件接收到之後將其作為元素id。

執行順序和之前一樣:在renderjs的mounted中載入外部js,載入完成後進行初始化操作,通過this.$ownerInstance.$vm.bsId獲取到service層中的bsId完成操作

同時,bsId也繫結了prop,監聽到改變時會重新進行初始化操作,所以在初始化的方法第一行加入了this.bs?.destroy(),如果範例已存在就先銷燬。

還記得一開始就說過的renderjs只支援app-vue和h5嗎,這裡主要說的是app端,因為如果是h5端的話,是可以在service中直接使用document的,壓根不用這麼麻煩。

這裡還有一點需要注意的::prop="bsId" :change:prop="BScroll.initBScroll"

實測,如果不寫這行程式碼,也就是不進行繫結prop的操作的話,是無法獲取到this.$ownerInstance.$vm.bsId。(app端是這樣,h5端不寫這個也可以,但是h5端壓根也用不著這種方法)

推薦:《》

以上就是renderjs有什麼用?聊聊uniapp中用renderjs的一些細節的詳細內容,更多請關注TW511.COM其它相關文章!