首页 > 解决方案 > 在 Reactjs 中管理状态时遇到问题

问题描述

我一直在练习 JavaScript 和 ReactJs,但我一直被一个问题困住了一段时间。基本上,我正在尝试使用 ReactJs 重写我的 HTML、CSS、Javascript 项目。

这是我的问题:(关于 React 代码)。假设我单击问题 1 的第一个答案选项,因此类名发生更改,样式更改(背景变为黑色)并且isClicked变为 true(两者都是 EachIndividualAnswer 类中的状态)。如果我然后单击第二个答案选项,我希望第一个答案选项(以及该问题的所有其他答案选项)的样式为空,并且 isClicked 为 false 并且只有第二个答案将具有 isClicked === true和 className="clicked"。

希望这是有道理的。抱歉发了这么多文件,不知道还有什么办法。

谢谢

我的 HTML、CSS 和 JAVASCRIPT 代码。(我试图用 ReactJs 重写的代码)

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

var numberOfQuestions = 5;
var choicesPerQuestion = 5;

var questionNumber = document.getElementsByClassName("questionNumber");

var question = document.getElementsByClassName("question");

var answers = document.getElementsByClassName("answers");

var answer_A = document.getElementsByClassName("answer_A");

var answer_B = document.getElementsByClassName("answer_B");

var answer_C = document.getElementsByClassName("answer_C");

var answer_D = document.getElementsByClassName("answer_D");

var answer_E = document.getElementsByClassName("answer_E");


var submit = document.getElementById("submit");

// Answer key

var answerKey = [21, 3, "Nani", "Kevin Durant", "Russ"];

var userAnswerArray = new Array(5);


// Put every single possible clickable answer in 5x5 array

// clicking an answer changes its background and color

var individual_answers = new Array(numberOfQuestions);


for(let i=0; i<numberOfQuestions; i++) {
	individual_answers[i] = new Array(choicesPerQuestion);
}


// Adding Event listeners to each answer choice

for (let i = 0; i < answers.length; i++) {
	specificAnswers = answers[i].getElementsByTagName("li"); // answers to each questions e.g. answers to qu.1, then qu.2

	for (let j = 0; j < specificAnswers.length; j++) {
		individual_answers[i][j] = specificAnswers[j]; // individual answers to each qu.
		var spanX = individual_answers[i][j].getElementsByTagName("span"); // did not use this
		individual_answers[i][j].addEventListener("click", click(i , j));
	}

}

function click(i, j) {
	return function() {
		console.log(individual_answers[i][j].innerText);

		if(individual_answers[i][j].style.background != "black") { // if it's not black, set all to white, then put specific one to black

			for(let x=0; x<choicesPerQuestion; x++) {
				individual_answers[i][x].style.cssText = "background: white";
				individual_answers[i][x].getElementsByTagName("span")[0].style.color = "black";
			}
			individual_answers[i][j].style.cssText = "background: black";
			individual_answers[i][j].style.color = "green";
			individual_answers[i][j].getElementsByTagName("span")[0].style.color = "white";

			userAnswerArray[i] = individual_answers[i][j].innerText; 
			// i = question number, j = specific answer to question number i
			// So on each click, if answer originally doesn't have a black background, add it to userArray

		}
		else { // If background is black, on click you have to remove that from individual array
			individual_answers[i][j].style.cssText = "background: white";
			individual_answers[i][j].getElementsByTagName("span")[0].style.color = "black";
			userAnswerArray.splice(i, 1);
		}

	}
}

// Adding event listener to submit button
submit.addEventListener("click", score);

/* Easiest thing to do would be to make an "Answer class" for each answer. (using prototypes) with field selected.
Then count the number of answers with fields selected and compare with answer key or smthn. Try this as an exercise for later, maybe ReactJs */

/* For now I will create an array for the answers that will change as the user clicks and use the actual words to see if they match */

function score() {
	/* Add a check later to see if he has answered every question or at least 60% */

	var counter = 0;

	for(let x=0; x<numberOfQuestions; x++) {
		if(answerKey[x] == userAnswerArray[x]) {
			counter++;
		}
	}

console.log("User has submitted the quiz and scored " + counter);

if(counter < 3) {
	alert("Try again, you failed");
}

else {
	alert("Are you a lizard?")
}

/* Show on a message and ask to retake*/

}
ul {
	list-style-type: square;
}

ul > li {
	color: blue;
	font-size: 30px;
}

ul > li > span {
	color: black;
	font-size: 20px;
}

