You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,性能更优:

  1. 修改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;
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:55:52