• 从0开始,手把手教你使用React开发答题App


    项目演示地址

    项目演示地址

    项目源码

    项目源码

    视频教程

    视频教程

    其他版本教程

    Vue版本

    小程序版本

    项目代码结构

    前言

    React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但早期 React 类组件的写法略显繁琐。React Hooks 是 React 16.8 发布以来最吸引人的特性之一,她简化了原有代码的编写,是未来 React 应用的主流写法。

    本文通过一个实战小项目,手把手从零开始带领大家快速入门React Hooks。本项目线上演示地址:

    在本项目中,会用到以下知识点:

    • React 组件化设计思想
    • React State 和 Props
    • React 函数式组件的使用
    • React Hooks useState 的使用
    • React Hooks useEffect 的使用
    • React 使用 Axios 请求远程接口获取问题及答案
    • React 使用Bootstrap美化界面

    Hello React

    (1)安装node.js 官网链接

    (2)安装vscode 官网链接

    (3)安装 creat-react-app 功能组件,该组件可以用来初始化一个项目, 即按照一定的目录结构,生成一个新项目。
    打开cmd 窗口 输入:

    npm install --g create-react-app 
    npm install --g yarn
    

    (-g 代表全局安装)

    如果安装失败或较慢。需要换源,可以使用淘宝NPM镜像,设置方法为:

    npm config set registry https://registry.npm.taobao.org
    

    设置完成后,重新执行

    npm install --g create-react-app
    npm install --g yarn
    

    (4)在你想创建项目的目录下 例如 D:/project/ 打开cmd命令 输入

    create-react-app react-exam
    

    去使用creat-react-app命令创建名字是react-exam的项目

    安装完成后,移至新创建的目录并启动项目

    cd react-exam
    yarn start
    

    一旦运行此命令,localhost:3000新的React应用程序将弹出一个新窗口。

    项目目录结构

    右键react-exam目录,使用vscode打开该目录。
    react-exam项目目录中有一个/public和/src目录,以及node_modules,.gitignore,README.md,和package.json。

    在目录/public中,重要文件是index.html,其中一行代码最重要

    <div id="root"></div>
    

    该div做为我们整个应用的挂载点

    /src目录将包含我们所有的React代码。

    要查看环境如何自动编译和更新您的React代码,请找到文件/src/App.js
    将其中的

            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
    

    修改为

            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              和豆约翰 Learn React
            </a>
    

    保存文件后,您会注意到localhost:3000编译并刷新了新数据。

    React-Exam项目实战

    1. 首页制作

    1.安装项目依赖,在package.json中添加:

      "dependencies": {
        "@testing-library/jest-dom": "^4.2.4",
        "@testing-library/react": "^9.3.2",
        "@testing-library/user-event": "^7.1.2",
        "react": "^16.13.1",
        "react-dom": "^16.13.1",
        "react-scripts": "3.4.1",
        "axios": "^0.19.2",
        "bootstrap": "^4.5.0",
        "he": "^1.2.0",
        "react-loading": "^2.0.3",
        "reactstrap": "^8.4.1"
      },
    

    执行命令:

    yarn install
    

    修改index.js,导入bootstrap样式

    import "bootstrap/dist/css/bootstrap.min.css";
    

    修改App.css代码

    html {
       80%;
      margin-left: 10%;
      margin-top: 2%;
    }
    
    .ansButton {
      margin-right: 4%;
      margin-top: 4%;
    }
    

    修改App.js,引入Quiz组件

    import React from 'react';
    import './App.css'
    import { Quiz } from './Exam/Quiz';
    
    function App() {
      return (
        <div className = 'layout'>
        <Quiz></Quiz>
        </div>
      );
    }
    
    export default App;
    
    

    在项目src目录下新增Exam目录,Exam目录中新建Quiz.js

    Quiz组件的定义如下:
    Quiz.js,引入开始页面组件Toggle。

    import React, { useState } from "react";
    import { Toggle } from "./Toggle";
    export const Quiz = () => {
      const [questionData, setQuestionData] = useState([]);
      const questions = questionData.map(({ question }) => [question]);
      const answers = questionData.map(({ incorrect_answers, correct_answer }) =>
        [correct_answer, incorrect_answers].flat()
      );
      return (
        <>
          <Toggle
            setQuestionData={setQuestionData}
          />
        </>
      );
    };
    

    Toggle.js,点击开始按钮,通过axios访问远程接口,获得题目及答案。

    import React from "react";
    import axios from "axios";
    import ToggleHeader from "./ToggleHeader";
    import {
      Button,
      Form,
    } from "reactstrap";
    
    export const Toggle = ({
      setQuestionData,
    }) => {
      const getData = async () => {
        try {
          const incomingData = await axios.get(
            `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple`
          );
          setQuestionData(incomingData.data.results);
        } catch (err) {
          console.error(err);
        }
      };
    
      return (
        <>
          <ToggleHeader />
          <Form
            onSubmit={(e) => {
              e.preventDefault();
              getData();
            }}
          >
            <Button color="primary">开始</Button>
          </Form>
        </>
      );
    };
    
    

    ToggleHeader.js

    import React from "react";
    import { Jumbotron, Container} from "reactstrap";
    
    export default function ToggleHeader() {
      return (
        <Jumbotron fluid>
          <Container fluid>
            <h1 className="display-4">计算机知识小测验</h1>
          </Container>
        </Jumbotron>
      );
    }
    

    https://opentdb.com/api.php接口返回的json数据格式为

    {
    	"response_code": 0,
    	"results": [{
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "The numbering system with a radix of 16 is more commonly referred to as ",
    		"correct_answer": "Hexidecimal",
    		"incorrect_answers": ["Binary", "Duodecimal", "Octal"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "This mobile OS held the largest market share in 2012.",
    		"correct_answer": "iOS",
    		"incorrect_answers": ["Android", "BlackBerry", "Symbian"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "How many values can a single byte represent?",
    		"correct_answer": "256",
    		"incorrect_answers": ["8", "1", "1024"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "In computing, what does MIDI stand for?",
    		"correct_answer": "Musical Instrument Digital Interface",
    		"incorrect_answers": ["Musical Interface of Digital Instruments", "Modular Interface of Digital Instruments", "Musical Instrument Data Interface"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "In computing, what does LAN stand for?",
    		"correct_answer": "Local Area Network",
    		"incorrect_answers": ["Long Antenna Node", "Light Access Node", "Land Address Navigation"]
    	}]
    }
    

    程序运行效果:

    当前项目目录结构为:

    2. 问题展示页面

    Quiz.js,新增toggleView变量用来切换视图。

      const [toggleView, setToggleView] = useState(true);
    

    Quiz.js,其中Question和QuestionHeader 组件,参见后面。

    import { Question } from "./Question";
    import { Jumbotron } from "reactstrap";
    import QuestionHeader from "./QuestionHeader";
    
    ...
    export const Quiz = () => {
      var [index, setIndex] = useState(0);
      const [questionData, setQuestionData] = useState([]);
    ...
     return (
        <>
          {toggleView && (
            <Toggle
              setIndex={setIndex}
              setQuestionData={setQuestionData}
              setToggleView={setToggleView}
            />
          )}
           {!toggleView &&
            (
              <Jumbotron>
                <QuestionHeader
                  setToggleView={setToggleView}
                />
                <Question question={questions[index]} />
              </Jumbotron>
            )}
        </>
      );
    

    使用index控制题目索引

    var [index, setIndex] = useState(0);
    

    修改Toggle.js
    获取完远程数据,通过setToggleView(false);切换视图。

    export const Toggle = ({
      setQuestionData,
      setToggleView,
      setIndex,
    }) => {
      
    ...
    
      return (
        <>
          <ToggleHeader />
          <Form
            onSubmit={(e) => {
              e.preventDefault();
              getData();
              setToggleView(false);
              setIndex(0);
            }}
          >
            <Button color="primary">开始</Button>
          </Form>
        </>
      );
    };
    

    QuestionHeader.js代码:
    同样的,点击 返回首页按钮 setToggleView(true),切换视图。

    import React from "react";
    import { Button } from "reactstrap";
    export default function QuestionHeader({ setToggleView, category }) {
      return (
        <>
          <Button color="link" onClick={() => setToggleView(true)}>
            返回首页
          </Button>
        </>
      );
    }
    
    

    Question.js代码
    接受父组件传过来的question对象,并显示。
    其中he.decode是对字符串中的特殊字符进行转义。

    import React from "react";
    import he from "he";
    export const Question = ({ question }) => {
      // he is a oddly named library that decodes html into string values
    
      var decode = he.decode(String(question));
    
      return (
        <div>
          <hr className="my-2" />
          <h1 className="display-5">
            {decode}
          </h1>
          <hr className="my-2" />
          <br />
        </div>
      );
    };
    
    

    程序运行效果:
    首页

    点击开始后,显示问题:

    当前项目目录结构为:
    image.png

    3. 加载等待动画

    新增LoadingSpin.js

    import React from "react";
    import { Spinner } from "reactstrap";
    export default function LoadingSpin() {
      return (
        <>
          <Spinner type="grow" color="primary" />
          <Spinner type="grow" color="secondary" />
          <Spinner type="grow" color="success" />
          <Spinner type="grow" color="danger" />
        </>
      );
    }
    
    

    修改Quiz.js

    
    import LoadingSpin from "./LoadingSpin";
    
    export const Quiz = () => {
    
      const [isLoading, setLoading] = useState(false);
    
    
      return (
        <>
          {toggleView && (
            <Toggle
              ...
              setLoading={setLoading}
            />
          )}
          {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
              ...
            ))}
        </>
      );
    };
    
    

    修改Toggle.js

    
    
    export const Toggle = ({
    ...
      setLoading,
    }) => {
      const getData = async () => {
        try {
          setLoading(true);
          const incomingData = await axios.get(
            `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple`
          );
          setQuestionData(incomingData.data.results);
          setLoading(false);
        } catch (err) {
          console.error(err);
        }
      };
    
     ...
    };
    
    

    运行效果:

    目前代码结构:
    image.png

    4. 实现下一题功能

    新增Answer.js,用户点击下一题按钮,修改index,触发主界面刷新,显示下一题:

    import React from "react";
    import { Button } from "reactstrap";
    
    export const Answer = ({ setIndex, index }) => {
      function answerResult() {
        setIndex(index + 1);
      }
    
      return (
        <Button className="ansButton" onClick={answerResult}>
          下一题
        </Button>
      );
    };
    
    

    修改Quiz.js,添加Answer组件:

    import { Answer } from "./Answer";
    ...
     {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
              <Jumbotron>
                ...
                <Answer
                setIndex={setIndex}
                index={index}
                />
              </Jumbotron>
    
            ))}
    

    运行效果:

    点击下一题:


    5. 实现选项展示

    新增AnswerList.js。
    通过属性answers传进来的选项列表,需要被打乱顺序(shuffle )

    import React from "react";
    import { Answer } from "./Answer";
    
    export const AnswerList = ({ answers, index, setIndex }) => {
      if (answers) var correctAns = answers[0];
    
      const shuffle = (array) => {
        return array.sort(() => Math.random() - 0.5);
      };
      const arrayCheck = (arg) => {
        return Array.isArray(arg) ? arg : [];
      };
    
      return (
        <>
          {shuffle(arrayCheck(answers)).map((text,ind) => (
            <Answer
              text={text}
              correct={correctAns}
              setIndex={setIndex}
              index={index}
              key={ind}
            />
          ))}
        </>
      );
    };
    
    

    修改Answer.js

    import React from "react";
    import he from "he";
    import { Button } from "reactstrap";
    export const Answer = ({ text, correct, setIndex, index }) => {
      function answerResult() {
        setIndex(index + 1);
      }
    
    
      var decode = he.decode(String(text));
    
      return (
        <Button className="ansButton" onClick={answerResult}>
          {decode}
        </Button>
      );
    };
    
    

    修改Quiz.js

    // import { Answer } from "./Answer";
    import { AnswerList } from "./AnswerList";
    
    
    export const Quiz = () => {
    ...
      return (
        <>
          ...
          {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
            ...
                <AnswerList
                  answers={answers[index]}
                  index={index}
                  setIndex={setIndex}
                />
              </Jumbotron>
    
            ))}
        </>
      );
    };
    
    

    运行效果:

    项目结构:

    6. 记录用户成绩

    修改quiz.js,添加setResult,并传递给AnswerList

    
    export const Quiz = () => {
     
      var [result, setResult] = useState(null);
    ...
      return (
        <>
        ...
          {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
              <Jumbotron>
              ...
                <AnswerList
                  answers={answers[index]}
                  index={index}
                  setIndex={setIndex}
                  setResult={setResult}
                />
              </Jumbotron>
    
            ))}
        </>
      );
    };
    
    

    修改AnswerList.js,传递setResult

    import React from "react";
    import { Answer } from "./Answer";
    
    export const AnswerList = ({ answers, index,setResult, setIndex }) => {
    ...
    
      return (
        <>
          {shuffle(arrayCheck(answers)).map((text,ind) => (
            <Answer
              text={text}
              correct={correctAns}
              setIndex={setIndex}
              setResult={setResult}
              index={index}
              key={ind}
            />
          ))}
        </>
      );
    };
    
    

    修改Answer.js,用户点击选项,回调setResult,通知Quiz组件,本次选择是对是错。

    import React from "react";
    import { Button } from "reactstrap";
    import he from 'he'
    
    
    export const Answer = ({ text, correct, setResult,setIndex, index }) => {
      function answerResult() {
        setIndex(index + 1);
        correct === text ? setResult(true) : setResult(false);
      }
    
      var decode = he.decode(String(text));
    
      return (
        <Button className="ansButton" onClick={answerResult}>
          {decode}
        </Button>
      );
    };
    
    

    修改Quiz.js,放一个隐藏的GameOver组件,每当index发生变化的时候,触发GameOver中的useEffect代码,累计用户答对题目的数目(setRight)

    
    import GameOver from "./GameOver";
    
    export const Quiz = () => {
    
      const [right, setRight] = useState(0);
      const [gameIsOver, setGameOver] = useState(false);
    
    
      return (
       <>
          {toggleView && (
            <Toggle
              setIndex={setIndex}
              setQuestionData={setQuestionData}
              setToggleView={setToggleView}
              setLoading={setLoading}
            />
          )}
          {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) :
              (
                <Jumbotron>
                  <QuestionHeader
                    setToggleView={setToggleView}
                  />
                  <Question question={questions[index]} />
                  <AnswerList
                    answers={answers[index]}
                    index={index}
                    setIndex={setIndex}
                    setResult={setResult}
                  />
                </Jumbotron>
          ))}
          <GameOver
            right={right}
            setRight={setRight}
            quizLength={questions.length}
            setGameOver={setGameOver}
            result={result}
            index={index}
          />
        </>
      );
    };
    
    

    新增GameOver.js组件,当index === quizLength && index时,setGameOver(true)设置游戏结束,显示用户得分。

    import React, { useEffect } from "react";
    
    export default function GameOver({
      right,
      setRight,
      setGameOver,
      index,
      quizLength,
      result,
    }) {
      useEffect(() => {
        if (result === true) {
          setRight(right + 1);
        } 
    
        if (index === quizLength && index) {
          setGameOver(true);
        }
      }, [index]);
    
      return <div></div>;
    }
    

    7. 游戏结束,展示用户得分

    新增ScoreBoard.js

    import React from "react";
    
    export const ScoreBoard = ({ finalScore, right }) => {
      // if index === 0 then right === 0 --> this way when index is reset in toggle so is right answers
      const scoreFormatted = score => {
        if (score === 1) {
          return 100;
        } else if (score === 0) {
          return 0;
        } else {
          return score.toFixed(2) * 100;
        }
      }
    
      return (
        <>
          <>
            <h1 className="display-4">Correct Answers: {right}</h1>
            <hr className="my-2" />
    
            <h1 className="display-4">
              Final Score: %{scoreFormatted(finalScore)}
            </h1>
    
            <hr className="my-2" />
          </>
          <p>谢谢使用 </p>
        </>
      );
    };
    
    

    ScoreHeader.js

    import React from "react";
    import { Button } from "reactstrap";
    export default function ScoreHeader({ setGameOver, setToggleView }) {
      return (
        <Button
          color="link"
          onClick={() => {
            setGameOver(false);
            setToggleView(true);
          }}
        >
          返回首页
        </Button>
      );
    }
    
    

    修改Quiz.js,当gameIsOver 变量为true时,显示得分页面。

    import { ScoreBoard } from "./ScoreBoard";
    import ScoreHeader from "./ScoreHeader";
    
    export const Quiz = () => {
    
    ...
    
      return (
        <>
     
          {!toggleView &&
            !gameIsOver &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
              ...
              ))}
                
          {gameIsOver && (
            <Jumbotron>
              <ScoreHeader
                setToggleView={setToggleView}
                setGameOver={setGameOver}
              />
              <ScoreBoard right={right} finalScore={right / index} />
            </Jumbotron>
          )}
          
       ...
        </>
      );
    };
    
    

    最后

  • 相关阅读:
    Vue 左右翻页,点赞动画
    gitbook 使用
    css3 序列帧动画抖动
    ios添加-webkit-overflow-scrolling依然卡顿
    css 多行省略号兼容移动端
    Vue粒子特效(vue-particles插件)
    css3 渐变色兼容移动端
    前端性能优化:客户端从输入到展示讲解
    前端通信:ajax设计方案(六)--- 全局配置、请求格式拓展和优化、请求二进制类型、浏览器错误搜集以及npm打包发布
    前端通信:ajax设计方案(五)--- 集成promise规范,更优雅的书写代码(改迭代已作废,移步迭代10)
  • 原文地址:https://www.cnblogs.com/songboriceboy/p/13278805.html
Copyright © 2020-2023  润新知