React Web应用中如何维护组件添加的顺序?
React组件顺序维护实现方案
需求说明
- 默认先显示Textarea组件,再显示blockquote组件;
- 点击按钮按规则添加组件:
- 点击「Text Area」按钮,在最后一个blockquote后添加一个Textarea;
- 点击「Code Area」按钮,在最后一个Textarea后添加一个blockquote。
核心思路
原代码将Textarea和blockquote拆分为两个独立数组维护,无法控制交叉显示顺序。改为用单一数组存储所有组件的类型与内容,每个元素记录type(区分text/code)和value,既能按顺序渲染,也能精准找到目标位置插入新组件。
修改后的完整代码
import "./styles.css"; import React, { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; const blogListData = [ { id: 1, heading: "React state", date: "22-May-2022", tag: "React", count: "3" }, { id: 2, heading: "Cypress testing detailss", date: "22-May-2022", tag: "Cypress", count: "5" } ]; const Admin = () => { const [createImageTag, setImageTag] = useState(""); // 用单一数组管理所有内容块,每个元素包含类型和内容值 const [contentBlocks, setContentBlocks] = useState([ { type: "text", value: null }, { type: "code", value: null } ]); const [blogList, setBlogList] = useState([]); const { register, handleSubmit, formState: { errors }, reset } = useForm(); useEffect(() => { setBlogList(blogListData); }, []); // 统一处理内容块的输入修改 function handleBlockChange(i, event) { const values = [...contentBlocks]; values[i].value = values[i].type === "text" ? event.target.value : event.currentTarget.innerText; setContentBlocks(values); } // 添加Text块:在最后一个code块之后插入 function handleTextAreaAdd() { const newBlocks = [...contentBlocks]; const lastCodeBlock = newBlocks.find(block => block.type === "code"); const lastCodeIndex = newBlocks.lastIndexOf(lastCodeBlock); const insertIndex = lastCodeIndex !== -1 ? lastCodeIndex + 1 : newBlocks.length; newBlocks.splice(insertIndex, 0, { type: "text", value: null }); setContentBlocks(newBlocks); } // 添加Code块:在最后一个text块之后插入 function handleCodeAreaAdd() { const newBlocks = [...contentBlocks]; const lastTextBlock = newBlocks.find(block => block.type === "text"); const lastTextIndex = newBlocks.lastIndexOf(lastTextBlock); const insertIndex = lastTextIndex !== -1 ? lastTextIndex + 1 : newBlocks.length; newBlocks.splice(insertIndex, 0, { type: "code", value: null }); setContentBlocks(newBlocks); } function handleImageAreaAdd() { const image = [...createImageTag]; image.push({ value: null }); setImageTag(image); } // 统一删除任意类型的内容块 function handleRemoveBlock(i) { const values = [...contentBlocks]; values.splice(i, 1); setContentBlocks(values); } const handleLogout = () => { localStorage.removeItem("loginEmail"); }; return ( <div id="App"> <div className="parent"> <div className="adminSection"> <h1>Create a new blog</h1> <div className="row"> <div className="logout"> <img src="/images/logout.png" alt="Logout" onClick={handleLogout} ></img> </div> <div className="createBlogSection"> <div className="row"> <button onClick={handleTextAreaAdd} className="textAreaBtn" > Text Area </button> <button onClick={handleCodeAreaAdd} className="codeAreaBtn" > Code Area </button> <button onClick={handleImageAreaAdd} className="imageAreaBtn" > Add Image </button> </div>{" "} <br></br> <div className="row"> {contentBlocks.map((block, idx) => { if (block.type === "text") { return ( <div key={`text-${idx}`} className="dtextArea"> <button type="button" onClick={() => handleRemoveBlock(idx)} className="closeElement" > X </button> <textarea type="text" id={`blogtext-${idx}`} placeholder="Enter your text here" className="defaultTextArea" {...register(`blogtext-${idx}`, { required: true, minLength: { value: 25, message: "Minimum length of 25 letters" } })} value={block.value || ""} onChange={(e) => handleBlockChange(idx, e)} /> <span className="validationmsg"> {errors[`blogtext-${idx}`] && errors[`blogtext-${idx}`].type === "required" && ( <span>Blog text is required !</span> )} {errors[`blogtext-${idx}`] && ( <span>{errors[`blogtext-${idx}`].message}</span> )} </span> </div> ); } else { return ( <div key={`code-${idx}`} className="dCodeArea"> <button type="button" onClick={() => handleRemoveBlock(idx)} className="closeElement" > X </button> <blockquote type="text" id={`blogCode-${idx}`} contentEditable="true" className="codehighlight" placeholder="Enter your code here" {...register(`blogCode-${idx}`, { required: true })} value={block.value || ""} onInput={(e) => handleBlockChange(idx, e)} /> <span className="validationmsg"> {errors[`blogCode-${idx}`] && errors[`blogCode-${idx}`].type === "required" && ( <span>Code block is required !</span> )} </span> </div> ); } })} </div> <div className="row"></div> <div className="row"> <div className="submitSection"> <input type="submit" className="submitBtn" /> </div> </div> </div> </div> </div> <div className="blogListSection"> <h1>Edit blogs</h1> <div className="row"> <div className="editBlogSection"> {blogList.map(({ id, heading, count }) => ( <a key={id} href="#" className="blogitems" > <pre> <span>{count}</span> {heading} </pre> </a> ))} </div> </div> </div> </div> </div> ); }; export default Admin;
关键修改点
- 状态统一:用
contentBlocks数组替代原有的两个独立状态,集中管理所有内容块的顺序、类型和内容; - 精准插入:通过
find和lastIndexOf定位最后一个目标类型的块,在其后插入新组件,满足添加规则; - 表单字段唯一:为每个内容块生成唯一的注册ID,避免表单字段冲突;
- 统一操作:修改、删除逻辑合并为统一函数,简化代码维护。
内容的提问来源于stack exchange,提问作者soccerway
相关产品推荐
相关产品推荐

