问题描述和解析:
-
[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
}
}