dcloudio / uni-app

A cross-platform framework using Vue.js
https://uniapp.dcloud.io
Apache License 2.0
40.19k stars 3.64k forks source link

小程序自定义组件slot内使用的组件无法正常通过ref获取组件实例 #3615

Closed amihhs closed 2 years ago

amihhs commented 2 years ago

问题描述 [问题描述:尽可能简洁清晰地把问题描述清楚] 在组件内在v-if标签内使用slot时,组件使用时无法正常通过ref获取填充在slot中组件实例 image

复现步骤

// component
// 在设置了if的时候会出现
<view v-if="props.status">
      <slot name="default"></slot>
</view>
// page 使用的时候
<view v-if="props.status">
      <component  :status="true">
           <selfComponent ref="selfComponent">
      </component >
</view>

示例地址

系统信息:

补充信息 [可选] [根据你的分析,出现这个问题的原因可能在哪里?]

zhongzhifa commented 2 years ago

给你提供一个我的解决方案,

export default {
  methods: {
    /**
     * 获取组件引用
     * @param {String} name 组件名字
     */
    getRef(name) {
      return new Promise((resolve, reject) => {
        return this.getRefMain(name, resolve, reject);
      });
    },
    /**
     * 获取主函数
     * @param {String} name 组件名字
     * @param {Function} resolve 成功
     * @param {Function} reject 失败
     */
    getRefMain(name, resolve, reject) {
      let ref = this.$refs[name];
      if (ref) {
        if (ref.length) ref = ref[0];
        resolve(ref);
      } else {
        setTimeout(() => {
          this.getRefMain(name, resolve, reject);
        }, 50);
      }
    }
  }
};

就是隔50ms,循环获取ref,直到取到值,有一个延时问题,当然你也可以设置一个很大的值去兼容不同手机去获取,比如onReady后1000ms

amihhs commented 2 years ago

给你提供一个我的解决方案,

export default {
  methods: {
    /**
     * 获取组件引用
     * @param {String} name 组件名字
     */
    getRef(name) {
      return new Promise((resolve, reject) => {
        return this.getRefMain(name, resolve, reject);
      });
    },
    /**
     * 获取主函数
     * @param {String} name 组件名字
     * @param {Function} resolve 成功
     * @param {Function} reject 失败
     */
    getRefMain(name, resolve, reject) {
      let ref = this.$refs[name];
      if (ref) {
        if (ref.length) ref = ref[0];
        resolve(ref);
      } else {
        setTimeout(() => {
          this.getRefMain(name, resolve, reject);
        }, 50);
      }
    }
  }
};

就是隔50ms,循环获取ref,直到取到值,有一个延时问题,当然你也可以设置一个很大的值去兼容不同手机去获取,比如onReady后1000ms

确实可以,在普通的script中获取到对应的ref; 但是请教下怎么才能在普通的script中给setup script 赋值, 直接通过this不行

<script setup>

cosnt a = ref('')

</script>

<script>
export default {
  onReady() {
     // 给a赋值
  },
</script>
fxy060608 commented 2 years ago

可以临时替换下node-modules中的 https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vue/dist/vue.runtime.esm.js 文件,稍后会发布新版本

amihhs commented 2 years ago

目前更新后,只有在页面内容有更新后才能获取到对应的实例

fxy060608 commented 2 years ago

https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vue/dist/vue.runtime.esm.js

发测试工程

amihhs commented 2 years ago

https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vue/dist/vue.runtime.esm.js

发测试工程

弄测试工程时无法复现,可能是缓存问题。3.0.0-alpha-3050020220617002 版本正常

amihhs commented 2 years ago

@fxy060608 大佬弄了个可以复现的工程,在页面跳转后会出现demo

fxy060608 commented 2 years ago

@fxy060608 大佬弄了个可以复现的工程,在页面跳转后会出现demo

代码的问题吧,为何要在watch formRef 的时候,设置 immediate 为 true,为true的时候,会立即执行,此时formRef肯定不存在啊,

amihhs commented 2 years ago

@fxy060608 大佬弄了个可以复现的工程,在页面跳转后会出现demo

代码的问题吧,为何要在watch formRef 的时候,设置 immediate 为 true,为true的时候,会立即执行,此时formRef肯定不存在啊,

但是他只在初始化的时候触发了一次,之后就没有更新了,所以就一直是undefined 。checkbox选择后才会触发更新,这个时候formRef才第一次赋值

fxy060608 commented 2 years ago

@fxy060608 大佬弄了个可以复现的工程,在页面跳转后会出现demo

代码的问题吧,为何要在watch formRef 的时候,设置 immediate 为 true,为true的时候,会立即执行,此时formRef肯定不存在啊,

但是他只在初始化的时候触发了一次,之后就没有更新了,所以就一直是undefined 。checkbox选择后才会触发更新,这个时候formRef才第一次赋值

我这里测试,在百度开发工具,大约1/5的几率获取不大, https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vue/dist/vue.runtime.esm.js#L4828 你自己修改下node_modules中 @dcloudio/uni-mp-vue 里上述文件的4828行,把 timeout 的 10ms 修改长一些,比如 30ms 试试

amihhs commented 2 years ago

@fxy060608 大佬弄了个可以复现的工程,在页面跳转后会出现demo

代码的问题吧,为何要在watch formRef 的时候,设置 immediate 为 true,为true的时候,会立即执行,此时formRef肯定不存在啊,

但是他只在初始化的时候触发了一次,之后就没有更新了,所以就一直是undefined 。checkbox选择后才会触发更新,这个时候formRef才第一次赋值

我这里测试,在百度开发工具,大约1/5的几率获取不大, https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vue/dist/vue.runtime.esm.js#L4828 你自己修改下node_modules中 @dcloudio/uni-mp-vue 里上述文件的4828行,把 timeout 的 10ms 修改长一些,比如 30ms 试试

我改成这样基本没什么问题

const doGetRefs = (_refs,count = 3) => {
        const refs = doSetByRefs(_refs);
        console.log('count',count)
        if (refs.length && count > 0) {
            setTimeout(() => {
                doGetRefs(_refs,count - 1)
            }, 20);
        }
    }
    const doSet = () => {
        const refs = doSetByRefs($templateRefs);
        if (refs.length) {
            setTimeout(() => {
                doGetRefs(refs)
            }, 20);
        }
    };
fxy060608 commented 2 years ago
doGetRefs

延长更多的时间也不行?必须多次延迟尝试才能获取?

amihhs commented 2 years ago
doGetRefs

延长更多的时间也不行?必须多次延迟尝试才能获取?

我这边测的,改到80ms,头条小程序出现的频率大概在7-8一次;改成多次的话基本头条最多两次,百度基本第一次就可以

zhongzhifa commented 2 years ago

靠延时来解决,终究不是最优解,遇到慢的机子就不行了吧?

fxy060608 commented 2 years ago

靠延时来解决,终究不是最优解,遇到慢的机子就不行了吧?

已调整为setData的callback来实现,不过此种方式,会影响性能。

fxy060608 commented 2 years ago
doGetRefs

延长更多的时间也不行?必须多次延迟尝试才能获取?

我这边测的,改到80ms,头条小程序出现的频率大概在7-8一次;改成多次的话基本头条最多两次,百度基本第一次就可以

cli更新至:3.0.0-alpha-3050020220617004