MobX报错:此时不允许修改状态,移除<ul>标签则恢复正常求解答
问题分析与解决方案
首先,我能理解你排查一整天的烦躁——这种“移除某个标签就好”的问题往往藏着容易忽略的细节,咱们一步步拆解:
1. 最直接的原因:非法HTML嵌套结构
你的render方法里已经有一个外层<ul styleName="Main-row">,但在renderRows处理子目录时,你直接往rows数组里塞了<ul>元素:
this.rows.push( <ul styleName="Main-row"> <FolderRow onClick={this.onClick} label={dir.name} /> </ul> );
这会导致最终DOM结构变成<ul><ul>...</ul></ul>,但HTML规范明确要求<ul>的直接子元素只能是<li>。这种非法嵌套会触发React的渲染警告甚至隐性报错,而移除<ul>后,rows里的<FolderRow>(应该是基于<li>的组件)直接作为外层<ul>的子元素,结构合法了,自然就能正常运行。
2. 深层问题:直接存储JSX到Mobx数组的反模式
你把React元素(<FolderRow>、<ul>)直接存在@observable rows里,这种做法本身就有问题:
- JSX是不可变的渲染描述对象,存储它们会让状态管理变得模糊,应该存储原始目录数据,再在
render里根据数据生成UI。 renderRows是异步递归函数,多次零散修改rows数组的时机难以控制,容易导致状态更新和渲染不同步。
修复方案
步骤1:先修正HTML结构
把嵌套的<ul>放到<li>里面,符合规范:
// 处理子目录时的正确结构 this.rows.push( <li key={dir.name}> <FolderRow onClick={this.onClick} label={dir.name} /> <ul styleName="Sub-row">{/* 后续渲染子目录 */}</ul> </li> );
步骤2:重构为“存数据+按需渲染”的模式
更可靠的做法是用Mobx存储目录树形数据,而非直接存JSX,重构后的代码如下:
@observer class LeftMenu extends Component { @observable directoryTree = []; // 存储目录树形数据 @observable isLoading = true; // 新增加载状态,优化用户体验 componentDidMount() { this.loadDirectoryTree(this.props.shareFiles); } // 递归加载目录数据,构建树形结构 loadDirectoryTree = async (data, parentPath = "") => { const treeNodes = []; for (const dir of data) { const currentPath = parentPath === "" ? dir.name : `${parentPath}/${dir.name}`; const subDirectories = await API.browseFolder.getFiles({ shareName: this.props.shareName, dirPath: currentPath }); // 构建目录节点,包含子目录信息 const dirNode = { name: dir.name, path: currentPath, children: subDirectories.data.length ? await this.loadDirectoryTree(subDirectories.data, currentPath) : [] }; treeNodes.push(dirNode); } this.directoryTree = treeNodes; this.isLoading = false; }; // 递归渲染目录树UI renderDirectoryNodes = (nodes) => { return nodes.map(node => ( <li key={node.path}> <FolderRow onClick={() => this.onClick(node)} label={node.name} /> {/* 有子目录时才渲染嵌套列表 */} {node.children.length > 0 && ( <ul styleName="Sub-row"> {this.renderDirectoryNodes(node.children)} </ul> )} </li> )); }; render() { const { shareName } = this.props; if (this.isLoading) { return <div styleName="Container">加载中...</div>; } return ( <div styleName="Container"> <h3 styleName="Share-name">{shareName}</h3> <ul styleName="Main-row"> {this.renderDirectoryNodes(this.directoryTree)} </ul> </div> ); } }
这样修改后,既解决了HTML结构非法的问题,也遵循了Mobx和React的最佳实践:状态只存原始数据,渲染逻辑和数据分离,异步加载的状态也更可控。
内容的提问来源于stack exchange,提问作者Alex Park
相关产品推荐
相关产品推荐

