不需要的 React 组件重新渲染?
Unwanted React Component rerender?
所以这是一个用户可以添加部分以添加问题(构建测验)的表单,我注意到当我填写 Answer Choices
并将文件放入我的 dropZone
(拖放有效但未正确更新,您可以忽略此)Answer Choices
和 dropZone
重新呈现,字段如刷新并变为空。
我不完全确定为什么会发生这种情况,我曾尝试查看类似的问题,但无法解决。 Here is my CodeSandbox with my App.
我在想可能是我的Questions
组件中的addQuestion
函数。这是代码:
addQuestion = question => {
questionIdx++;
var newQuestion = { uniqueId: uuid(), question: "" }
this.setState(prevState => ({
questions: [...prevState.questions, newQuestion]
}));
return { questions: newQuestion }
};
我是 React 和 Js 的新手,所以任何 tips/explanations 都会提供很多帮助。谢谢!
当您添加新问题并更新 Questions
组件状态变量 questions
(数组类型)时,整个组件(Questions
及其子组件)都会进行更新它根据新状态重新计算输出 DOM 树(虚拟 DOM)并将其与状态更改前的虚拟 DOM 进行比较的过程。在 'rerendered' 之前,它会检查两个版本的虚拟 DOMs 是否不同,如果不同,它将尽可能有效地重新渲染更改的部分。
在你的情况下,重新计算会看到许多 Question
内部的许多 Answers
组件,并且由于 Answers
没有任何道具,它基本上是对其初始的全新渲染包含 4 个空输入的状态。我能想到的最简单的解决方案是在 Questions
组件的状态下,确保 this.state.questions
数组中的每个对象都有一个 answers
属性(对象数组类型)。在 addQuestion
方法中修改 var newQuestion = { uniqueId: uuid(), question: "" };
以将此数据包含在与该问题相关的答案中。
然后在呈现每个单独的问题时,将此答案数据(答案数组)作为道具传递给 Answers
,然后根据索引(对象或对象)依次传递给每个单独的 Answer
组件一个字符串)。同时,您必须将 updateAnswers
方法作为 prop 传递给 Question
,后者又传递给 Answers
和 Answer
,当 [=23] =]s 输入字段已更改。需要将问题id和答案的id传递给这个方法,最终修改现在应该存储在Questions
组件状态中的答案数据。我调整了下面沙箱中的代码以按照这些思路工作,尽管我没有确保清除所有损坏:
import React, { Component } from "react";
import "./App.css";
var uuid = require("uuid-v4");
// Generate a new UUID
var myUUID = uuid();
// Validate a UUID as proper V4 format
uuid.isUUID(myUUID); // true
class DropZone extends Component {
constructor(props) {
super(props);
this.state = {
file: "",
fileId: uuid(),
className: "dropZone"
};
this.handleChange = this.handleChange.bind(this);
this._onDragEnter = this._onDragEnter.bind(this);
this._onDragLeave = this._onDragLeave.bind(this);
this._onDragOver = this._onDragOver.bind(this);
this._onDrop = this._onDrop.bind(this);
}
handleChange(file = "") {
this.setState({
file: URL.createObjectURL(file)
});
//document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
}
componentDidMount() {
window.addEventListener("mouseup", this._onDragLeave);
window.addEventListener("dragenter", this._onDragEnter);
window.addEventListener("dragover", this._onDragOver);
document
.getElementById("dragbox")
.addEventListener("dragleave", this._onDragLeave);
window.addEventListener("drop", this._onDrop);
}
componentWillUnmount() {
window.removeEventListener("mouseup", this._onDragLeave);
window.removeEventListener("dragenter", this._onDragEnter);
window.addEventListener("dragover", this._onDragOver);
document
.getElementById("dragbox")
.removeEventListener("dragleave", this._onDragLeave);
window.removeEventListener("drop", this._onDrop);
}
_onDragEnter(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDragOver(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
_onDragLeave(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDrop(e, event) {
e.preventDefault();
this.handleChange(e.dataTransfer.files[0]);
let files = e.dataTransfer.files;
console.log("Files dropped: ", files);
// Upload files
console.log(this.state.file);
return false;
}
render() {
const uniqueId = this.state.fileId;
return (
<div>
<input
type="file"
id={uniqueId}
name={uniqueId}
class="inputFile"
onChange={e => this.handleChange(e.target.files[0])}
/>
<label htmlFor={uniqueId} value={this.state.file}>
{this.props.children}
<div className="dropZone" id="dragbox" onChange={this.handleChange}>
Drop or Choose File
<img src={this.state.file} id="pic" name="file" accept="image/*" />
</div>
</label>
<div />
</div>
);
}
}
class Answers extends Component {
constructor(props) {
super(props);
this.state = {
answers: props.answers,
};
this.handleUpdate = this.handleUpdate.bind(this);
}
// let event = {
// index: 1,
// value: 'hello'
// };
handleUpdate(event) {
//if ("1" == 1) // true
//if ("1" === 1) //false
// var answers = this.state.answers;
// answers[event.index] = event.value;
// this.setState(() => ({
// answers: answers
// }));
var answers = this.state.answers.slice();
for (var i = 0; i < answers.length; i++) {
if (answers[i].answerId == event.answerId) {
answers[i].answer = event.value;
break;
}
}
this.setState(() => ({
answers: answers
}));
this.props.updateAnswers(answers)
console.log(event);
}
render() {
return (
<div id="answers">
Answer Choices<br />
{this.state.answers.map((value, index) => (
<Answer
key={`${value}-${index}`}
onUpdate={this.handleUpdate}
value={value}
number={index}
name="answer"
/>
))}
</div>
);
}
}
class Answer extends Component {
constructor(props) {
super(props);
this.state = {
answer: props.value.answer,
answerId: props.value.answerId,
isCorrect: props.value.isCorrect,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({
answer: value
});
this.props.onUpdate({
answerId: this.state.answerId,
value
});
// let sample = {
// kyle: "toast",
// cam: "pine"
// };
// sample.kyle
// sample.cam
}
render() {
return (
<div>
<input type="checkbox" />
<input
type="text"
value={this.state.answer}
onChange={this.handleChange}
key={this.state.answerId}
name="answer"
/>
{/*console.log(this.state.answerId)*/}
</div>
);
}
}
var questionIdx = 0;
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.handleUpdate = this.handleUpdate.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.removeQuestion = this.removeQuestion.bind(this);
}
handleUpdate(event) {
//if ("1" == 1) // true
//if ("1" === 1) //false
var questions = this.state.questions.slice();
for (var i = 0; i < questions.length; i++) {
if (questions[i].uniqueId == event.uniqueId) {
questions[i].question = event.value;
break;
}
}
this.setState(() => ({
questions: questions
}));
console.log(event, questions);
}
updateAnswers(answers, uniqueId) {
const questions = this.state.questions
questions.forEach((question) => {
if (question.uniqueId === uniqueId) {
question.answers = answers
}
})
this.setState({
questions,
})
}
addQuestion = question => {
questionIdx++;
var newQuestion = {
uniqueId: uuid(),
question: "",
answers: [
{ answer: "", answerId: uuid(), isCorrect: false,},
{ answer: "", answerId: uuid(), isCorrect: false,},
{ answer: "", answerId: uuid(), isCorrect: false,},
{ answer: "", answerId: uuid(), isCorrect: false,}]
}
this.setState(prevState => ({
questions: [...prevState.questions, newQuestion]
}));
return { questions: newQuestion };
};
removeQuestion(uniqueId, questions) {
this.setState(({ questions }) => {
var questionRemoved = this.state.questions.filter(
props => props.uniqueId !== uniqueId
);
return { questions: questionRemoved };
});
console.log(
"remove button",
uniqueId,
JSON.stringify(this.state.questions, null, " ")
);
}
render() {
return (
<div id="questions">
<ol id="quesitonsList">
{this.state.questions.map((value, index) => (
<li key={value.uniqueId}>
{
<RemoveQuestionButton
onClick={this.removeQuestion}
value={value.uniqueId}
/>
}
{
<Question
onUpdate={this.handleUpdate}
value={value}
number={index}
updateAnswers={(answers) => this.updateAnswers(answers, value.uniqueId) }
/>
}
{<br />}
</li>
))}
</ol>
<AddQuestionButton onClick={this.addQuestion} />
</div>
);
}
}
class Question extends Component {
constructor(props) {
super(props);
this.state = {
question: props.value.question,
uniqueId: props.value.uniqueId,
answers: props.value.answers,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({
question: value
});
this.props.onUpdate({
uniqueId: this.state.uniqueId,
value
});
}
render() {
return (
<div id={"questionDiv" + questionIdx} key={myUUID + questionIdx + 1}>
Question<br />
<input
type="text"
value={this.state.question}
onChange={this.handleChange}
key={this.state.uniqueId}
name="question"
/>
<DropZone />
<Answers updateAnswers={this.props.updateAnswers} answers={this.state.answers} />
</div>
);
}
}
class IntroFields extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
author: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
console.log([name]);
this.setState((previousState, props) => ({
[name]: value
}));
}
render() {
return (
<div id="IntroFields">
Title:{" "}
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
name="title"
/>
Author:{" "}
<input
type="text"
value={this.state.author}
onChange={this.handleChange}
name="author"
/>
</div>
);
}
}
class AddQuestionButton extends Component {
addQuestion = () => {
this.props.onClick();
};
render() {
return (
<div id="addQuestionButtonDiv">
<button id="button" onClick={this.addQuestion} />
<label id="addQuestionButton" onClick={this.addQuestion}>
Add Question
</label>
</div>
);
}
}
class RemoveQuestionButton extends Component {
removeQuestion = () => {
this.props.onClick(this.props.value);
};
render() {
return (
<div id="removeQuestionButtonDiv">
<button id="button" onClick={this.removeQuestion} key={uuid()} />
<label
id="removeQuestionButton"
onClick={this.removeQuestion}
key={uuid()}
>
Remove Question
</label>
</div>
);
}
}
class BuilderForm extends Component {
render() {
return (
<div id="formDiv">
<IntroFields />
<Questions />
</div>
);
}
}
export default BuilderForm;
所以这是一个用户可以添加部分以添加问题(构建测验)的表单,我注意到当我填写 Answer Choices
并将文件放入我的 dropZone
(拖放有效但未正确更新,您可以忽略此)Answer Choices
和 dropZone
重新呈现,字段如刷新并变为空。
我不完全确定为什么会发生这种情况,我曾尝试查看类似的问题,但无法解决。 Here is my CodeSandbox with my App.
我在想可能是我的Questions
组件中的addQuestion
函数。这是代码:
addQuestion = question => {
questionIdx++;
var newQuestion = { uniqueId: uuid(), question: "" }
this.setState(prevState => ({
questions: [...prevState.questions, newQuestion]
}));
return { questions: newQuestion }
};
我是 React 和 Js 的新手,所以任何 tips/explanations 都会提供很多帮助。谢谢!
当您添加新问题并更新 Questions
组件状态变量 questions
(数组类型)时,整个组件(Questions
及其子组件)都会进行更新它根据新状态重新计算输出 DOM 树(虚拟 DOM)并将其与状态更改前的虚拟 DOM 进行比较的过程。在 'rerendered' 之前,它会检查两个版本的虚拟 DOMs 是否不同,如果不同,它将尽可能有效地重新渲染更改的部分。
在你的情况下,重新计算会看到许多 Question
内部的许多 Answers
组件,并且由于 Answers
没有任何道具,它基本上是对其初始的全新渲染包含 4 个空输入的状态。我能想到的最简单的解决方案是在 Questions
组件的状态下,确保 this.state.questions
数组中的每个对象都有一个 answers
属性(对象数组类型)。在 addQuestion
方法中修改 var newQuestion = { uniqueId: uuid(), question: "" };
以将此数据包含在与该问题相关的答案中。
然后在呈现每个单独的问题时,将此答案数据(答案数组)作为道具传递给 Answers
,然后根据索引(对象或对象)依次传递给每个单独的 Answer
组件一个字符串)。同时,您必须将 updateAnswers
方法作为 prop 传递给 Question
,后者又传递给 Answers
和 Answer
,当 [=23] =]s 输入字段已更改。需要将问题id和答案的id传递给这个方法,最终修改现在应该存储在Questions
组件状态中的答案数据。我调整了下面沙箱中的代码以按照这些思路工作,尽管我没有确保清除所有损坏:
import React, { Component } from "react";
import "./App.css";
var uuid = require("uuid-v4");
// Generate a new UUID
var myUUID = uuid();
// Validate a UUID as proper V4 format
uuid.isUUID(myUUID); // true
class DropZone extends Component {
constructor(props) {
super(props);
this.state = {
file: "",
fileId: uuid(),
className: "dropZone"
};
this.handleChange = this.handleChange.bind(this);
this._onDragEnter = this._onDragEnter.bind(this);
this._onDragLeave = this._onDragLeave.bind(this);
this._onDragOver = this._onDragOver.bind(this);
this._onDrop = this._onDrop.bind(this);
}
handleChange(file = "") {
this.setState({
file: URL.createObjectURL(file)
});
//document.getElementsByClassName("dropZone").style.backgroundImage = 'url(' + this.state.file + ')';
}
componentDidMount() {
window.addEventListener("mouseup", this._onDragLeave);
window.addEventListener("dragenter", this._onDragEnter);
window.addEventListener("dragover", this._onDragOver);
document
.getElementById("dragbox")
.addEventListener("dragleave", this._onDragLeave);
window.addEventListener("drop", this._onDrop);
}
componentWillUnmount() {
window.removeEventListener("mouseup", this._onDragLeave);
window.removeEventListener("dragenter", this._onDragEnter);
window.addEventListener("dragover", this._onDragOver);
document
.getElementById("dragbox")
.removeEventListener("dragleave", this._onDragLeave);
window.removeEventListener("drop", this._onDrop);
}
_onDragEnter(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDragOver(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
_onDragLeave(e) {
e.stopPropagation();
e.preventDefault();
return false;
}
_onDrop(e, event) {
e.preventDefault();
this.handleChange(e.dataTransfer.files[0]);
let files = e.dataTransfer.files;
console.log("Files dropped: ", files);
// Upload files
console.log(this.state.file);
return false;
}
render() {
const uniqueId = this.state.fileId;
return (
<div>
<input
type="file"
id={uniqueId}
name={uniqueId}
class="inputFile"
onChange={e => this.handleChange(e.target.files[0])}
/>
<label htmlFor={uniqueId} value={this.state.file}>
{this.props.children}
<div className="dropZone" id="dragbox" onChange={this.handleChange}>
Drop or Choose File
<img src={this.state.file} id="pic" name="file" accept="image/*" />
</div>
</label>
<div />
</div>
);
}
}
class Answers extends Component {
constructor(props) {
super(props);
this.state = {
answers: props.answers,
};
this.handleUpdate = this.handleUpdate.bind(this);
}
// let event = {
// index: 1,
// value: 'hello'
// };
handleUpdate(event) {
//if ("1" == 1) // true
//if ("1" === 1) //false
// var answers = this.state.answers;
// answers[event.index] = event.value;
// this.setState(() => ({
// answers: answers
// }));
var answers = this.state.answers.slice();
for (var i = 0; i < answers.length; i++) {
if (answers[i].answerId == event.answerId) {
answers[i].answer = event.value;
break;
}
}
this.setState(() => ({
answers: answers
}));
this.props.updateAnswers(answers)
console.log(event);
}
render() {
return (
<div id="answers">
Answer Choices<br />
{this.state.answers.map((value, index) => (
<Answer
key={`${value}-${index}`}
onUpdate={this.handleUpdate}
value={value}
number={index}
name="answer"
/>
))}
</div>
);
}
}
class Answer extends Component {
constructor(props) {
super(props);
this.state = {
answer: props.value.answer,
answerId: props.value.answerId,
isCorrect: props.value.isCorrect,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({
answer: value
});
this.props.onUpdate({
answerId: this.state.answerId,
value
});
// let sample = {
// kyle: "toast",
// cam: "pine"
// };
// sample.kyle
// sample.cam
}
render() {
return (
<div>
<input type="checkbox" />
<input
type="text"
value={this.state.answer}
onChange={this.handleChange}
key={this.state.answerId}
name="answer"
/>
{/*console.log(this.state.answerId)*/}
</div>
);
}
}
var questionIdx = 0;
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.handleUpdate = this.handleUpdate.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.removeQuestion = this.removeQuestion.bind(this);
}
handleUpdate(event) {
//if ("1" == 1) // true
//if ("1" === 1) //false
var questions = this.state.questions.slice();
for (var i = 0; i < questions.length; i++) {
if (questions[i].uniqueId == event.uniqueId) {
questions[i].question = event.value;
break;
}
}
this.setState(() => ({
questions: questions
}));
console.log(event, questions);
}
updateAnswers(answers, uniqueId) {
const questions = this.state.questions
questions.forEach((question) => {
if (question.uniqueId === uniqueId) {
question.answers = answers
}
})
this.setState({
questions,
})
}
addQuestion = question => {
questionIdx++;
var newQuestion = {
uniqueId: uuid(),
question: "",
answers: [
{ answer: "", answerId: uuid(), isCorrect: false,},
{ answer: "", answerId: uuid(), isCorrect: false,},
{ answer: "", answerId: uuid(), isCorrect: false,},
{ answer: "", answerId: uuid(), isCorrect: false,}]
}
this.setState(prevState => ({
questions: [...prevState.questions, newQuestion]
}));
return { questions: newQuestion };
};
removeQuestion(uniqueId, questions) {
this.setState(({ questions }) => {
var questionRemoved = this.state.questions.filter(
props => props.uniqueId !== uniqueId
);
return { questions: questionRemoved };
});
console.log(
"remove button",
uniqueId,
JSON.stringify(this.state.questions, null, " ")
);
}
render() {
return (
<div id="questions">
<ol id="quesitonsList">
{this.state.questions.map((value, index) => (
<li key={value.uniqueId}>
{
<RemoveQuestionButton
onClick={this.removeQuestion}
value={value.uniqueId}
/>
}
{
<Question
onUpdate={this.handleUpdate}
value={value}
number={index}
updateAnswers={(answers) => this.updateAnswers(answers, value.uniqueId) }
/>
}
{<br />}
</li>
))}
</ol>
<AddQuestionButton onClick={this.addQuestion} />
</div>
);
}
}
class Question extends Component {
constructor(props) {
super(props);
this.state = {
question: props.value.question,
uniqueId: props.value.uniqueId,
answers: props.value.answers,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
this.setState({
question: value
});
this.props.onUpdate({
uniqueId: this.state.uniqueId,
value
});
}
render() {
return (
<div id={"questionDiv" + questionIdx} key={myUUID + questionIdx + 1}>
Question<br />
<input
type="text"
value={this.state.question}
onChange={this.handleChange}
key={this.state.uniqueId}
name="question"
/>
<DropZone />
<Answers updateAnswers={this.props.updateAnswers} answers={this.state.answers} />
</div>
);
}
}
class IntroFields extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
author: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
console.log([name]);
this.setState((previousState, props) => ({
[name]: value
}));
}
render() {
return (
<div id="IntroFields">
Title:{" "}
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
name="title"
/>
Author:{" "}
<input
type="text"
value={this.state.author}
onChange={this.handleChange}
name="author"
/>
</div>
);
}
}
class AddQuestionButton extends Component {
addQuestion = () => {
this.props.onClick();
};
render() {
return (
<div id="addQuestionButtonDiv">
<button id="button" onClick={this.addQuestion} />
<label id="addQuestionButton" onClick={this.addQuestion}>
Add Question
</label>
</div>
);
}
}
class RemoveQuestionButton extends Component {
removeQuestion = () => {
this.props.onClick(this.props.value);
};
render() {
return (
<div id="removeQuestionButtonDiv">
<button id="button" onClick={this.removeQuestion} key={uuid()} />
<label
id="removeQuestionButton"
onClick={this.removeQuestion}
key={uuid()}
>
Remove Question
</label>
</div>
);
}
}
class BuilderForm extends Component {
render() {
return (
<div id="formDiv">
<IntroFields />
<Questions />
</div>
);
}
}
export default BuilderForm;