Vue在onMounted中querySelector获取不到dom元素的解决方案

850 阅读2分钟

问题描述和解析:

  • [1] - 如果我们在 vue 里写的 虚拟Dom 是通过 v-if 或 v-for 这类动态渲染的方式去控制的话,那么我们在 onMounted(()=>{}) 事件里是获取不到真实 dom 元素的,因为 onMounted 事件只是在UI第一次挂载的时候执行的,而我们动态渲染的dom时在绑定的响应式变量有了满足条件的值之后才会把对应的dom挂载上;

  • 若理解上述解释,那其实应该就能最快的去想到使用 watch 监听响应的响应式变量再去获取动态挂载的dom,但是用watch的话恐怕还需要添加些判断,毕竟watch是只要响应式变量有变化就会执行,而我们获取dom希望执行一次就好了;

  • 我有找到过说在 onMounted 里写 setTimeout(()=>{}, 300) 来等待一定的时间后再去查找 dom,但是实际项目中这个时间很难设置准确,万一在300毫秒后才挂载上dom,那又会出问题了;

  • 综上思考,我想到了以下解决方案

解决方案:

  • 方案说明:既然 document.querySelector(selectors) 不一定能获取到 dom,那我就参考爬虫思想去去等待并重复尝试获取 dom 直到获取到为止;

  • 当然我们前端项目没必要长时间去查找dom,所以我设置了一个较少的尝试次数,只要确保在页面加载的合理时间内去尝试几次查找 dom 即可,页面性能正常的情况下,这种方式必然可以获取到dom

/**
 * 同步查找 dom 元素
 * @param selectors 同 document.querySelector
 * @returns 查找到的 dom 节点
 */
export async function querySelectorSync (selectors:string) {
  // 若一直找不到满足条件的元素,则会最多尝试 20 次,间隔200ms,即 20*200ms = 4s,因为我们认为一个页面如果4秒还没获取到我们需要的元素,那这个页面一定是加载太慢了,重点是要优化页面性能了
  const count = 20 // 最多尝试查找的次数
  const timeMs = 200 // 若没找到满足条件的元素集合,则间隔该时间后,再次尝试查找
  let timeIndex = 0
  let timeEnd = false

  let domNode = document.querySelector(selectors)
  if (domNode) {
    return domNode
  } else {
    timeEnd = !!domNode
    while (!domNode && timeIndex < count && !timeEnd) {
      timeIndex += 1
      domNode = await new Promise((resolve:(value:Element | null)=>void) => {
        setTimeout(() => {
          const domNodeInner = document.querySelector(selectors)
          timeEnd = !!domNodeInner
          resolve(domNodeInner)
        }, timeMs)
      })
    }
    return domNode
  }
}

/**
 * 同步查找满足条件的所有 dom 元素集合
 * @param selectors 同 document.querySelectorAll
 * @param nodeCount 要求数量,默认为0;若设置了数量,则只有查找到的 dom 数量大于或等于这个数,才会返回,否则会继续查找
 * @returns 查找到的 dom 节点
 */
export async function querySelectorAllSync (selectors: string, nodeCount:number=0) {
  const needNodeCount = nodeCount < 1 ? 0 : nodeCount
  // 若一直找不到满足条件的元素集合,则会最多尝试 20 次,间隔200ms,即 20*200ms = 4s,因为我们认为一个页面如果4秒还没获取到我们需要的元素,那这个页面一定是加载太慢了,重点是要优化页面性能了
  const count = 20 // 最多尝试查找的次数
  const timeMs = 200 // 若没找到满足条件的元素集合,则间隔该时间后,再次尝试查找
  let timeIndex = 0
  let timeEnd = false

  let domNodeList = document.querySelectorAll(selectors)
  if (domNodeList.length >= needNodeCount) {
    return domNodeList
  } else {
    timeEnd = domNodeList.length >= needNodeCount
    while (domNodeList.length < needNodeCount && timeIndex < count && !timeEnd) {
      timeIndex += 1
      domNodeList = await new Promise((resolve:(value:NodeListOf<Element>)=>void) => {
        setTimeout(() => {
          const domNodeListInner = document.querySelectorAll(selectors)
          timeEnd = domNodeListInner.length >= needNodeCount
          resolve(domNodeListInner)
        }, timeMs)
      })
    }
    return domNodeList
  }
}