#submit {
	font-size: 30px;
}
<!--I will try to create a multiple choice exam. The user can NOT submit until he has answered 60% of the questions. Once he submits
I will show him his score. Give him the option to see which questions he failed, as well as the right answer. -->

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Multiple Choice Exam</title>
        <link rel="stylesheet" href="mcq.css">
    </head>
    <body>
            <h1>NASA Final Entry Exam</h1>
            <h2>Only the most genius of individuals will pass</h2>

            <br>
            <hr>
            <br>

            <p class="question"><span class="questionOne">1</span>. What is 9+10</p>
            <ul class="answers" id="answers1">
                <li class="answer_A"><span>1</span></li>
                <li class="answer_B"><span>19</span></li>
                <li class="answer_C"><span>21</span></li>
                <li class="answer_D"><span>90</span></li>
                <li class="answer_E"><span>-1<span></li>
            </ul>

            <p class="question"><span class="questiontwo">2</span>. How many goals did Ronaldo score against Spain in the World Cup 2018</p>
            <ul class="answers">
                <li class="answer_A"><span>1</span></li>
                <li class="answer_B"><span>3</span></li>
                <li class="answer_C"><span>5</span></li>
                <li class="answer_D"><span>0</span></li>
                <li class="answer_E"><span>-1<span></li>
            </ul>

            <p class="question"><span class="questionThree">3</span>. Who Stole Ronaldo's (CR7) greates ever goal?</p>
            <ul class="answers">
                <li class="answer_A"><span>Pepe</span></li>
                <li class="answer_B"><span>Messi</span></li>
                <li class="answer_C"><span>Casillas</span></li>
                <li class="answer_D"><span>Benzema</span></li>
                <li class="answer_E"><span>Nani<span></li>
            </ul>

            <p class="question"><span class="questionFour">4</span>. Which one of these players ruined the NBA</p>
            <ul class="answers">
                <li class="answer_A"><span>Allen Iverson</span></li>
                <li class="answer_B"><span>Kevin Durant</span></li>
                <li class="answer_C"><span>Steph Curry</span></li>
                <li class="answer_D"><span>Lebron James</span></li>
                <li class="answer_E"><span>Russel Westbrook<span></li>
            </ul>

            <p class="question"><span class="questionFive">5</span>. Who is currently number 1 in the internet L ranking?</p>
            <ul class="answers">
                <li class="answer_A"><span>Drake</span></li>
                <li class="answer_B"><span>Pusha T</span></li>
                <li class="answer_C"><span>Russel WestBrook</span></li>
                <li class="answer_D"><span>Lil Xan</span></li>
                <li class="answer_E"><span>Russ<span></li>
            </ul>

            <button id="submit">Submit</button>
        
        <script src="mcq.js"></script>
        
    </body>
</html>

到目前为止,这是我的 REACJS 项目。不确定如何正确上传这些文件:

[App.js]

import React, { Component } from 'react';
import './App.css';
import Title from './Title/Title';
import Question from './Question/Question';
import Aux from './hoc/Aux';

class App extends Component {
  state = {
    counter: 0,
    questionArray: [
      "What is 9+10",
      "How many goals did Ronaldo score against Spain in the World Cup 2018",
      "Who Stole Ronaldo's (CR7) greates ever goal?",
      "Which one of these players ruined the NBA",
      "Who is currently number 1 in the internet L rankings?"
  ],
    answerChoicesArray: [
      ["1", "19", "21", "90", "-1"],
      ["1", "3", "5", "0", "-1"],
      ["Pepe", "Messi", "Casillas", "Benzema", "Nani"],
      ["Allen Iverson", "Kevin Durant", "Steph Curry", "Lebron James", "Russel Westbrook"],
      ["Drake", "Pusha T", "Russel Westbrook", "Lil Xan", "Russ"]
    ]
  }

  render() {
    return (
      <div className="App">
        <div className="container">
          <Aux>
            <Title />
            <h2>Only the most genius of individuals will pass</h2>
            <hr/>
            <Question
              questionArray={this.state.questionArray}
              answerChoicesArray={this.state.answerChoicesArray} />
            <button
            onClick={() => alert("We don't support this yet")}
            type="submit">SUBMIT</button>
          </Aux>
        </div>
      </div>
    );
  }
}

export default App;

.

.

[Question.js]

import React from 'react';
import AnswerChoices from '../AnswersChoices/AnswerChoices';

const Question = (props) => // why doesn't it work if I put a curly brace here
    props.questionArray.map((question, index) => {
        return(
            <div>
                <p>{index + 1}. {question}</p>

                <AnswerChoices
                    index={index} // try just index answersArray is the array of ALL answers
                    answerChoicesArray={props.answerChoicesArray} /> 
            </div>
    );
})

export default Question;

.

.
[AnswerChoices.js]

import React from 'react';
import SpecificAnswerChoice from './SpecificAnswerChoice/SpecificAnswerChoice'

const AnswerChoices = (props) => {
    console.log(props.answerChoicesArray[props.index]);
        return (
            // 5 answers array for each question
            <div>
                <ul>
                <SpecificAnswerChoice 
                    answers={props.answerChoicesArray[props.index]}/>
                </ul>
            </div>
        )
    }

