项目演示地址

项目演示地址

项目源码

项目源码

其他版本教程

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 {
width: 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>
);
};

程序运行效果:

首页



点击开始后,显示问题:

当前项目目录结构为:

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);
}
}; ...
};

运行效果:



目前代码结构:

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>
)} ...
</>
);
};

最后

从0开始,手把手教你使用React开发答题App的更多相关文章

  1. 手把手教你使用 Clion 开发 Linux C++ 项目

    手把手教你使用 Clion 开发 Linux C++ 项目 关于CLion CLion是一款专为开发C及C++所设计的跨平台IDE.它是以IntelliJ为基础设计的,包含了许多智能功能来提高开发人员 ...

  2. 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统系列博文索引

    近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座,来讲解如何利用FineUI快速开 ...

  3. 手把手教你Chrome扩展开发:本地存储篇

    手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单 手把手教你开发Chrome扩展二:为html添加行为 手把手教你开发Chrome扩展三:关于本地存储数据 HTML5 ...

  4. 手把手教你搭建织女星开发板RISC-V开发环境

    前言 Windows环境下搭建基于Eclipse + RISC-V gcc编译器的RISC-V开发环境,配合openocd调试软件,可以实现RISC-V内核程序的编译.下载和调试. 准备工作 工欲善其 ...

  5. 0、手把手教React Native实战之开山篇

    ##作者简介 东方耀    Android开发   RN技术   facebook   github     android ios  原生开发   react reactjs nodejs 前端   ...

  6. React实战教程之从零开始手把手教你使用 React 最新特性Hooks API 打造一款计算机知识测验App

    项目演示地址 项目演示地址 项目代码结构 前言 React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但早期 React 类组件的写法略显繁琐.React Hoo ...

  7. 从0开始,手把手教你用Vue开发一个答题App01之项目创建及答题设置页面开发

    项目演示 项目演示 项目源码 项目源码 教程说明 本教程适合对Vue基础知识有一点了解,但不懂得综合运用,还未曾使用Vue从头开发过一个小型App的读者.本教程不对所有的Vue知识点进行讲解,而是手把 ...

  8. 从0开始,手把手教你用Vue开发一个答题App

    项目演示 项目演示 项目源码 项目源码 教程说明 本教程适合对Vue基础知识有一点了解,但不懂得综合运用,还未曾使用Vue从头开发过一个小型App的读者.本教程不对所有的Vue知识点进行讲解,而是手把 ...

  9. 倒计时0日!Apache DolphineScheduler4月 Meetup 大佬手把手教你大数据开发,离线调度

    随着互联网技术和信息技术的发展,信息的数据化产生了许多无法用常规工具量化.处理和捕捉的数字信息.面对多元的数据类型,海量的信息价值,如何有效地对大数据进行挖掘分析,对大数据工作流进行调度,是保障企业大 ...

随机推荐

  1. mysql 大表mysqldump迁移方案

    场景 一张历史表product_history 500万数据,凌晨的才会将正式表的数据迁移到历史表,此次需求将历史表迁移到一个更便宜的数据库实例进行存储. 条件 1.此表不是实时写,凌晨才会更新 2. ...

  2. 实验二 Linux系统简单文件操作命令

    项目 内容 这个作业属于哪个课程 班级课程的主页链接 这个作业的要求在哪里 作业要求链接接地址 学号-姓名 17041428-朱槐健 作业学习目标 1.学习在Linux系统终端下进行命令行操作 2.掌 ...

  3. 双剑合璧 Nacos 结合 Sentinel 实现流量安全控制

    Alibaba Sentinel 是一款高性能且轻量级的流量控制.熔断降级解决方案.是面向分布式服务架构的高可用流量控制组件. Sentinel 官网:https://sentinelguard.io ...

  4. 获取随机User-Agent的请求头

    ''' 获取随机User-Agent的请求头 ''' import random #用户代理User-Agent列表 USER_AGENTS = [ "Mozilla/5.0 (Macint ...

  5. CISCN 2019-ikun

    0x01 进去网址,页面如下: 刚开始有个登陆和注册的按钮,上图是我已经注册后登陆成功后的页面,我们发现在图的左下角给了一个关键的提示,购买LV6,通过寻找我们发现页面数很多,大概500页,一个一个找 ...

  6. Excel常用公式大全

    公式是单个或多个函数的结合运用. AND “与”运算,返回逻辑值,仅当有参数的结果均为逻辑“真(TRUE)”时返回逻辑“真(TRUE)”,反之返回逻辑“假(FALSE)”. 条件判断 AVERAGE ...

  7. mybatis 学习教程

    https://www.cnblogs.com/ashleyboy/category/1246107.html

  8. 2.K8S的核心资源管理方法

    目录 1.1陈述式资源管理方法 1.1.1.管理名称空间资源 1.1.2.管理Deployment资源 1.1.3.管理Service资源 1.1.4.kubectl用法总结 1.2.声明式资源管理方 ...

  9. Linux下安装java环境

    准备工作: linux环境 xshell6 1.在Windows本地www,oracle.com下载对应的linux系统的JDK安装包,我下载的是 2.下载下来后,通过xftp远程传输到linux服务 ...

  10. Python3使用cookielib模块

    同时使用过python2和python3的应该都知道,好多模块在python2中能直接安装,但是到了python3中却无法安装直接使用,同样python3中的好些模块在python2中也是一样 如下: ...