JavaScript递归实现论坛嵌套评论时出现TypeError错误求助
论坛评论嵌套展示递归函数报错问题
问题描述
实现论坛评论嵌套展示时,递归函数findChildComment的最后一行document.querySelector(idForInnerHtml).innerHTML = commentHtml报出**Uncaught TypeError: Cannot set properties of null (setting 'innerHTML')**错误。已确认父评论对应的<div id="cm_reply_view${row.id}"></div>与idForInnerHtml = '#cm_reply_view' + parentId的取值一致,但问题依然存在。
错误原因
核心问题是父评论的HTML还未插入到DOM中,就提前调用了findChildComment尝试操作其子元素:
在findAllComment函数中,你先循环生成父评论的HTML字符串commentHtml,同时在循环内部直接调用findChildComment处理子评论;但此时commentHtml还没有赋值给.cm_list的innerHTML,DOM中根本不存在cm_reply_view${row.id}这个元素,document.querySelector自然找不到目标元素,返回null导致报错。
解决方案
方案1:先插入父评论到DOM,再处理子评论
调整代码顺序,先把所有父评论的HTML插入到页面中,再递归处理子评论:
function findAllComment() { // ...其他代码不变... // 3. 生成父评论HTML response.result.data.forEach(row => { commentHtml += ` <div class="card py-0 mt-3"> <div class="card-header bg-light"> <span class="comment-writer"><span class="card-text">${row.account.nickname}</span></span> <a sec:authorize="isAuthenticated()" th:if="${row.account.id == accountId}"> <a class="comment-delete" onclick="deleteComment(${row.id});"><i class="bi bi-trash3"></i><span class="card-text ml-1">삭제</span></a> <a class="comment-edit" onclick="findEditComment(${row.id});"><i class="fa fa-pencil"></i><span class="card-text ml-1">수정</span></a> </a> <a class="comment-reply" onclick="findReplyComment(${row.id})"><i class="fa fa-comment fa"></i><span class="card-text ml-1">답글</span></a> <span class="date" id="commentDate">${dayjs(row.createdDateTime).format('YYYY-MM-DD HH:mm')}</span> </div> <div class="card-body"> <span class="comment-content"><span class="card-text">${row.content}</span></span> </div> </div> <div id="cm_reply_view${row.id}"></div> <div id="cm_edit_view${row.id}"></div> <div id="cm_createReply_view${row.id}"></div> `; }) // 4. 先将父评论插入DOM document.querySelector('.cm_list').innerHTML = commentHtml; // 5. 再递归处理子评论 response.result.data.forEach(row => { if (row.children.length) { row.children.forEach(childRow => { findChildComment(row.id, childRow); }) } }) }
方案2:递归生成完整HTML字符串,一次性插入DOM
这种方式避免多次操作DOM,性能更优:
- 修改
findChildComment,让它返回子评论的HTML字符串,而非直接操作DOM:
// 子评论生成函数,返回HTML字符串 function buildChildComment(child) { const accountId = [[ ${account.id} ]]; let commentHtml = ` <div class="card py-0 mt-3"> <div class="card-header bg-light"> <span class="comment-writer"><span class="card-text">${child.account.nickname}</span></span> <a sec:authorize="isAuthenticated()" th:if="${child.account.id == accountId}"> <a class="comment-delete" onclick="deleteComment(${child.id});"><i class="bi bi-trash3"></i><span class="card-text ml-1">삭제</span></a> <a class="comment-edit" onclick="findEditComment(${child.id});"><i class="fa fa-pencil"></i><span class="card-text ml-1">수정</span></a> </a> <a class="comment-reply" onclick="findReplyComment(${child.id})"><i class="fa fa-comment fa"></i><span class="card-text ml-1">답글</span></a> <span class="date" id="commentDate">${dayjs(child.createdDateTime).format('YYYY-MM-DD HH:mm')}</span> </div> <div class="card-body"> <span class="comment-content"><span class="card-text">${child.content}</span></span> </div> </div> <div id="cm_reply_view${child.id}"></div> <div id="cm_edit_view${child.id}"></div> <div id="cm_createReply_view${child.id}"></div> `; // 递归处理孙子评论,替换到当前子评论的reply容器中 if (child.children.length) { let grandChildHtml = ''; child.children.forEach(grandChild => { grandChildHtml += buildChildComment(grandChild); }); commentHtml = commentHtml.replace(`<div id="cm_reply_view${child.id}"></div>`, `<div id="cm_reply_view${child.id}">${grandChildHtml}</div>`); } return commentHtml; }
- 在
findAllComment中调用该函数,一次性生成所有评论的HTML并插入:
function findAllComment() { // ...其他代码不变... response.result.data.forEach(row => { let parentHtml = ` <div class="card py-0 mt-3"> <div class="card-header bg-light"> <span class="comment-writer"><span class="card-text">${row.account.nickname}</span></span> <a sec:authorize="isAuthenticated()" th:if="${row.account.id == accountId}"> <a class="comment-delete" onclick="deleteComment(${row.id});"><i class="bi bi-trash3"></i><span class="card-text ml-1">삭제</span></a> <a class="comment-edit" onclick="findEditComment(${row.id});"><i class="fa fa-pencil"></i><span class="card-text ml-1">수정</span></a> </a> <a class="comment-reply" onclick="findReplyComment(${row.id})"><i class="fa fa-comment fa"></i><span class="card-text ml-1">답글</span></a> <span class="date" id="commentDate">${dayjs(row.createdDateTime).format('YYYY-MM-DD HH:mm')}</span> </div> <div class="card-body"> <span class="comment-content"><span class="card-text">${row.content}</span></span> </div> </div> <div id="cm_reply_view${row.id}"></div> <div id="cm_edit_view${row.id}"></div> <div id="cm_createReply_view${row.id}"></div> `; // 插入子评论HTML if (row.children.length) { let childHtml = ''; row.children.forEach(childRow => { childHtml += buildChildComment(childRow); }); parentHtml = parentHtml.replace(`<div id="cm_reply_view${row.id}"></div>`, `<div id="cm_reply_view${row.id}">${childHtml}</div>`); } commentHtml += parentHtml; }) document.querySelector('.cm_list').innerHTML = commentHtml; }
额外建议
- 避免使用
async: false,它会阻塞浏览器主线程,影响页面响应性,建议改成异步处理逻辑。 - 可以给
document.querySelector添加空值判断,避免类似错误:
const targetElement = document.querySelector(idForInnerHtml); if (targetElement) { targetElement.innerHTML = commentHtml; } else { console.warn(`未找到元素:${idForInnerHtml}`); }
内容的提问来源于stack exchange,提问作者Joshep
相关产品推荐
相关产品推荐

