WordPress Gutenberg可编辑Quiz重复字段实现方案求助
问题描述
在WordPress Gutenberg中开发Quiz区块时,尝试通过useState生成重复的<QuizWrapper>组件,点击按钮可新增组件,但重复后的输入框无法编辑。单独使用<QuizWrapper>时编辑功能正常,批量生成的组件仅能显示,无法交互。
问题根源
- 共享属性冲突:所有重复的
<QuizWrapper>都复用同一个Block属性(question、answers等),修改其中一个会同步影响所有组件,且渲染时无法区分不同项的独立状态。 - 错误的组件存储方式:用
useState直接存储React元素,导致组件无法响应属性更新,失去编辑交互能力。 - 数据结构不支持多Quiz项:当前Block的Attributes设计为单个Quiz的字段,未适配多Quiz项的存储需求。
解决方案
1. 重构Block Attributes
修改index.js,将单个Quiz字段改为数组类型,用于存储多个独立Quiz项的数据:
import { registerBlockType } from "@wordpress/blocks"; import "./style.scss"; import Edit from "./edit"; import save from "./save"; import metadata from "./block.json"; registerBlockType(metadata.name, { attributes: { quizzes: { type: "array", default: [ { question: "Example Question", answers: [""], correctAnswer: undefined, bgColor: "#ededed" } ] }, }, edit: Edit, save, });
2. 修改Edit组件逻辑
不再用useState存储组件,而是基于quizzes属性动态渲染组件,新增/删除Quiz项时直接更新Block属性:
import { __ } from "@wordpress/i18n"; import { Button } from "@wordpress/components"; import { useBlockProps } from "@wordpress/block-editor"; import "./editor.scss"; import QuizWrapper from "./components/QuizWrapper"; export default function Edit(props) { const { attributes, setAttributes } = props; const blockProps = useBlockProps({ className: "quiz-wrapper" }); // 新增Quiz项 const addSection = () => { setAttributes({ quizzes: [ ...attributes.quizzes, { question: "New Question", answers: [""], correctAnswer: undefined, bgColor: "#ededed" } ] }); }; // 删除指定索引的Quiz项 const deleteQuiz = (index) => { const newQuizzes = attributes.quizzes.filter((_, i) => i !== index); setAttributes({ quizzes: newQuizzes }); }; return ( <div {...blockProps}> {attributes.quizzes.map((quiz, index) => ( <QuizWrapper key={index} quiz={quiz} index={index} setAttributes={setAttributes} quizzes={attributes.quizzes} deleteQuiz={() => deleteQuiz(index)} /> ))} <Button isPrimary onClick={addSection}> Add Another Section </Button> </div> ); }
3. 更新QuizWrapper组件
接收单个Quiz数据和索引,传递给子组件并添加删除功能:
import React from 'react' import Question from "./Question" import Answers from "./Answers" import { Button } from "@wordpress/components"; const QuizWrapper = ({ quiz, index, setAttributes, quizzes, deleteQuiz }) => { return ( <div className='quizes'> <Question quiz={quiz} index={index} setAttributes={setAttributes} quizzes={quizzes} /> <Answers quiz={quiz} index={index} setAttributes={setAttributes} quizzes={quizzes} /> {quizzes.length > 1 && ( <Button isDestructive onClick={deleteQuiz} style={{ marginTop: '16px' }}> Delete Quiz </Button> )} </div> ) } export default QuizWrapper
4. 修改Question组件
操作对应索引的Quiz数据,实现独立编辑:
import { TextControl } from "@wordpress/components"; export default function Question({ quiz, index, setAttributes, quizzes }) { const updateQuestion = (value) => { const newQuizzes = [...quizzes]; newQuizzes[index].question = value; setAttributes({ quizzes: newQuizzes }); }; return ( <div className="question"> <h2 style={{ fontSize: "20px", margin: "20px 0 8px 0" }}>Questions: </h2> <TextControl value={quiz.question} onChange={updateQuestion} /> </div> ); }
5. 更新Answers组件
操作对应索引的Quiz的答案数据,维护独立的答案状态:
import { TextControl, Button, Icon } from "@wordpress/components"; export default function Answers({ quiz, index, setAttributes, quizzes }) { const markAsCorrect = (answerIndex) => { const newQuizzes = [...quizzes]; newQuizzes[index].correctAnswer = answerIndex; setAttributes({ quizzes: newQuizzes }); }; const deleteAnswer = (answerIndexToDelete) => { const newQuizzes = [...quizzes]; newQuizzes[index].answers = newQuizzes[index].answers.filter((_, i) => i !== answerIndexToDelete); // 删除正确答案时重置标记 if (answerIndexToDelete === newQuizzes[index].correctAnswer) { newQuizzes[index].correctAnswer = undefined; } setAttributes({ quizzes: newQuizzes }); }; const addAnswer = () => { const newQuizzes = [...quizzes]; newQuizzes[index].answers = [...newQuizzes[index].answers, ""]; setAttributes({ quizzes: newQuizzes }); }; return ( <div className="answers"> <p style={{ fontSize: "13px", margin: "20px 0 8px 0" }}>Answers: </p> {quiz.answers.map((answer, answerIndex) => ( <div className="answer-wrapper" key={answerIndex}> <Button onClick={() => markAsCorrect(answerIndex)}> <Icon className="mark-as-correct" icon={quiz.correctAnswer === answerIndex ? "yes-alt" : "yes"} /> </Button> <TextControl value={answer} onChange={(updatedAnswer) => { const newQuizzes = [...quizzes]; newQuizzes[index].answers[answerIndex] = updatedAnswer; setAttributes({ quizzes: newQuizzes }); }} /> <Button isLink className="deleteBtn" onClick={() => deleteAnswer(answerIndex)} > Delete </Button> </div> ))} <Button isPrimary onClick={addAnswer}> Add Another Answer </Button> </div> ); }
关键说明
- 所有Quiz项的数据都存储在Block的
quizzes属性中,每个项独立维护自己的问题、答案等字段。 - 通过索引区分不同的Quiz项,修改时仅更新对应索引的数据,彻底避免状态冲突。
- 不再存储React元素,而是基于数据动态渲染组件,确保组件能响应属性更新,恢复编辑交互能力。
内容的提问来源于stack exchange,提问作者Farhan Mullick
相关产品推荐
相关产品推荐

