React实战教程之从零开始手把手教你使用 React 最新特性Hooks API 打造一款计算机知识测验App
前言
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>