本篇文章帶大家了解一下renderjs,通過在uniapp中使用better-scroll,聊聊renderjs的一些細(xì)節(jié),希望對大家有所幫助!

包含內(nèi)容:
使用renderjs在app端獲取dom
renderjs和service層之間的通信
renderjs中如何接收到service層中的自定義id(重點(diǎn),官方文檔沒有的)
一、renderjs
1.1 renderjs的概念
-
官方文檔
-
運(yùn)行在視圖層的js,只支持app-vue和h5(簡單來說就是開了另外一條線程)
1.2 renderjs的作用
-
大幅降低邏輯層和視圖層的通訊損耗,提供高性能視圖交互能力(減少通訊損耗提升性能,例如一些手勢或canvas動(dòng)畫的場景)
-
在視圖層操作dom,運(yùn)行for web的js庫(可以操作dom,意味著擁有window、document等這些全局變量,在app-vue的service層沒有這些)
1.3 renderjs的使用
- 官方文檔給出的示例 (內(nèi)容較為詳細(xì),也可以看我接下來的簡要概括)
- 在原先的script標(biāo)簽的同級(jí)新增一個(gè)script,設(shè)置
lang=renderjs,module=(值任意,相當(dāng)于命名空間,之后會(huì)根據(jù)這個(gè)名字調(diào)用其中的方法) - 新script標(biāo)簽內(nèi)的結(jié)構(gòu)和之前的幾乎一致,有幾點(diǎn)不同的需要注意:
- 生命周期不和uniapp相同,而是和vue相同,
onLoad應(yīng)該寫成原生vue的created - 官方文檔好像說了renderjs中無法使用uni這個(gè)全局變量,具體哪個(gè)地方忘了。實(shí)測結(jié)果是:部分可以。例如
uni.upx2px是可以用的,uni.request不可以,所以使用uni全局變量之前先輸出看一下有沒有
- 生命周期不和uniapp相同,而是和vue相同,
- 在template中使用一開始給renderjs的命名加
.的方式調(diào)用其中的方法
<template> <view> <button @tap="test.handleClick">點(diǎn)擊</button> </view> </template> <script> export default { // 原先的script,這里被稱為service層 } </script> <script module="test"> export default { data() { return {} }, methods: { handleClick(event, ownerInstance) { // event是事件對象 // ownerInstance和this.$ownerInstance是一樣的,用來調(diào)用service層的方法 console.log('點(diǎn)擊了按鈕') } }, created() { console.log('renderjs初始化完畢') } } </script>
二、renderjs和service層的通信
具體分為三部分:
在template中通過用戶手動(dòng)操作觸發(fā)事件
在service層中調(diào)用方法
在renderjs中調(diào)用方法
從renderjs到service層:通過
this.$ownerInstance.callMethod()方法可以調(diào)用service中的方法,第一個(gè)參數(shù)是方法名,第二個(gè)參數(shù)是傳過去的參數(shù)
<template> <view> <button @tap="test.onClick">點(diǎn)擊</button> </view> </template> <script> export default { methods: { acceptDataFromRenderjs(data) { console.log('從renderjs中接收到的數(shù)據(jù)', data) } } } </script> <script module="test"> export default { data() { return {} }, methods: { onClick(event, ownerInstance) { ownerInstance.callMethod('acceptDataFromRenderjs', { content: '測試文字' }) // 或this.$ownerInstance.callMethod('acceptDataFromRenderjs', { content: '測試文字' }) // 需要注意的是:只有通過在template中用戶手動(dòng)操作觸發(fā)renderjs的方法參數(shù)是這兩個(gè):event, ownerInstance; // 通過其他方法觸發(fā)的函數(shù)參數(shù)不一樣,后面會(huì)說 } } } </script>
從service層到renderjs:
這里就需要template了,首先在template中綁定一個(gè)service中定義的值,然后在同樣的位置增加
:change:(屬性名)=(觸發(fā)的方法)來實(shí)現(xiàn)通信。簡單來說就是service負(fù)責(zé)數(shù)據(jù)的更改,通過template監(jiān)聽數(shù)據(jù)的變化來通知renderjs
<template> <view> // prop是個(gè)名字,可以隨意改,注意:change:[name]這兩個(gè)名字需要相同就行了 <text :prop="options" :change:prop="test.onChange">無內(nèi)容</text> <button @tap="changeOptionFn">點(diǎn)擊修改options</button> </view> </template> <script> export default { data() { return { options: { // 這里存放準(zhǔn)備傳遞給renderjs的數(shù)據(jù) token: null, num: 1 } } }, methods: { changeOptionFn() { this.options = { // 這個(gè)地方我用時(shí)間戳來修改token,用于觸發(fā)change,實(shí)際需要傳遞的數(shù)據(jù)是num token: Date.now(), num: Math.random() } } } } </script> <script module="test"> export default { methods: { onChange(newValue, oldValue, ownerInstance, instance) { console.log('service層中的options發(fā)生變化') console.log('新值', newValue) console.log('舊值', oldValue) // ownerInstance和this.$ownerInstance一樣,可用來向service層通信 // instance和ownerInstance的區(qū)別是: // instance.$el指向的是觸發(fā)事件的那個(gè)節(jié)點(diǎn);ownerInstance.$el指向當(dāng)前vue文件中的根節(jié)點(diǎn); // instance的作用目前尚不明確,官方?jīng)]有給出用法 } } } </script>
在上面的例子中,prop初次綁定到節(jié)點(diǎn)時(shí),事件不會(huì)觸發(fā)。
用戶首先通過點(diǎn)擊按鈕觸發(fā)
changeOptionFn事件,函數(shù)中修改了this.options的值。而在
text節(jié)點(diǎn)監(jiān)聽到綁定值發(fā)生了改變就會(huì)觸發(fā)test.onChange,從而實(shí)現(xiàn)service到renderjs的通信上面的例子中有一點(diǎn)需要注意:在
this.options中我定義了一個(gè)token屬性,每次修改時(shí)都將最新的時(shí)間戳賦值給他,這樣就保證了我每一次的點(diǎn)擊都會(huì)使options發(fā)生改變。如果沒有這個(gè)
token的話就會(huì)出現(xiàn)明明修改了options但是并未觸發(fā)onChange的情況。了解js基礎(chǔ)的都知道,修改
options是我是直接重新賦值的,改變了索引,所以即使我num值和原先的相同,他也應(yīng)該做出改變(例如vue中的watch),但是事實(shí)并不是所以可以推測出這個(gè)監(jiān)聽數(shù)據(jù)改變監(jiān)聽的是內(nèi)部的屬性值,只有屬性的增刪改才能觸發(fā)回調(diào)。(如果一開始綁定的就是基礎(chǔ)數(shù)據(jù)類型的話,直接修改就好了)
故,當(dāng)綁定值使用對象的時(shí)候,在對象中增加一個(gè)每次都一定會(huì)變的值,即可保證事件的觸發(fā)(如上例中的
token)補(bǔ)充一點(diǎn):進(jìn)行過prop綁定的值,觸發(fā)過一次監(jiān)聽事件之后,在renderjs中可以直接使用
this.屬性名的方式獲取到例如上面的代碼中,
options改變導(dǎo)致test.onChange觸發(fā)一次之后,在renderjs中可以直接通過this.options獲取到值
三、renderjs中如何接收到service層中的自定義id
3.1 在renderjs中使用better-scroll
做過app-vue開發(fā)的話應(yīng)該知道在service層中沒有document對象,無法獲取dom節(jié)點(diǎn)。
所以引用一些外部js的時(shí)候,如果初始化的時(shí)候需要傳入一個(gè)選擇器的,那基本就斷定用到了document對象獲取節(jié)點(diǎn)。
這時(shí)候就需要用到renderjs了,首先看一個(gè)
better-scroll的示例。根據(jù)官方給出的示例做一些修改,我們可以得到以下代碼
<template> <view id="my-scroll"> <view><slot /></view> </view> </template> <script> export default {} </script> <script module="BScroll"> export default { mounted() { // 如果這個(gè)插件支持ESModule的話就不用這么寫,直接import導(dǎo)入就好了 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
重點(diǎn)來了,上面的例子中雖然實(shí)現(xiàn)了效果,但是也出現(xiàn)了一個(gè)問題:id是固定的。
如果我在同一頁面中多次使用該組件,就會(huì)導(dǎo)致出現(xiàn)多個(gè)重復(fù)id,導(dǎo)致無法預(yù)料的錯(cuò)誤。
在官方給出的示例中,包括我研究過的插件市場中的很多項(xiàng)目,都是使用固定的id。
解決的方法就是由外部傳入自定義id或是由內(nèi)部生成隨機(jī)id。那么應(yīng)該如何在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>
在父級(jí)中傳入自定義的bsId,組件接收到之后將其作為元素id。
執(zhí)行順序和之前一樣:在renderjs的
mounted中加載外部js,加載完成后進(jìn)行初始化操作,通過this.$ownerInstance.$vm.bsId獲取到service層中的bsId完成操作同時(shí),bsId也綁定了prop,監(jiān)聽到改變時(shí)會(huì)重新進(jìn)行初始化操作,所以在初始化的方法第一行加入了
this.bs?.destroy(),如果實(shí)例已存在就先銷毀。還記得一開始就說過的renderjs只支持app-vue和h5嗎,這里主要說的是app端,因?yàn)槿绻莌5端的話,是可以在service中直接使用document的,壓根不用這么麻煩。
這里還有一點(diǎn)需要注意的:
:prop="bsId" :change:prop="BScroll.initBScroll"實(shí)測,如果不寫這行代碼,也就是不進(jìn)行綁定prop的操作的話,是無法獲取到
this.$ownerInstance.$vm.bsId。(app端是這樣,h5端不寫這個(gè)也可以,但是h5端壓根也用不著這種方法)
推薦:《uniapp教程》
站長資訊網(wǎng)