export default AnswerChoices;

.

.

[SpecificAnswerChoice.js]

    import React, { Component } from 'react';
    import EachIndividualAnswer from './EachIndividualAnswer/EachIndividualAnswer'

    class SpecificAnswerChoice extends Component {
        // If I click once, set all to white and specific to black
        state = {
            resetClicksState: true // can start w/ false then change to always true inside resetClicks function
        }

        resetClicks = () => {
            console.log("TEST");
        }


        render() {

            // const style = {
            //     backgroundColor: 'white'
            // };


            return(  
                this.props.answers.map(individualAnswer => {
                    return (
                        <EachIndividualAnswer
                            className={this.state.class}
                            individualAnswer={individualAnswer}
                            resetClicks={this.resetClicks}
                            // onClick={this.clickHandler}
                            />
                    );
                })          
            )
        }
    }

    export default SpecificAnswerChoice;


    import React, { Component } from 'react';

.

. [EachIndividualAnswer.js]

class EachIndividualAnswer extends Component {
    state = {
        isClicked: false,
        class: ""
    }

    // clickHandler = (style) => {
    //     if(style.backgroundColor === 'white') {
    //         style.backgroundColor = 'black';
    //         style.color = 'white';
    //     }
    // }

    onClickHandler = () => {
        console.log(this.state.isClicked);
        console.log("djhfdf");
        if(this.state.isClicked) {
            var tempClass=""
            this.setState({
                isClicked: false,
                class: tempClass
            });
        } else {
            tempClass="clicked"
            this.setState({
                isClicked: true,
                class:tempClass
            })
        }
        this.props.resetClicks();
    }



    // testingOnClick = () => {
    //     console.log("If this works then I have 2+ functions on OnClick");
    // }

    // if props.resetClicks is true, which it always is, className='', isClicked=false for EVERY
    // EachIndividualAnswer. Then I do my logc that I already had
    render() {

        return (<li 
                    className={this.state.class}
                    onClick={this.onClickHandler}>
                    <span>
                        {this.props.individualAnswer}
                    </span>
                </li>);
    }
}

export default EachIndividualAnswer;

.

.

[Aux.js]

const aux = (props) => props.children;

export default aux;

标签: javascripthtmlcssreactjs

解决方案


如果我理解正确,这是一个可能的答案。我完全在模仿这种情况,所以这对你来说不是一个完整的解决方案。

class App extends React.Component {
  state = {
    answers: [ "1", "19", "21", "90", "-1" ],
    selected: {},
  }

  handleClick = e => {
    const {answer} = e.target.dataset;
    this.setState({selected:{
      [answer]: !!answer,
    }})
  };


