Mithril递归View函数调用后子评论未正常渲染问题排查
问题根因
子评论无法正常渲染由3个核心问题导致:
- 遍历子评论时使用了
Array.prototype.forEach,该方法没有返回值,Mithril框架的view函数拿不到生成的虚拟节点数组,无法生成子评论DOM。 - 直接修改
commentStruct.showReplies状态后,没有触发Mithril的重绘逻辑,点击展开按钮后框架不会重新执行视图渲染。 - 表格DOM结构不符合规范:
<tr>标签的合法直接子元素只有<td>/<th>,原代码直接把展开箭头<i>、操作下拉框<select>放在<tr>下,浏览器自动修正DOM时会打乱节点结构。
修复方案
将遍历方法替换为返回数组的map,切换展开状态后手动调用重绘方法,同时修正表格结构,把非单元格元素放到对应<td>内部:
function DisplayComment(commentStruct, identity) { const comment = commentStruct.comment; return { oninit: (v) => { console.log(comment.mComment); }, view: (v) => [ m('tr', [ // 展开箭头+评论内容放在同一个td内,符合表格规范 m('td', [ m('i.fas.fa-angle-right', { class: 'fa-rotate-' + (commentStruct.showReplies ? '90' : '0'), style: 'margin-right: 8px; cursor: pointer; margin-top:12px', onclick: () => { commentStruct.showReplies = !commentStruct.showReplies; // 手动触发框架重绘 m.redraw(); }, }), comment.mComment ]), // 操作下拉框放在独立td内 m('td', [ m( 'select[id=options]', { onchange: (e) => { if (e.target.selectedIndex === 1) { // 回复逻辑 util.popupMessage( m(AddComment, { parent_comment: comment.mComment, channelId: comment.mMeta.mGroupId, authorId: identity, threadId: comment.mMeta.mThreadId, parentId: comment.mMeta.mMsgId, }) ); } else if (e.target.selectedIndex === 2) { // 赞逻辑 AddVote( util.GXS_VOTE_UP, comment.mMeta.mGroupId, comment.mMeta.mThreadId, identity, comment.mMeta.mMsgId ); } else if (e.target.selectedIndex === 3) { // 踩逻辑 AddVote( util.GXS_VOTE_DOWN, comment.mMeta.mGroupId, comment.mMeta.mThreadId, identity, comment.mMeta.mMsgId ); } // 重置下拉框默认选中项 e.target.selectedIndex = 0; }, }, [ m('option[hidden][selected]', 'Options'), util.optionSelect.opts.map((option) => m('option', { value: option }, option.toLocaleString()) ), ] ) ]), m('td', rs.userList.userMap[comment.mMeta.mAuthorId]), m( 'td', typeof comment.mMeta.mPublishTs === 'object' ? new Date(comment.mMeta.mPublishTs.xint64 * 1000).toLocaleString() : 'undefined' ), m('td', comment.mScore), m('td', comment.mUpVotes), m('td', comment.mDownVotes), ]), commentStruct.showReplies ? Data.ParentCommentMap[comment.mMeta.mMsgId] // 用map替换forEach,返回虚拟节点数组供框架渲染 ? Data.ParentCommentMap[comment.mMeta.mMsgId].map( (value) => Data.Comments[value.mMeta.mThreadId] && Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId] ? m(DisplayComment( Data.Comments[value.mMeta.mThreadId][value.mMeta.mMsgId], identity )) : null ) : null : null, ], }; }
可选优化
- 递归渲染时传入层级参数,给子评论的第一个td增加对应倍数的左侧内边距,从视觉上明确评论的层级关系。
- 空返回值统一用
null替代空字符串'',避免框架渲染无用的空文本节点。
内容的提问来源于stack exchange,提问作者Sukhamjot Singh
相关产品推荐
相关产品推荐

