React函数组件转类组件实现(含useMemo等价转换方案)
React 函数组件转类组件完整方案
转换规则说明
- 原函数组件中
useState管理的状态,统一迁移到类组件的state属性中,状态更新调用this.setState方法 - 原组件内的事件处理函数改写为类的箭头函数属性,自动绑定组件实例上下文,避免this指向错误
- 原
useMemo逻辑的等价实现:useMemo的核心能力是仅在依赖项引用变化时重新计算目标值,其余场景复用缓存结果减少性能损耗。类组件中通过实例属性缓存上一次的依赖值和计算结果,render阶段对比当前依赖和缓存的依赖是否为同一引用,一致则直接返回缓存值,不一致则重新计算并更新缓存,和Hook的行为完全对齐。本场景中分数score仅依赖questions状态,该缓存逻辑可以完全复现原useMemo的效果。
完整转换后代码
import React from "react"; import "./styles.css"; const initialQuestions = [ { id: 1, questionText: "What is the capital of France or Ireland?", questionType: "checkbox", answerOptions: [ { answerText: "New York", isCorrect: false, checked: false }, { answerText: "London", isCorrect: false, checked: false }, { answerText: "Paris", isCorrect: true, checked: false }, { answerText: "Dublin", isCorrect: true, checked: false } ] }, { id: 2, questionText: "Who is CEO of Tesla or Amazon?", questionType: "select", answerOptions: [ { answerText: "Jeff Bezos", isCorrect: true, checked: false }, { answerText: "Elon Musk", isCorrect: true, checked: false }, { answerText: "Bill Gates", isCorrect: false, checked: false }, { answerText: "Tony Stark", isCorrect: false, checked: false } ] }, { id: 3, questionText: "The iPhone was created by which company?", questionType: "select", answerOptions: [ { answerText: "Apple", isCorrect: true, checked: false }, { answerText: "Intel", isCorrect: false, checked: false }, { answerText: "Amazon", isCorrect: false, checked: false }, { answerText: "Microsoft", isCorrect: false, checked: false } ] } ]; class App extends React.Component { // 初始化状态,对应原三个useState state = { currentQuestion: 0, showScore: false, questions: initialQuestions }; // 缓存useMemo计算结果和依赖,对应原useMemo逻辑 _renderCache = { lastQuestions: null, cachedScore: 0 }; // 答案选择处理逻辑 handleAnswerOptionClick = (e, questionId) => { const answerTextValue = e.target.value; const updatedQuestions = this.state.questions.map((question) => { if (question.id === questionId) { const updatedAnswersOptions = question.answerOptions.map((answer) => { if (answer.answerText === answerTextValue) { return { ...answer, checked: !answer.checked }; } else { return question.questionType === "select" ? { ...answer, checked: false } : answer; } }); return { ...question, answerOptions: updatedAnswersOptions }; } else { return question; } }); this.setState({ questions: updatedQuestions }); }; // 下一题按钮处理逻辑 handleNext = () => { const nextQuestion = this.state.currentQuestion + 1; if (nextQuestion < this.state.questions.length) { this.setState({ currentQuestion: nextQuestion }); } else { this.setState({ showScore: true }); } }; render() { const { currentQuestion, showScore, questions } = this.state; // 等价useMemo计算score:依赖questions,引用不变则复用缓存 let score; if (this._renderCache.lastQuestions === questions) { score = this._renderCache.cachedScore; } else { score = questions.reduce((acc, q) => { const correctAnswerSelected = q.answerOptions.reduce((ansAcc, answer) => { if (answer.checked && answer.isCorrect) { ansAcc = ansAcc + 1; } return ansAcc; }, 0); return acc + correctAnswerSelected; }, 0); this._renderCache.lastQuestions = questions; this._renderCache.cachedScore = score; } return ( <div className="App"> {showScore ? ( <div> You scored {score} out of {questions.length} </div> ) : ( <main className="question"> {score} <div> <div> <h2>{questions[currentQuestion].questionText}</h2> </div> </div> <div className="question__options"> {questions[currentQuestion].questionType === "select" && questions[currentQuestion].answerOptions.map( (answerOption, i) => ( <React.Fragment key={answerOption.answerText}> <input type="radio" name="radio-group" value={answerOption.answerText} checked={answerOption.checked} onChange={(e) => this.handleAnswerOptionClick( e, questions[currentQuestion].id ) } /> <label htmlFor={i}>{answerOption.answerText}</label> </React.Fragment> ) )} {questions[currentQuestion].questionType === "checkbox" && questions[currentQuestion].answerOptions.map( (answerOption, i) => ( <div key={answerOption.answerText}> <input className="option" type="checkbox" id={i} value={answerOption.answerText} onClick={(e) => this.handleAnswerOptionClick( e, questions[currentQuestion].id ) } /> <label htmlFor={i}>{answerOption.answerText}</label> </div> ) )} <div className="question__submit"> <button onClick={this.handleNext}>Next</button> </div> </div> </main> )} </div> ); } } export default App;
逻辑一致性说明
转换后的代码和原函数组件运行效果完全对齐:
- 单选题互斥选择、多选题多选逻辑和原实现一致
- 分数计算规则完全相同,且保留了原
useMemo的缓存能力,不会在每次渲染时重复遍历题目计算分数 - 题目切换、最终得分页展示逻辑和原实现无差异
内容的提问来源于stack exchange,提问作者Andikatama
相关产品推荐
相关产品推荐