  render() {
    const {answers} = this.state;
    console.log(this.state.selected);
    return (
      <div>
      <ul>
        {
          answers.map( answer => 
            <li
            data-answer={answer}
            className={
                  this.state.selected[answer] ? 'colored' : ''
            }
            onClick={this.handleClick}
            >{answer}
            </li>
          )
        }
      </ul>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.colored {
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

在这里,我们使用selected状态来保存选定的情况,并根据这种情况添加我们的类或将其设置为 null。我们的handleClick函数会改变这个选择。在 render 方法中有一个console.log,所以你可以看到这里发生了什么。

另外,我在这里使用数据集来获取值,因为我不喜欢 JSX 中的绑定函数。有了.bind它就可以这样。只有相关部分:

handleClick2 = answer =>
   this.setState({
     selected: {
       [answer]: !!answer,
     }
})

onClick={this.handleClick2.bind(this, answer)}

EachIndividualAnswer此处的另一种可能的解决方案是,您可以在组件中执行此逻辑,而不是在组件中执行此逻辑SpecificAnswerChoice。我的意思是持有状态并拥有handleClick处理程序。因此,您可以将此处理程序传递给您EachIndividualAnswer,而answer不是使用您的回调,您可以在EachIndividualAnswer. 因此,无需使用数据集或.bind.

最后,正如评论中的其他人所说,您应该在有问题的地方分享最少的代码。因此,人们可以轻松查看您的代码并尽力而为。

如果您认为将较长的答案作为对象属性保存很愚蠢,那么这里是另一个使用答案数组索引的答案:

class App extends React.Component {
  state = {
    answers: [ "1", "19", "21", "90", "-1" ],
    selected: {},
  }

  handleClick = e => {
    const {index} = e.target.dataset;
    this.setState({selected:{
      [index]: !!index,
    }})
  };


  render() {
    const {answers} = this.state;
    console.log(this.state.selected);
    return (
      <div>
      <ul>
        {
          answers.map( (answer, index) => 
            <li
            data-index={index}
            className={
                  this.state.selected[index] ? 'colored' : ''
            }
            onClick={this.handleClick}
            >{answer}
            </li>
          )
        }
      </ul>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.colored {
  color: red;
 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

在评论中的问题后编辑

const {answers} = this.state

这是 Javascript 的解构赋值语法。我们只是从this.state对象中挑选一个属性。此代码的简写版本:

const answers = this.state.answers;

起初,这似乎不是那么有用,但您可以在对象中选择任意数量的属性。想想一个拥挤的物体,你只需要其中三个:

const { one, two, three } = object;

这是以下的简写:

const one = object.one;
const two = object.two;
const three = object.three;

很有用。有关更多信息,请阅读此答案,当然还有官方文档。你可以在 React 世界中看到破坏,因为有些人倾向于在合适的地方使用它。当它被过度使用时,它可能并不总是那么好。有时会更难阅读代码。但是当你在合适的地方使用它时,它会节省时间,甚至使可读性更好。

我们在我们的州保留了答案。在您的原始代码中,您将它们作为道具。在根据道具进行解释后,我将提供另一个答案。selected是真正的交易,声明我们保留我们选择的元素。

this.setState({selected:{ [answer]: !!answer }})

是的,这有点尴尬。我们正在为对象使用计算属性。因此,我们可以在对象的属性中使用变量。[answer],这就是我们使用的。因此,在selected状态中,我们正在设置一个名称为answer变量的属性。现在,我们使用右侧将我们的值用作布尔值,并且在第一次状态更改时将其始终设置为 true。

answer变量在这里是一个字符串。在 Javascript 中,如果您!在其计算结果为 的字符串上使用逻辑非 , 运算符false。所以我们使用它两次来获得true. 例如,当我们点击“19”时,这将是这样的:

selected: {
    "19": !!"19"
}

!!"19"您可以在 Javascript 控制台中尝试,您将获得true. 我们在这里只使用变量,而不是使用真实值:[answer]: !!answer

现在,我将在上一个代码示例中稍微改变一下这个语法。如果您查看setState的文档,您会发现它是一个异步操作。所以,React 团队不鼓励我们像这样直接使用它,特别是如果我们使用我们状态中任何部分的先前状态。实际上,在我们的示例中我们并没有这样做,但最好使用回调this.setState。如果这个解释对你来说还不够,请去阅读官方文档。这次我们如何使用它:

  handleClick = answer => {
    this.setState(prevState =>
      ({
        selected: {
          [answer]: !prevState.selected.answer,
        }
      })
    )
  };

如您所见setState,此处接受回调并使用prevState(或您给它的名称)来响应先前的状态。selected.answer现在,由于我们以前的状态中没有任何内容,所以它是undefined第一位的。因此,我们可以使用!prevState.selected.answer来生成值true,而不是在这里使用两个逻辑非操作数。请记住,在前面的示例中,我们这里有一个字符串,而不是未定义的值。这就是为什么我们在那里使用两个逻辑非操作数。

现在,这是适合您情况的最后一个代码。您将得到答案作为道具,然后渲染另一个组件来显示这些答案。我像您一样使用三个组件,然后呈现各个答案。

const answers = ["1", "19", "21", "90", "-1"]
const AnswerChoices = () => (
  <SpecificAnswerChoice answers={answers} />
)

class SpecificAnswerChoice extends React.Component {
  state = {
    selected: {},
  }

  handleClick = answer =>
    this.setState(prevState =>
      ({
        selected: {
          [answer]: !prevState.selected.answer,
        }
      })
    );

  
  render() {
    const { answers } = this.props;
    return (
      <div>
        {
          answers.map( individualAnswer => (
            <EachIndividualAnswer
            individualAnswer={individualAnswer}
            onClick={this.handleClick}
            selected={this.state.selected}
            key={individualAnswer}
          />
          ) )
        }
      </div>
    )
  }
}

// Again, destructring. Instead of (props) we use ({....})
// and pick our individual props here.
const EachIndividualAnswer = ({selected,individualAnswer, onClick}) => {
  const handleClick = () => onClick(individualAnswer)
  return (
    <div>
    <ul>
        {
          <li
            onClick={handleClick}
            className={
              selected[individualAnswer] ? 'colored' : ''
            }
          >{individualAnswer}
          </li>
        }
      </ul>
    </div>
  )
}

const rootElement = document.getElementById("root");
ReactDOM.render(<AnswerChoices />, rootElement);
.colored {
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

我将EachIndividualAnswer组件声明为功能组件,因为它不需要是一个类。另外,我为点击事件传递了一个handleClick道具。使用此处理程序,子组件发送答案,父组件取回它并更新其状态。获得的一个道具EachIndividualAnswerselected状态。所以它决定是否添加一个类。因此,我们的selected状态存在于SpecificAnswerChoice组件中,子组件将其作为道具获取。最后,answers如您所见,此组件从其父组件获取。


推荐阅读