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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:55:29