javascript - 在Reactjs中管理状态时遇到麻烦

标签 javascript html css reactjs

我一直在练习JavaScript和ReactJs,一段时间以来我一直陷入一个问题。基本上,我试图使用ReactJs重写我的HTML,CSS,Javascript项目。

这是我的问题:(关于React代码)。说我单击问题1的第一个答案选择,类名称因此更改,样式更改(背景变为黑色)并且isClicked变为true(这两个都是EachIndividualAnswer类内部的状态)。如果我然后单击第二个答案选择,则我希望第一个答案选择的样式(以及该问题的所有其他答案选择)为null,并且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;

最佳答案

如果我理解正确,这是一个可能的答案。我完全模仿这种情况,因此这不是您的完整解决方案。



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;


很有用。有关更多信息,请阅读this answer,当然也请阅读the official documentation。您会看到破坏整个React世界,因为有些人倾向于在合适的地方使用它。过度使用它可能并不总是那么好。有时,使阅读代码变得更加困难。但是,当您在适当的地方使用它时,它可以节省时间,甚至可以提高可读性。

我们在这里保持答案的状态。在原始代码中,您将它们当作道具。在根据道具进行解释后,我将提供另一个答案。在这里selected是真正的交易,请声明我们保留选定的元素。

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


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

answer变量是一个字符串。在Javascript中,如果对字符串使用运算符!而不是逻辑运算符,则它的值为false。因此,我们两次使用它来获取true。例如,当我们单击“ 19”时,将如下所示:

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


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

现在,我将在上一个代码示例中对此语法进行一些更改。如果您查看setState的文档,将会看到它是一个异步操作。因此,React团队不鼓励我们像这样直接使用它,特别是如果我们使用状态中任何部分的先前状态。实际上,在我们的示例中我们并没有这样做,但是最好使用this.setState的回调。如果此说明对您来说不够用,请阅读official documentation。这是我们这次的用法:

  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道具。使用此处理程序,子级发送答案,父级组件将其取回并更新其状态。 EachIndividualAnswer获得的一个道具是selected状态。因此,它决定是否添加一个类。因此,我们的selected状态驻留在SpecificAnswerChoice组件中,子代将其作为道具。最后,如您所见,此组件从其父级获取answers

关于javascript - 在Reactjs中管理状态时遇到麻烦,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51508892/

相关文章:

javascript - 有没有办法从当前函数中获取当前函数?

javascript - 如何加载 leanmodal div 内容 onclick

css - Div 水平滚动条 - 悬停时子 div 背景不会获得全宽

html - Dashing.io 带有字幕的小部件

css - Div 不适合容器

javascript - React OnAnimation结束问题

javascript - 如何向 React Material UI 扩展面板添加动态内容,同时一次只保留一个事件选项卡功能

html - 连续两个 div 后的第三个 div

javascript - 如何给每个圆圈一个随机的颜色?

javascript - 悬停时展开当前 div 并缩小其他 div