uniapp如何使用renderjs通信

说明:本文介绍uniapp如何使用renderjs通信:逻辑层和视图层通信,html模板中的组件如何通信,子组件更新数据父组件如何触发renderjs方法。

renderjs介绍

先介绍一下renderjs: renderjs是一个运行在视图层的js。
官方介绍renderjs的主要作用有2个:

  1. 大幅降低逻辑层和视图层的通讯损耗,提供高性能视图交互能力

  2. 在视图层操作dom,运行 for web 的 js库。

  3. 仅支持在APP(vue页面)H5页面使用。nvue无法使用。

  4. 可以在renderjs使用mars3d或者openlayers库

解释一下:因为uniapp的逻辑层和视图层是分离的,所以我们无法在vue代码中获取到这个视图层的document。这种机制就造成了逻辑层和视图层的通信阻塞。renderjs运行在视图层,直接就可以操作dom元素了。

renderjs基础使用方法

  • 需要在script标签上命名一个module,直接在标签上使用。

  • lang=renderjs是必须要写的

  • renderjs里面是可以直接获取到document的

<template>
  <view>
   <button @click='test.fn'>按钮</button>
  </view>
</template>

<script module="test">
export default {
  methods: {
   fn() {
    console.log('我是fn');
    console.log('doc', document)
   }
  },
}
</script>

image.png

视图层和逻辑层

  • 视图层:视图层是我们标记上renderjs的script标签
    <script module="test"></script>

  • 逻辑层: 是我们正常的script标签
    <script></script>

视图层发消息给逻辑层

  • 通过this.$ownerInstance.callMethod方法可以调用我们逻辑层里面的方法,也可以传参。

<template>
  <view>
   <button @click='test.fn'>按钮</button>
  </view>
</template>

// 逻辑层
<script>
export default {
  methods: {
   receiveRenderjsFn(bool) {
    console.log('receiveRenderjsFn参数', bool);
   }
  },
}
</script>

// 视图层
<script module="test">
export default {
  methods: {
   fn() {
    this.$ownerInstance.callMethod('receiveRenderjsFn', true)
   }
  },
}
</script>

逻辑层发消息给视图层

逻辑层如何发消息给视图层呢?答案是发不了。
那咋办,需求不做了是吧,容俺在想想。

答案是: 通过html模板通过绑定prop和change:prop,我们的prop可以绑定在逻辑层的data数据里面,change:prop绑定renderjs里面的方法,当prop发生变化后就会自动触发change:prop的方法,而这个方法就绑定在renderjs里面的函数,参数则是prop。
(emm,上面的说法确实有点绕,举个栗子吧)

  1. 我们在view这个标签绑定了prop和:change:prop, prop绑定在逻辑层,:change:prop的函数绑定在视图层。:prop='num'相当于监听了num(可以比作watch,而:change:prop可以比作watch里面执行的方法)

  2. 当我们点击按钮,触发add添加方法之后,num就+1,然后就会触发test里面的onChange方法

  3. onChange方法里面有四个参数,新值、旧值、ownerInstance、instance

  4. 新值就是这个逻辑层的num

<template>
  <view>
   <view :prop='num' :change:prop='test.onChange'>
    {{num}}
   </view>
   <button @click='add'>按钮</button>
  </view>
</template>
// 逻辑层
<script>
export default {
  data() {
   return {
    num: 1,
   }
  },
  methods: {
   add() {
    this.num++
   }
  }
}
</script>
// 视图层
<script module="test">
export default {
  methods: {
   onChange(newValue, oldValue, ownerInstance, instance) {
    // ownerInstance和this.$ownerInstance一样,可用来向service层通信
    // instance和ownerInstance的区别是
    // instance.$el指向的是触发事件的那个节点
    // ownerInstance.$el指向当前vue文件中的根节点
    console.log('newValue',newValue);
    console.log('oldValue',oldValue);
    console.log('ownerInstance',ownerInstance);
    console.log('instance',instance);
   }
  },
}
</script>

补充:不一定是写:prop和:change:prop,也可以是:test和:change:test, 只要名字相同就行了。

<view :test='obj' :change:test='test.onChange'>
  <childVue :prop.sync='obj'></childVue>
</view>

某些情况下可能不会触发onChange,下面来介绍如何处理

当传输给子组件,参数是对象数组的时候,嵌套多层的结构时,无法监听到深层发生变化,就不会触发onChange。
index.vue

<template>
  <view>
  <childVue :prop.sync='obj' :change:prop='test.onChange'></childVue>
  </view>
</template>

<script>
import childVue from './child.vue';
export default {
  components:{childVue},
  data() {
   return {
    obj: {
     list: [
      {
       name: '11',
      },
      {
       name: '22',
      }
     ]
    }
   }
  },
}
</script>

<script module="test">
export default {
  methods: {
   onChange(newValue, oldValue, ownerInstance, instance) {
    console.log('newValue',newValue);
    console.log('oldValue',oldValue);
    console.log('ownerInstance',ownerInstance);
    console.log('instance',instance);
   }
  },
}
</script>

child.vue

<template>
  <view>
   子组件
   <button @click='changeObj'>子组件按钮</button>
  </view>
</template>

<script>
export default {
  props: {
   prop: {
    type: Object,
   }
  },
  methods: {
   changeObj() {
    this.$emit('update:prop', {
     list: [
      {
       name: '333',
      },
      {
       name: '2442',
      }
     ]
    })
   }
  }
}
</script>    }   } } </script>

上面的例子我们传入参数给子组件,然后为了在子组件更新参数给父组件,父组件接受到新的参数来触发onChange事件,但是确没有触发,因为嵌套了太多层,无法监听到,所以我们用另外的方法解决:
需要改变的部分

  • 在父组件外面嵌套一层view标签,作为一个容器来让renderjs监听到变化,至于原因:可能是避免:prop这种写法,让子组件以为是传参数,而不是renderjs监听语法,所以我们嵌套一个外部容器view来作为监听

  • 在child.vue组件更新时候,给对象添加一个当前时间,为了让renderjs监听到内容发生变化

index.vue

<template>
 <view :prop='obj' :change:prop='test.onChange'>
  <childVue :prop.sync='obj'></childVue>
 </view>
</template>

child.vue

methods: {
  changeObj() {
   this.$emit('update:prop', {
   // 添加内容
    now: Date.now(),
    list: [
     {
      name: '333',
     },
     {
      name: '2442',
     }
    ]
   })
  }
}

总体代码
index.vue

<template>
  <view :prop='obj' :change:prop='test.onChange'>
    <childVue :prop.sync='obj'></childVue>
  </view>
</template>


<script>
import childVue from './child.vue';
export default {
  components:{childVue},
  data() {
   return {
    obj: {
     list: [
      {
       name: '11',
      },
      {
       name: '22',
      }
     ]
    }
   }
  },
}
</script>

<script module="test">
export default {
  methods: {
   onChange(newValue, oldValue, ownerInstance, instance) {
    console.log('newValue',newValue);
    console.log('oldValue',oldValue);
    console.log('ownerInstance',ownerInstance);
    console.log('instance',instance);
   }
  },
}
</script>

child.vue

<template>
  <view>
   子组件
   <button @click='changeObj'>子组件按钮</button>
  </view>
</template>

<script>
export default {
  props: {
   prop: {
    type: Object,
   }
  },
  methods: {
   changeObj() {
    this.$emit('update:prop', {
     now: Date.now(),
     list: [
      {
       name: '333',
      },
      {
       name: '2442',
      }
     ]
    })
   }
  }
}
</script>