项目演示地址

项目演示地址

项目源码

项目源码

其他版本教程

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. test tt=0 <=>test(0)

    class test{ int mvalue; public: test(int i){ cout << "test(int i) =" << i < ...

  2. Java学习之IO流及网络编程

    一.字节 1.1字节输入流(java.io.InputStream) ​ 此抽象类是表示字节输入流的所有类的超类 1.1.1定义了所有子类共性的方法: ​ int read() 从输入流中读取数据的下 ...

  3. PuTTY通过SSH连接上Ubuntu20.04

    在PuTTY中连接到Ubuntu20.04大致需要几个步骤(不一定对应文本中的序号):1) 安装opensh-server (Ubuntu安装好之后 ,一般openssh-client自动已经安装好) ...

  4. Codeforces Round #648 (Div. 2)

    链接 : https://codeforces.com/contest/1365/problems problem A 统计可用的行和列的最小值, 模2输出即可 /* * Author: RoccoS ...

  5. ViewDragHelper类的基本使用

    在android的开发包android.support.v4.widget中有一个ViewDragHelper类.这个类的作用是帮助我们处理View的拖拽滑动.在一个ViewGroup类的内部定义一个 ...

  6. 一文梳理JS事件

    JavaScript与HTML的交互是通过事件进行的.事件,就是文档或浏览器窗口发生的一些特定的交互瞬间. 事件流 事件捕获 事件冒泡 事件处理程序 事件委托 1. 事件流 如果单机页面上的某个按钮, ...

  7. cb10a_c++_顺序容器的操作3关系运算符

    cb10a_c++_cb09a_c++_顺序容器的操作3 2 顺序容器的操作3 3 关系运算符 4 所有的容器类型都可以使用 5 比较的容器必须具有相同的容器类型,double不能与int作比较 6 ...

  8. fork,vfork和clone底层实现

    分类: LINUX2011-10-13 09:33 1116人阅读 评论(0) 收藏 举报 structdstsignalthreadnulldomain fork,vfork,clone都是linu ...

  9. 关于 JOIN 耐心总结,学不会你打我系列

    现在随着各种数据库框架的盛行,在提高效率的同时也让我们忽略了很多底层的连接过程,这篇文章是对 SQL 连接过程梳理,并涉及到了现在常用的 SQL 标准. 其实标准就是在不同的时间,制定的一些写法或规范 ...

  10. Python3-datetime模块-日期与时间

    官方文档 http://python.usyiyi.cn/translate/python_352/library/datetime.html 代码示例 from datetime import da ...